mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-03 13:00:22 +01:00
wider migration from old starter
This commit is contained in:
143
StarterNG/Views/AboutWindow.cs
Normal file
143
StarterNG/Views/AboutWindow.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using StarterNG.Infrastructure;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public static class AboutWindow
|
||||
{
|
||||
private const string RepositoryUrl = "https://github.com/maj00r/StarterNG";
|
||||
|
||||
private static readonly string[] ChangelogNames =
|
||||
{
|
||||
"changelog.txt", "starter/changelog.txt", "CHANGELOG.md"
|
||||
};
|
||||
|
||||
public static Window Create()
|
||||
{
|
||||
var win = new Window
|
||||
{
|
||||
Title = App.Loc["AboutTitle"],
|
||||
Width = 640,
|
||||
Height = 460,
|
||||
MinWidth = 480,
|
||||
MinHeight = 320,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
|
||||
var header = new StackPanel { Spacing = 2, Margin = new Avalonia.Thickness(20, 18, 20, 12) };
|
||||
header.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "Starter MaSzyna",
|
||||
FontSize = 22,
|
||||
FontWeight = FontWeight.Bold
|
||||
});
|
||||
header.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"{App.Loc["Version"]} {AppInfo.Version}",
|
||||
Opacity = 0.8
|
||||
});
|
||||
header.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["AboutForSimulator"],
|
||||
Opacity = 0.8
|
||||
});
|
||||
|
||||
var repo = new Button
|
||||
{
|
||||
Content = RepositoryUrl,
|
||||
Tag = RepositoryUrl,
|
||||
Padding = new Avalonia.Thickness(0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Avalonia.Thickness(0, 6, 0, 10),
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
repo.Classes.Add("Basic");
|
||||
repo.Click += (_, _) => OpenUrl(RepositoryUrl);
|
||||
header.Children.Add(repo);
|
||||
|
||||
var changelog = new TextBox
|
||||
{
|
||||
Text = ReadChangelog(),
|
||||
IsReadOnly = true,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.NoWrap,
|
||||
FontFamily = MonoFont.Family,
|
||||
FontSize = 12,
|
||||
Margin = new Avalonia.Thickness(20, 0, 20, 12)
|
||||
};
|
||||
ScrollViewer.SetHorizontalScrollBarVisibility(changelog, ScrollBarVisibility.Auto);
|
||||
ScrollViewer.SetVerticalScrollBarVisibility(changelog, ScrollBarVisibility.Auto);
|
||||
|
||||
var close = new Button
|
||||
{
|
||||
Content = App.Loc["Close"],
|
||||
MinWidth = 90,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
close.Classes.Add("Accent");
|
||||
close.Click += (_, _) => win.Close();
|
||||
|
||||
var footer = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Avalonia.Thickness(20, 0, 20, 16),
|
||||
Children = { close }
|
||||
};
|
||||
|
||||
var root = new DockPanel();
|
||||
DockPanel.SetDock(header, Dock.Top);
|
||||
DockPanel.SetDock(footer, Dock.Bottom);
|
||||
root.Children.Add(header);
|
||||
root.Children.Add(footer);
|
||||
|
||||
var logLabel = new TextBlock
|
||||
{
|
||||
Text = App.Loc["AboutChangelog"],
|
||||
Margin = new Avalonia.Thickness(20, 0, 20, 4),
|
||||
Opacity = 0.8
|
||||
};
|
||||
DockPanel.SetDock(logLabel, Dock.Top);
|
||||
root.Children.Add(logLabel);
|
||||
root.Children.Add(changelog);
|
||||
|
||||
win.Content = root;
|
||||
return win;
|
||||
}
|
||||
|
||||
private static void OpenUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Infrastructure.Diagnostics.Log($"Open {url}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadChangelog()
|
||||
{
|
||||
foreach (string name in ChangelogNames)
|
||||
{
|
||||
string path = Path.Combine(Directory.GetCurrentDirectory(), name);
|
||||
if (!File.Exists(path)) continue;
|
||||
try
|
||||
{
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnostics.Log($"Changelog {path}", ex);
|
||||
}
|
||||
}
|
||||
return App.Loc["AboutNoChangelog"];
|
||||
}
|
||||
}
|
||||
187
StarterNG/Views/AdvancedSettingsWindow.cs
Normal file
187
StarterNG/Views/AdvancedSettingsWindow.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using StarterNG.Infrastructure;
|
||||
using SettingsModel = StarterNG.Classes.Settings;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public sealed class AdvancedSettingsWindow : Window
|
||||
{
|
||||
private readonly TextBox _log = new()
|
||||
{
|
||||
IsReadOnly = true,
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.NoWrap,
|
||||
FontFamily = MonoFont.Family,
|
||||
FontSize = 12
|
||||
};
|
||||
|
||||
private readonly CheckBox _compressTex = new();
|
||||
private readonly CheckBox _mipmaps = new();
|
||||
private readonly CheckBox _scaleSpeculars = new();
|
||||
private readonly CheckBox _shaderGamma = new();
|
||||
private readonly CheckBox _useGles = new();
|
||||
private readonly CheckBox _convertModels = new();
|
||||
private readonly CheckBox _resourceMove = new();
|
||||
private readonly CheckBox _resourceSweep = new();
|
||||
private readonly CheckBox _ignoreIrrelevant = new();
|
||||
private readonly Slider _threads = new() { Minimum = 0, Maximum = 16, TickFrequency = 1, IsSnapToTickEnabled = true };
|
||||
private readonly TextBlock _threadsValue = new() { VerticalAlignment = VerticalAlignment.Center, MinWidth = 24 };
|
||||
|
||||
public event Action<string>? ToolRequested;
|
||||
|
||||
public AdvancedSettingsWindow()
|
||||
{
|
||||
Title = App.Loc["AdvancedSettings"];
|
||||
Width = 620;
|
||||
Height = 470;
|
||||
MinWidth = 480;
|
||||
MinHeight = 360;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
|
||||
_compressTex.Content = App.Loc["CompressTextures"];
|
||||
_mipmaps.Content = App.Loc["ScreenMipmaps"];
|
||||
_scaleSpeculars.Content = App.Loc["ScaleSpeculars"];
|
||||
_shaderGamma.Content = App.Loc["ShaderGamma"];
|
||||
_useGles.Content = App.Loc["UseGLES"];
|
||||
_convertModels.Content = App.Loc["ExtendedModelConversion"];
|
||||
_resourceMove.Content = App.Loc["GfxResourceMove"];
|
||||
_resourceSweep.Content = App.Loc["GfxResourceSweep"];
|
||||
_ignoreIrrelevant.Content = App.Loc["IgnoreIrrelevantTrains"];
|
||||
_threads.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.Property == RangeBase.ValueProperty)
|
||||
_threadsValue.Text = ((int)_threads.Value).ToString();
|
||||
};
|
||||
|
||||
Load();
|
||||
|
||||
var tabs = new TabControl();
|
||||
tabs.Items.Add(new TabItem { Header = App.Loc["AdvLogPage"], Content = BuildLogPage() });
|
||||
tabs.Items.Add(new TabItem { Header = App.Loc["Advanced"], Content = BuildOptionsPage() });
|
||||
tabs.Items.Add(new TabItem { Header = App.Loc["Tools"], Content = BuildToolsPage() });
|
||||
|
||||
var close = new Button
|
||||
{
|
||||
Content = App.Loc["Close"],
|
||||
MinWidth = 100,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
close.Classes.Add("Accent");
|
||||
close.Click += (_, _) => { Store(); Close(); };
|
||||
|
||||
var root = new DockPanel { Margin = new Avalonia.Thickness(10) };
|
||||
DockPanel.SetDock(close, Dock.Bottom);
|
||||
root.Children.Add(close);
|
||||
root.Children.Add(tabs);
|
||||
Content = root;
|
||||
}
|
||||
|
||||
private Control BuildLogPage()
|
||||
{
|
||||
_log.Text = Diagnostics.Text;
|
||||
|
||||
var clear = new Button
|
||||
{
|
||||
Content = App.Loc["AdvClearLog"],
|
||||
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
clear.Classes.Add("Flat");
|
||||
clear.Click += (_, _) => { Diagnostics.Clear(); _log.Text = ""; };
|
||||
|
||||
ScrollViewer.SetHorizontalScrollBarVisibility(_log, ScrollBarVisibility.Auto);
|
||||
ScrollViewer.SetVerticalScrollBarVisibility(_log, ScrollBarVisibility.Auto);
|
||||
|
||||
var panel = new DockPanel { Margin = new Avalonia.Thickness(8) };
|
||||
DockPanel.SetDock(clear, Dock.Bottom);
|
||||
panel.Children.Add(clear);
|
||||
panel.Children.Add(_log);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Control BuildOptionsPage()
|
||||
{
|
||||
var sp = new StackPanel { Margin = new Avalonia.Thickness(10), Spacing = 8 };
|
||||
foreach (var c in new Control[]
|
||||
{
|
||||
_compressTex, _scaleSpeculars, _useGles, _shaderGamma, _mipmaps,
|
||||
_convertModels, _resourceMove, _resourceSweep, _ignoreIrrelevant
|
||||
})
|
||||
sp.Children.Add(c);
|
||||
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Margin = new Avalonia.Thickness(0, 6, 0, 0) };
|
||||
row.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["TrainPhysicsThreads"],
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
_threads.Width = 220;
|
||||
row.Children.Add(_threads);
|
||||
row.Children.Add(_threadsValue);
|
||||
sp.Children.Add(row);
|
||||
|
||||
return new ScrollViewer { Content = sp, VerticalScrollBarVisibility = ScrollBarVisibility.Auto };
|
||||
}
|
||||
|
||||
private Control BuildToolsPage()
|
||||
{
|
||||
var sp = new StackPanel { Margin = new Avalonia.Thickness(10), Spacing = 8 };
|
||||
|
||||
Button Tool(string key, string tag)
|
||||
{
|
||||
var b = new Button
|
||||
{
|
||||
Content = App.Loc[key],
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
b.Classes.Add("Flat");
|
||||
b.Click += (_, _) => ToolRequested?.Invoke(tag);
|
||||
return b;
|
||||
}
|
||||
|
||||
sp.Children.Add(Tool("AddAllCategory", "category"));
|
||||
sp.Children.Add(Tool("AddUniqueMmd", "mmd"));
|
||||
sp.Children.Add(Tool("RemoveAllTrains", "removeall"));
|
||||
return sp;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
var s = SettingsModel.Instance;
|
||||
_compressTex.IsChecked = s.CompressTextures;
|
||||
_mipmaps.IsChecked = s.ScreenMipmaps;
|
||||
_scaleSpeculars.IsChecked = s.ScaleSpeculars;
|
||||
_shaderGamma.IsChecked = s.ShaderGamma;
|
||||
_useGles.IsChecked = s.UseGLES;
|
||||
_convertModels.IsChecked = s.ExtendedModelConversion;
|
||||
_resourceMove.IsChecked = s.GfxResourceMove;
|
||||
_resourceSweep.IsChecked = s.GfxResourceSweep;
|
||||
_ignoreIrrelevant.IsChecked = s.IgnoreIrrelevantTrains;
|
||||
_threads.Value = s.TrainPhysicsThreads;
|
||||
_threadsValue.Text = s.TrainPhysicsThreads.ToString();
|
||||
}
|
||||
|
||||
private void Store()
|
||||
{
|
||||
var s = SettingsModel.Instance;
|
||||
s.CompressTextures = _compressTex.IsChecked == true;
|
||||
s.ScreenMipmaps = _mipmaps.IsChecked == true;
|
||||
s.ScaleSpeculars = _scaleSpeculars.IsChecked == true;
|
||||
s.ShaderGamma = _shaderGamma.IsChecked == true;
|
||||
s.UseGLES = _useGles.IsChecked == true;
|
||||
s.ExtendedModelConversion = _convertModels.IsChecked == true;
|
||||
s.GfxResourceMove = _resourceMove.IsChecked == true;
|
||||
s.GfxResourceSweep = _resourceSweep.IsChecked == true;
|
||||
s.IgnoreIrrelevantTrains = _ignoreIrrelevant.IsChecked == true;
|
||||
s.TrainPhysicsThreads = (int)_threads.Value;
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,43 @@ public static class ConsistContextMenu
|
||||
Control owner,
|
||||
Func<Trainset?> getTarget,
|
||||
Action<List<Dynamic>> applyVehicles,
|
||||
Action? removeAll = null)
|
||||
Action? removeAll = null,
|
||||
Func<Dynamic?>? getVehicle = null,
|
||||
Action<Dynamic>? removeVehicle = null,
|
||||
Action? randomOrder = null,
|
||||
Action? randomTurn = null)
|
||||
{
|
||||
var menu = new ContextMenu();
|
||||
|
||||
menu.Opening += (_, _) => Populate(menu, owner, getTarget, applyVehicles, removeAll);
|
||||
menu.Opening += (_, _) =>
|
||||
{
|
||||
Populate(menu, owner, getTarget, applyVehicles, removeAll);
|
||||
|
||||
if (randomOrder != null || randomTurn != null)
|
||||
{
|
||||
var schedule = new MenuItem { Header = App.Loc["ConsistSchedule"] };
|
||||
if (randomOrder != null)
|
||||
{
|
||||
var mo = new MenuItem { Header = App.Loc["RandomizeOrder"] };
|
||||
mo.Click += (_, _) => randomOrder();
|
||||
schedule.Items.Add(mo);
|
||||
}
|
||||
if (randomTurn != null)
|
||||
{
|
||||
var mt = new MenuItem { Header = App.Loc["RandomizeOrientation"] };
|
||||
mt.Click += (_, _) => randomTurn();
|
||||
schedule.Items.Add(mt);
|
||||
}
|
||||
menu.Items.Add(schedule);
|
||||
}
|
||||
|
||||
if (removeVehicle == null || getVehicle?.Invoke() is not { } v) return;
|
||||
|
||||
menu.Items.Add(new Separator());
|
||||
var mi = new MenuItem { Header = App.Loc["RemoveVehicle"] };
|
||||
mi.Click += (_, _) => removeVehicle(v);
|
||||
menu.Items.Add(mi);
|
||||
};
|
||||
return menu;
|
||||
}
|
||||
|
||||
@@ -50,6 +82,31 @@ public static class ConsistContextMenu
|
||||
}
|
||||
menu.Items.Add(load);
|
||||
|
||||
var append = new MenuItem { Header = App.Loc["WarehouseAppend"] };
|
||||
if (presets.Count == 0)
|
||||
{
|
||||
append.Items.Add(new MenuItem { Header = App.Loc["PresetEmpty"], IsEnabled = false });
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var preset in presets)
|
||||
{
|
||||
var entry = preset;
|
||||
var mi = new MenuItem { Header = entry.Name };
|
||||
mi.Click += (_, _) =>
|
||||
{
|
||||
var added = ConsistText.VehiclesFrom(entry.Entry);
|
||||
if (added is null || added.Count == 0) return;
|
||||
var merged = target is null
|
||||
? added
|
||||
: new List<Dynamic>(target.Vehicles).Concat(added).ToList();
|
||||
applyVehicles(merged);
|
||||
};
|
||||
append.Items.Add(mi);
|
||||
}
|
||||
}
|
||||
menu.Items.Add(append);
|
||||
|
||||
var sort = new MenuItem { Header = App.Loc["PresetSort"] };
|
||||
var sortName = new MenuItem
|
||||
{
|
||||
@@ -148,7 +205,7 @@ public static class ConsistContextMenu
|
||||
|
||||
var nameBox = new TextBox
|
||||
{
|
||||
Watermark = App.Loc["PresetNamePrompt"],
|
||||
PlaceholderText = App.Loc["PresetNamePrompt"],
|
||||
Text = DefaultName(target),
|
||||
MinWidth = 220
|
||||
};
|
||||
|
||||
@@ -7,162 +7,177 @@
|
||||
x:CompileBindings="True"
|
||||
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
|
||||
x:Class="StarterNG.Views.Depot">
|
||||
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid Margin="0" ColumnSpacing="3" RowSpacing="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="340" />
|
||||
<ColumnDefinition Width="270" MinWidth="220" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<!-- Consists list and the brakes/loads editor share the upper area,
|
||||
the editor taking a bit more of it. -->
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="1.25*" MinHeight="250" />
|
||||
<RowDefinition Height="190" />
|
||||
<RowDefinition Height="*" MinHeight="230" />
|
||||
<RowDefinition Height="275" MinHeight="260" />
|
||||
<RowDefinition Height="175" MinHeight="165" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Vehicle database browser (left, rows 0-1) -->
|
||||
<Border Grid.RowSpan="2" Background="{StaticResource CarbonPanelBg}"
|
||||
<Border Grid.Column="0" Grid.Row="0" Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
CornerRadius="2" Padding="8">
|
||||
<DockPanel LastChildFill="True">
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6" LastChildFill="True">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<DockPanel Grid.Row="0" Margin="0,0,0,6" LastChildFill="True">
|
||||
<CheckBox Name="hideArchivalCheck" DockPanel.Dock="Right" FontSize="11"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"
|
||||
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
||||
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
||||
<TextBox Name="searchBox" VerticalAlignment="Center"
|
||||
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
||||
PlaceholderText="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
||||
TextChanged="SearchBox_OnTextChanged" />
|
||||
</DockPanel>
|
||||
<Grid DockPanel.Dock="Top" RowDefinitions="Auto,Auto" RowSpacing="6" Margin="0,0,0,6">
|
||||
<ComboBox Name="categoryCombo" Grid.Row="0" HorizontalAlignment="Stretch"
|
||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||
PointerWheelChanged="Combo_OnPointerWheel"
|
||||
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,Auto" RowSpacing="6" Margin="0,0,0,6">
|
||||
<DockPanel Grid.Row="0" LastChildFill="True">
|
||||
<Button Name="textureBaseButton" DockPanel.Dock="Right" Classes="Flat"
|
||||
Width="30" Margin="4,0,0,0" Cursor="Hand" Content="🔍"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="TextureBaseButton_OnClick"
|
||||
ToolTip.Tip="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox Name="categoryCombo" HorizontalAlignment="Stretch"
|
||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||
PointerWheelChanged="Combo_OnPointerWheel"
|
||||
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
||||
</DockPanel>
|
||||
<ComboBox Name="classCombo" Grid.Row="1" HorizontalAlignment="Stretch"
|
||||
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
||||
PointerWheelChanged="Combo_OnPointerWheel"
|
||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||
</Grid>
|
||||
|
||||
<!-- The arrow stays enabled with nothing selected - adding a whole
|
||||
category needs no highlighted entry. -->
|
||||
<DockPanel DockPanel.Dock="Bottom" Margin="0,8,0,0">
|
||||
<Button Name="addMoreButton" DockPanel.Dock="Right" Classes="Flat Accent"
|
||||
Width="28" Margin="4,0,0,0" Cursor="Hand" Content="▾"
|
||||
HorizontalContentAlignment="Center"
|
||||
ToolTip.Tip="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="TopEdgeAlignedRight">
|
||||
<MenuItem Click="AddAllCategoryButton_OnClick"
|
||||
Header="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipAddAllCategory], Source={x:Static starterNg:App.Loc}}" />
|
||||
<MenuItem Click="AddUniqueMmdButton_OnClick"
|
||||
Header="{Binding [AddUniqueMmd], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipAddUniqueMmd], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Separator />
|
||||
<MenuItem Click="TextureBaseButton_OnClick"
|
||||
Header="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button Name="addVehicleButton" Classes="Flat Accent"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
IsEnabled="False" Cursor="Hand"
|
||||
Click="AddVehicleButton_OnClick"
|
||||
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Grid Name="browserDetails" DockPanel.Dock="Bottom" ColumnDefinitions="130,*"
|
||||
ColumnSpacing="6" Margin="0,6,0,0" IsVisible="False">
|
||||
<Border Name="miniPreviewPanel" Grid.Column="0" Height="66"
|
||||
VerticalAlignment="Top"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
CornerRadius="2" Cursor="Hand"
|
||||
PointerPressed="MiniPreview_OnPointerPressed">
|
||||
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
||||
</Border>
|
||||
<StackPanel Name="textureInfoPanel" Grid.Column="1" Spacing="1"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Border Name="miniPreviewPanel" Grid.Row="3" Height="66"
|
||||
Margin="0,6,0,0"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
CornerRadius="2" Cursor="Hand"
|
||||
PointerPressed="MiniPreview_OnPointerPressed">
|
||||
<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" MinHeight="120"
|
||||
<Button Name="addVehicleButton" Grid.Row="4" Classes="Flat Accent"
|
||||
Margin="0,8,0,0" Cursor="Hand" IsEnabled="False"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Click="AddVehicleButton_OnClick"
|
||||
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<ListBox Name="vehicleListBox" Grid.Row="2" Classes="Compact"
|
||||
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
||||
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
||||
PointerPressed="VehicleListBox_OnPointerPressed" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Consists present on the scenery selected in the Scenarios tab.
|
||||
Selecting one loads it into the editor below; the header carries the
|
||||
scenery name (see PopulateSceneryConsists). -->
|
||||
<HeaderedContentControl Name="mapConsistsPanel" Grid.Column="1" Grid.Row="0"
|
||||
Theme="{StaticResource GroupBox}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<ListBox Name="sceneryConsistList" Classes="Compact"
|
||||
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
||||
KeyDown="SceneryConsistList_OnKeyDown" />
|
||||
</DockPanel>
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button Name="addToWarehouseButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="AddToWarehouseButton_OnClick"
|
||||
Content="{Binding [AddToWarehouse], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="16"
|
||||
Margin="2,6,0,0">
|
||||
<CheckBox Name="depotDrivableOnlyCheck" FontSize="11"
|
||||
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||
Content="{Binding [ShowDrivableOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox Name="depotShowAiCheck" FontSize="11"
|
||||
IsCheckedChanged="ConsistFilter_OnChanged"
|
||||
Content="{Binding [ShowAiVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<ListBox Name="sceneryConsistList" Classes="Compact"
|
||||
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
||||
KeyDown="SceneryConsistList_OnKeyDown" />
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [WarehouseConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button Name="removeFromWarehouseButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="RemoveFromWarehouseButton_OnClick"
|
||||
Content="{Binding [RemoveFromWarehouse], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ListBox Name="warehouseList" Classes="Compact"
|
||||
DoubleTapped="WarehouseList_OnDoubleTapped" />
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<!-- Brakes / Loads editor (middle-right). Header carries the vehicle
|
||||
name (see UpdateDetails). -->
|
||||
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Column="1" Grid.Row="1"
|
||||
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Theme="{StaticResource GroupBox}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<TabControl>
|
||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||
<Grid ColumnDefinitions="290*,3,380*,3,280*">
|
||||
<TabControl Grid.Column="0">
|
||||
<TabItem Header="{Binding [GeneralInfo], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True" Margin="4">
|
||||
<Button Name="removeVehicleButton" DockPanel.Dock="Bottom" Classes="Flat"
|
||||
Margin="0,6,0,0" HorizontalAlignment="Stretch" Cursor="Hand"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="RemoveVehicleButton_OnClick"
|
||||
Content="{Binding [RemoveVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="generalPanel" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [AboutTexture], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="brakesPanel" Margin="4" Spacing="8" />
|
||||
<StackPanel Name="consistTexturePanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Loads], Source={x:Static starterNg:App.Loc}}">
|
||||
</TabControl>
|
||||
|
||||
<TabControl Grid.Column="2">
|
||||
<TabItem Header="{Binding [Configuration], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="loadsPanel" Margin="4" Spacing="8" />
|
||||
<StackPanel Name="loadsPanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="brakesPanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<TabControl Grid.Column="4">
|
||||
<TabItem Header="{Binding [Coupling], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="couplerPanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Damage], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Name="damagePanel" Margin="4" Spacing="8" />
|
||||
<StackPanel Name="damagePanel" Margin="4" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<!-- Consist (full-width bottom, horizontally scrollable) -->
|
||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<!-- Train totals + randomize tools -->
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="4">
|
||||
<Button Name="randomTexturesButton" Classes="Basic" FontSize="11"
|
||||
Padding="8,2" Cursor="Hand" Click="RandomTexturesButton_OnClick"
|
||||
Content="{Binding [RandomizeTextures], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipRandomizeTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button Name="randomOrderButton" Classes="Basic" FontSize="11"
|
||||
Padding="8,2" Cursor="Hand" Click="RandomOrderButton_OnClick"
|
||||
Content="{Binding [RandomizeOrder], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipRandomizeOrder], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button Name="randomOrientButton" Classes="Basic" FontSize="11"
|
||||
Padding="8,2" Cursor="Hand" Click="RandomOrientButton_OnClick"
|
||||
Content="{Binding [RandomizeOrientation], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipRandomizeOrientation], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<TextBlock Name="trainStats" FontSize="12" Opacity="0.85"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<StackPanel Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
<Panel Name="consistDropTarget">
|
||||
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
||||
Text="{Binding [ConsistEmpty], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel Name="consistStack" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" Spacing="0" />
|
||||
<ScrollViewer Name="consistScroll"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<Panel>
|
||||
<StackPanel Name="consistStack" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" Spacing="0" />
|
||||
<Canvas Name="consistOverlay" IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
</DockPanel>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
262
StarterNG/Views/RulesWindow.cs
Normal file
262
StarterNG/Views/RulesWindow.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Domain;
|
||||
using StarterNG.Infrastructure;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public sealed class RulesWindow : Window
|
||||
{
|
||||
private readonly List<List<string>> _rules = new();
|
||||
private readonly ListBox _ruleList = new() { Classes = { "Compact" } };
|
||||
private readonly WrapPanel _members = new() { Orientation = Orientation.Horizontal };
|
||||
private readonly ComboBox _category = new();
|
||||
private readonly ComboBox _model = new();
|
||||
private readonly VehicleDatabase _db = GameData.Instance.Vehicles;
|
||||
|
||||
public RulesWindow()
|
||||
{
|
||||
Title = App.Loc["RandomizeTexturesRules"];
|
||||
Width = 720;
|
||||
Height = 460;
|
||||
MinWidth = 560;
|
||||
MinHeight = 360;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
|
||||
LoadRules();
|
||||
BuildCategories();
|
||||
|
||||
_ruleList.SelectionChanged += (_, _) => ShowMembers();
|
||||
|
||||
var add = new Button
|
||||
{
|
||||
Content = App.Loc["RuleAdd"],
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
add.Classes.Add("Flat");
|
||||
add.Click += (_, _) => AddToRule();
|
||||
|
||||
_category.SelectionChanged += (_, _) => FillModels();
|
||||
|
||||
var addRow = new Grid
|
||||
{
|
||||
Margin = new Avalonia.Thickness(0, 8, 0, 0),
|
||||
ColumnDefinitions = new ColumnDefinitions("*,6,*,6,Auto")
|
||||
};
|
||||
_category.MinWidth = 0;
|
||||
_model.MinWidth = 0;
|
||||
_category.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
_model.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
Grid.SetColumn(_category, 0);
|
||||
Grid.SetColumn(_model, 2);
|
||||
Grid.SetColumn(add, 4);
|
||||
addRow.Children.Add(_category);
|
||||
addRow.Children.Add(_model);
|
||||
addRow.Children.Add(add);
|
||||
|
||||
var save = new Button
|
||||
{
|
||||
Content = App.Loc["RandomizeTexturesRulesSave"],
|
||||
MinWidth = 110,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
save.Classes.Add("Accent");
|
||||
save.Click += async (_, _) => { if (await SaveRules()) Close(); };
|
||||
|
||||
var cancel = new Button
|
||||
{
|
||||
Content = App.Loc["Cancel"],
|
||||
MinWidth = 90,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
cancel.Classes.Add("Flat");
|
||||
cancel.Click += (_, _) => Close();
|
||||
|
||||
var footer = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Spacing = 8,
|
||||
Margin = new Avalonia.Thickness(0, 10, 0, 0),
|
||||
Children = { cancel, save }
|
||||
};
|
||||
|
||||
var right = new DockPanel { LastChildFill = true };
|
||||
DockPanel.SetDock(addRow, Dock.Bottom);
|
||||
DockPanel.SetDock(footer, Dock.Bottom);
|
||||
right.Children.Add(footer);
|
||||
right.Children.Add(addRow);
|
||||
right.Children.Add(new ScrollViewer
|
||||
{
|
||||
Content = _members,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto
|
||||
});
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
Margin = new Avalonia.Thickness(10),
|
||||
ColumnDefinitions = new ColumnDefinitions("260,10,*")
|
||||
};
|
||||
Grid.SetColumn(_ruleList, 0);
|
||||
Grid.SetColumn(right, 2);
|
||||
grid.Children.Add(_ruleList);
|
||||
grid.Children.Add(right);
|
||||
|
||||
Content = grid;
|
||||
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void LoadRules()
|
||||
{
|
||||
string path = TexRandomizer.RulesPath;
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
foreach (string line in File.ReadAllLines(path))
|
||||
{
|
||||
var members = line.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => x.Length > 0)
|
||||
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
if (members.Count > 0)
|
||||
_rules.Add(members);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnostics.Log($"reguly.txt {path}", ex);
|
||||
}
|
||||
|
||||
_rules.Add(new List<string>());
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> SaveRules()
|
||||
{
|
||||
string path = TexRandomizer.RulesPath;
|
||||
try
|
||||
{
|
||||
string? dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllLines(path,
|
||||
_rules.Where(r => r.Count > 0).Select(r => string.Join(",", r)));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Diagnostics.ReportAsync(path, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshList()
|
||||
{
|
||||
int keep = _ruleList.SelectedIndex;
|
||||
_ruleList.Items.Clear();
|
||||
|
||||
foreach (var rule in _rules)
|
||||
_ruleList.Items.Add(new ListBoxItem
|
||||
{
|
||||
Content = new TextBlock
|
||||
{
|
||||
Text = rule.Count == 0 ? App.Loc["RuleNew"] : string.Join(" ", rule),
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
Opacity = rule.Count == 0 ? 0.6 : 1
|
||||
}
|
||||
});
|
||||
|
||||
_ruleList.SelectedIndex = keep >= 0 && keep < _rules.Count ? keep : 0;
|
||||
ShowMembers();
|
||||
}
|
||||
|
||||
private void ShowMembers()
|
||||
{
|
||||
_members.Children.Clear();
|
||||
int idx = _ruleList.SelectedIndex;
|
||||
if (idx < 0 || idx >= _rules.Count) return;
|
||||
|
||||
foreach (string member in _rules[idx])
|
||||
{
|
||||
string name = member;
|
||||
var btn = new Button
|
||||
{
|
||||
Content = name + " ✕",
|
||||
FontSize = 12,
|
||||
Margin = new Avalonia.Thickness(0, 0, 4, 4),
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
btn.Classes.Add("Flat");
|
||||
ToolTip.SetTip(btn, App.Loc["RuleRemove"]);
|
||||
btn.Click += (_, _) =>
|
||||
{
|
||||
_rules[idx].Remove(name);
|
||||
RefreshList();
|
||||
};
|
||||
_members.Children.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildCategories()
|
||||
{
|
||||
var cats = _db.Textures
|
||||
.Select(t => t.ResolvedCategory)
|
||||
.Where(c => !string.IsNullOrEmpty(c))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(c => c, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
foreach (string? c in cats)
|
||||
_category.Items.Add(new ComboBoxItem { Content = c, Tag = c });
|
||||
|
||||
if (_category.Items.Count > 0)
|
||||
_category.SelectedIndex = 0;
|
||||
FillModels();
|
||||
}
|
||||
|
||||
private void FillModels()
|
||||
{
|
||||
_model.Items.Clear();
|
||||
string? cat = (_category.SelectedItem as ComboBoxItem)?.Tag as string;
|
||||
|
||||
var models = _db.Textures
|
||||
.Where(t => cat == null ||
|
||||
string.Equals(t.ResolvedCategory, cat, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(t => t.Model ?? "")
|
||||
.Where(m => m.Length > 0)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(m => m, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (string m in models)
|
||||
_model.Items.Add(m);
|
||||
|
||||
if (_model.Items.Count > 0)
|
||||
_model.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void AddToRule()
|
||||
{
|
||||
int idx = _ruleList.SelectedIndex;
|
||||
if (idx < 0 || idx >= _rules.Count) return;
|
||||
if (_model.SelectedItem is not string model || model.Length == 0) return;
|
||||
|
||||
if (_rules[idx].Contains(model, StringComparer.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
_rules[idx].Add(model);
|
||||
_rules[idx].Sort(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (idx == _rules.Count - 1)
|
||||
_rules.Add(new List<string>());
|
||||
|
||||
RefreshList();
|
||||
}
|
||||
}
|
||||
@@ -7,103 +7,104 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="StarterNG.Views.Scenarios"
|
||||
x:CompileBindings="True">
|
||||
<Grid Margin="0" ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid Margin="0" ColumnSpacing="0" RowSpacing="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="280" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="280" />
|
||||
<ColumnDefinition Width="*" MinWidth="200" MaxWidth="460" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="3.2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="150" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Scenery tree (left) -->
|
||||
<HeaderedContentControl Grid.Column="0" Grid.Row="0" Theme="{StaticResource GroupBox}"
|
||||
Classes="Compact"
|
||||
Header="{Binding [Sceneries], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<ToggleSwitch DockPanel.Dock="Bottom" Name="archivalSwitch"
|
||||
Margin="2,4,0,0" FontSize="11"
|
||||
IsCheckedChanged="ArchivalSwitch_OnChanged"
|
||||
OnContent="{Binding [ShowArchival], Source={x:Static starterNg:App.Loc}}"
|
||||
OffContent="{Binding [ShowArchival], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8" Margin="2,4,0,0">
|
||||
<Button x:Name="reloadButton" Classes="Flat" FontSize="11" Padding="8,2" Cursor="Hand"
|
||||
Click="ReloadSceneries_OnClick"
|
||||
Content="{Binding [ReloadSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button Classes="Flat" FontSize="11" Padding="8,2" Cursor="Hand"
|
||||
Click="OpenSceneryDir_OnClick"
|
||||
Content="{Binding [OpenSceneryFolder], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<CheckBox DockPanel.Dock="Bottom" Name="archivalSwitch"
|
||||
Margin="2,4,0,0" FontSize="11"
|
||||
IsCheckedChanged="ArchivalSwitch_OnChanged"
|
||||
Content="{Binding [HideArchivalSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TreeView Name="sceneryList" Classes="Compact"
|
||||
SelectionChanged="SceneryList_OnSelectionChanged" />
|
||||
SelectionChanged="SceneryList_OnSelectionChanged">
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Click="ReloadSceneries_OnClick"
|
||||
Header="{Binding [ReloadSceneries], Source={x:Static starterNg:App.Loc}}" />
|
||||
<MenuItem Click="OpenSceneryDir_OnClick"
|
||||
Header="{Binding [OpenSceneryFolder], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ContextMenu>
|
||||
</TreeView.ContextMenu>
|
||||
</TreeView>
|
||||
</DockPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<!-- Centre: trainsets list (compact, fills) + description/weather/timetable tabs -->
|
||||
<Grid Grid.Column="1" Grid.Row="0" RowSpacing="8">
|
||||
<GridSplitter Grid.Column="1" Grid.Row="0" Width="6"
|
||||
Background="Transparent" ResizeDirection="Columns" />
|
||||
|
||||
<Grid Grid.Column="2" Grid.Row="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="3*" />
|
||||
<RowDefinition Height="2*" MinHeight="90" />
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="3*" MinHeight="140" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<HeaderedContentControl Grid.Row="0" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Vehicles], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="16"
|
||||
Margin="2,4,0,0">
|
||||
<CheckBox Name="showAiCheck" FontSize="11"
|
||||
IsCheckedChanged="VehicleFilter_OnChanged"
|
||||
Content="{Binding [ShowAiVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox Name="drivableOnlyCheck" FontSize="11"
|
||||
IsCheckedChanged="VehicleFilter_OnChanged"
|
||||
Content="{Binding [ShowDrivableOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<ListBox Name="vehicleList" Classes="Compact"
|
||||
SelectionChanged="VehicleList_OnSelectionChanged" />
|
||||
</DockPanel>
|
||||
Header="{Binding [SceneryConsists], Source={x:Static starterNg:App.Loc}}">
|
||||
<HeaderedContentControl.Tag>
|
||||
<Button Name="randomTexturesButton" Classes="Flat" Cursor="Hand"
|
||||
FontSize="11" MinHeight="0" Padding="10,1"
|
||||
Click="RandomTexturesButton_OnClick"
|
||||
Content="{Binding [RandomizeTextures], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipRandomizeTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||
</HeaderedContentControl.Tag>
|
||||
|
||||
<ListBox Name="vehicleList" Classes="Compact"
|
||||
SelectionChanged="VehicleList_OnSelectionChanged" />
|
||||
</HeaderedContentControl>
|
||||
|
||||
<TabControl Grid.Row="1">
|
||||
<GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch"
|
||||
Background="Transparent" ResizeDirection="Rows" />
|
||||
|
||||
<TabControl Grid.Row="2">
|
||||
<TabItem Header="{Binding [TabDescription], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock TextWrapping="Wrap" Name="missionDescription" Margin="4" />
|
||||
</ScrollViewer>
|
||||
<Grid RowDefinitions="2*,6,*" Margin="4">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,3,225">
|
||||
<HeaderedContentControl Grid.Column="0" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
|
||||
</ScrollViewer>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<Border Grid.Column="2" ClipToBounds="True"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1">
|
||||
<Image Name="sceneryImage" Stretch="Uniform" Margin="2" />
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock TextWrapping="Wrap" Name="missionDescription" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Weather], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<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>
|
||||
<StackPanel Name="weatherForm" Margin="8,6" Spacing="8" IsEnabled="False">
|
||||
<Grid ColumnDefinitions="Auto,12,380,8,70"
|
||||
RowDefinitions="Auto,6,Auto,14,Auto,Auto,8,Auto,Auto,14,Auto,14,Auto">
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
||||
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
||||
<TimePicker Name="weatherTime" ClockIdentifier="24HourClock"
|
||||
MinuteIncrement="1" VerticalAlignment="Center"
|
||||
SelectedTimeChanged="WeatherTime_OnChanged" />
|
||||
<Button Name="weatherTimeNow" Classes="Flat" Cursor="Hand"
|
||||
VerticalAlignment="Center" Padding="10,4"
|
||||
Click="WeatherTimeNow_OnClick"
|
||||
Content="{Binding [CurrentTime], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button Name="weatherRestore" Classes="Flat" Cursor="Hand"
|
||||
VerticalAlignment="Center" Padding="10,4"
|
||||
Click="WeatherRestore_OnClick"
|
||||
Content="{Binding [RestoreWeather], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
|
||||
Text="{Binding [Season], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" Name="weatherSeason" Width="180"
|
||||
HorizontalAlignment="Left"
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [Date], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Grid Grid.Row="0" Grid.Column="2" ColumnDefinitions="150,10,150,10,*">
|
||||
<DatePicker Grid.Column="0" Name="weatherDate"
|
||||
YearVisible="False" MinWidth="0"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
||||
SelectedDateChanged="WeatherDate_OnChanged" />
|
||||
<ComboBox Grid.Column="2" Name="weatherSeason"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
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}}" />
|
||||
@@ -111,103 +112,121 @@
|
||||
<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="-50" Maximum="50" Width="220"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||
ValueChanged="Weather_OnSliderChanged" />
|
||||
<TextBlock Name="weatherTempLabel" VerticalAlignment="Center" MinWidth="48" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
||||
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
||||
<Slider Name="weatherFog" Minimum="0" Maximum="10000" Width="220"
|
||||
TickFrequency="250" IsSnapToTickEnabled="True"
|
||||
ValueChanged="Weather_OnSliderChanged" />
|
||||
<TextBlock Name="weatherFogLabel" VerticalAlignment="Center" MinWidth="56" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" VerticalAlignment="Center"
|
||||
Text="{Binding [Overcast], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox Grid.Row="5" Grid.Column="1" Name="weatherOvercast" Width="220"
|
||||
HorizontalAlignment="Left"
|
||||
SelectionChanged="Weather_OnSelectionChanged">
|
||||
<ComboBoxItem Tag="-1.5" Content="{Binding [OvercastClear], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0" Content="{Binding [OvercastFair], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0.3" Content="{Binding [OvercastLight], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0.7" Content="{Binding [OvercastModerate], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1" Content="{Binding [OvercastHeavy], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1.1" Content="{Binding [OvercastCloudy], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1.5" Content="{Binding [OvercastStorm], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Column="4" Name="weatherDayDate"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Name="weatherHint" Opacity="0.6" FontSize="11" TextWrapping="Wrap"
|
||||
Text="{Binding [WeatherHint], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Grid Grid.Row="2" Grid.Column="2" ColumnDefinitions="150,8,*">
|
||||
<TimePicker Grid.Column="0" Name="weatherTime"
|
||||
ClockIdentifier="24HourClock" MinuteIncrement="1"
|
||||
MinWidth="0" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedTimeChanged="WeatherTime_OnChanged" />
|
||||
<Button Grid.Column="2" Name="weatherTimeNow"
|
||||
Classes="Flat" Cursor="Hand" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Click="WeatherTimeNow_OnClick"
|
||||
Content="{Binding [CurrentTime], Source={x:Static starterNg:App.Loc}}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [Temperature], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Grid.Row="4" Grid.Column="2" Name="weatherTemp"
|
||||
Minimum="-50" Maximum="50"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||
ValueChanged="Weather_OnSliderChanged" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="4" Name="weatherTempLabel"
|
||||
VerticalAlignment="Center" TextAlignment="Right"
|
||||
FontWeight="SemiBold" />
|
||||
<DockPanel Grid.Row="5" Grid.Column="2" Margin="0,-6,0,0">
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||
Foreground="{StaticResource CarbonFgDim}" Text="+50 °C" />
|
||||
<TextBlock FontSize="11"
|
||||
Foreground="{StaticResource CarbonFgDim}" Text="-50 °C" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Grid.Row="7" Grid.Column="2" Name="weatherFog"
|
||||
Minimum="0" Maximum="1000"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||
ValueChanged="Weather_OnSliderChanged" />
|
||||
<TextBlock Grid.Row="7" Grid.Column="4" Name="weatherFogLabel"
|
||||
VerticalAlignment="Center" TextAlignment="Right"
|
||||
FontWeight="SemiBold" />
|
||||
<DockPanel Grid.Row="8" Grid.Column="2" Margin="0,-6,0,0">
|
||||
<TextBlock DockPanel.Dock="Right" FontSize="11"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [VisibilityGood], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBlock FontSize="11" Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [VisibilityBad], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource CarbonFgDim}"
|
||||
Text="{Binding [Overcast], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox Grid.Row="10" Grid.Column="2" Name="weatherOvercast"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectionChanged="Weather_OnSelectionChanged">
|
||||
<ComboBoxItem Tag="random" Content="{Binding [OvercastRandom], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="-1.5" Content="{Binding [OvercastClear], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0" Content="{Binding [OvercastFair], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0.3" Content="{Binding [OvercastLight], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="0.7" Content="{Binding [OvercastModerate], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1" Content="{Binding [OvercastHeavy], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1.1" Content="{Binding [OvercastCloudy], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1.5" Content="{Binding [OvercastStorm], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
|
||||
<Border Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="5"
|
||||
Height="1" VerticalAlignment="Center"
|
||||
Background="{StaticResource CarbonBorderLight}" />
|
||||
|
||||
<Button Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3"
|
||||
Name="weatherRestore" Classes="Flat" Cursor="Hand"
|
||||
HorizontalAlignment="Right" MinWidth="180"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="WeatherRestore_OnClick"
|
||||
Content="{Binding [RestoreWeather], Source={x:Static starterNg:App.Loc}}" />
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Timetable], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||
<TextBlock Name="timetableContent" Margin="4"
|
||||
FontFamily="{x:Static infra:MonoFont.Family}"
|
||||
FontSize="12" TextWrapping="NoWrap" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Name="infoTab" Header="{Binding [TabInfo], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Name="faultListBox" Margin="4" IsReadOnly="True"
|
||||
AcceptsReturn="True" TextWrapping="Wrap"
|
||||
FontFamily="{x:Static infra:MonoFont.Family}" FontSize="12" />
|
||||
</ScrollViewer>
|
||||
<TabItem Name="timetableTab" ToolTip.ShowOnDisabled="True"
|
||||
Header="{Binding [Timetable], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True" Margin="4">
|
||||
<ScrollViewer DockPanel.Dock="Bottom" HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled" MaxHeight="72">
|
||||
<StackPanel Name="attachmentsPanel" Orientation="Horizontal" Spacing="6" />
|
||||
</ScrollViewer>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||
<TextBlock Name="timetableContent"
|
||||
FontFamily="{x:Static infra:MonoFont.Family}"
|
||||
FontSize="12" TextWrapping="NoWrap" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Right: scenery image (//$i) + scenery description (//$d) -->
|
||||
<Grid Grid.Column="2" Grid.Row="0" RowSpacing="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" ClipToBounds="True" VerticalAlignment="Top"
|
||||
Background="{StaticResource CarbonPanelBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
Height="{Binding $self.Bounds.Width}">
|
||||
<Image Name="sceneryImage" Stretch="Uniform" Margin="2" />
|
||||
</Border>
|
||||
|
||||
<HeaderedContentControl Grid.Row="1" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<StackPanel Name="attachmentsPanel" DockPanel.Dock="Bottom" Spacing="2" Margin="0,6,0,0" />
|
||||
<StackPanel Name="includesPanel" DockPanel.Dock="Bottom" Spacing="2" Margin="0,6,0,0" />
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</HeaderedContentControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Consist preview (full-width bottom, horizontally scrollable) -->
|
||||
<HeaderedContentControl Grid.Row="1" Grid.ColumnSpan="3" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<TextBlock Name="trainStats" DockPanel.Dock="Top" FontSize="12"
|
||||
Opacity="0.85" Margin="0,0,0,6" TextTrimming="CharacterEllipsis" />
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Name="consistStack" />
|
||||
<StackPanel DockPanel.Dock="Top" Name="trainStats" Orientation="Horizontal"
|
||||
VerticalAlignment="Center" Margin="0,0,0,6" />
|
||||
<ScrollViewer Name="consistScroll"
|
||||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<Panel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||
Name="consistStack" />
|
||||
<Canvas Name="consistOverlay" IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
@@ -8,7 +8,9 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Markup.Xaml;
|
||||
@@ -37,9 +39,7 @@ public partial class Scenarios : UserControl
|
||||
Sceneries = GameData.Instance.Sceneries;
|
||||
|
||||
_loadingFilters = true;
|
||||
showAiCheck.IsChecked = StarterNG.Classes.Settings.Instance.ShowAiVehicles;
|
||||
drivableOnlyCheck.IsChecked = StarterNG.Classes.Settings.Instance.DrivableOnly;
|
||||
archivalSwitch.IsChecked = StarterNG.Classes.Settings.Instance.ShowArchivalSceneries;
|
||||
archivalSwitch.IsChecked = !StarterNG.Classes.Settings.Instance.ShowArchivalSceneries;
|
||||
_loadingFilters = false;
|
||||
|
||||
BuildSceneryTree();
|
||||
@@ -63,7 +63,7 @@ public partial class Scenarios : UserControl
|
||||
{
|
||||
sceneryList.Items.Clear();
|
||||
|
||||
bool showArchival = archivalSwitch.IsChecked ?? true;
|
||||
bool showArchival = archivalSwitch.IsChecked != true;
|
||||
var groupNodes = new Dictionary<string, TreeViewItem>();
|
||||
var topLevel = new List<TreeViewItem>();
|
||||
|
||||
@@ -213,15 +213,15 @@ public partial class Scenarios : UserControl
|
||||
{
|
||||
vehicleList.Items.Clear();
|
||||
|
||||
bool showAi = showAiCheck.IsChecked ?? true;
|
||||
bool drivableOnly = drivableOnlyCheck.IsChecked ?? true;
|
||||
bool showAi = StarterNG.Classes.Settings.Instance.ShowAiVehicles;
|
||||
bool drivableOnly = StarterNG.Classes.Settings.Instance.DrivableOnly;
|
||||
|
||||
for (int i = 0; i < scn.Trainsets.Count; i++)
|
||||
{
|
||||
var trainset = scn.Trainsets[i];
|
||||
|
||||
if (IsAiTrainset(trainset) && !showAi) continue;
|
||||
if (trainset.Decor && drivableOnly) continue;
|
||||
if (drivableOnly && !UData.StaffedTrain(trainset)) continue;
|
||||
|
||||
var listItem = new ListBoxItem
|
||||
{
|
||||
@@ -244,23 +244,11 @@ public partial class Scenarios : UserControl
|
||||
!string.IsNullOrEmpty(trainset.Description) &&
|
||||
trainset.Description.TrimStart().StartsWith("-", StringComparison.Ordinal);
|
||||
|
||||
private void VehicleFilter_OnChanged(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_loadingFilters) return;
|
||||
|
||||
StarterNG.Classes.Settings.Instance.ShowAiVehicles = showAiCheck.IsChecked ?? true;
|
||||
StarterNG.Classes.Settings.Instance.DrivableOnly = drivableOnlyCheck.IsChecked ?? true;
|
||||
StarterNG.Classes.Settings.Instance.Save();
|
||||
|
||||
if (sceneryList.SelectedItem is TreeViewItem { Tag: int tag } && tag < Sceneries.Count)
|
||||
PopulateVehicleList(Sceneries[tag]);
|
||||
}
|
||||
|
||||
private void ArchivalSwitch_OnChanged(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_loadingFilters) return;
|
||||
|
||||
StarterNG.Classes.Settings.Instance.ShowArchivalSceneries = archivalSwitch.IsChecked ?? true;
|
||||
StarterNG.Classes.Settings.Instance.ShowArchivalSceneries = archivalSwitch.IsChecked != true;
|
||||
StarterNG.Classes.Settings.Instance.Save();
|
||||
|
||||
BuildSceneryTree();
|
||||
@@ -329,9 +317,10 @@ public partial class Scenarios : UserControl
|
||||
{
|
||||
sceneryImage.Source = new Bitmap(imagePath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
sceneryImage.Source = null;
|
||||
StarterNG.Infrastructure.Diagnostics.Log($"Scenery image {imagePath}", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -340,7 +329,6 @@ public partial class Scenarios : UserControl
|
||||
}
|
||||
|
||||
PopulateAttachments(scenery);
|
||||
PopulateIncludes(scenery);
|
||||
PopulateFaults(scenery);
|
||||
}
|
||||
|
||||
@@ -368,8 +356,8 @@ public partial class Scenarios : UserControl
|
||||
lines.Add($"# unknown loose texture: {v.Name} ({v.SkinFile})");
|
||||
}
|
||||
|
||||
faultListBox.Text = string.Join(Environment.NewLine, lines);
|
||||
infoTab.IsVisible = lines.Count > 0 || scenery.Includes.Any(i => i.Kind != 2);
|
||||
if (lines.Count > 0)
|
||||
StarterNG.Infrastructure.Diagnostics.Log(lines);
|
||||
}
|
||||
|
||||
private void PopulateAttachments(Scenery scenery)
|
||||
@@ -392,38 +380,6 @@ public partial class Scenarios : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateIncludes(Scenery scenery)
|
||||
{
|
||||
includesPanel.Children.Clear();
|
||||
var opts = scenery.Includes.Where(i => i.Kind != 2).ToList();
|
||||
if (opts.Count == 0) return;
|
||||
|
||||
includesPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["OptionalIncludes"],
|
||||
FontWeight = Avalonia.Media.FontWeight.Bold,
|
||||
FontSize = 12,
|
||||
Margin = new Thickness(0, 0, 0, 2)
|
||||
});
|
||||
|
||||
foreach (var inc in opts)
|
||||
{
|
||||
var check = new CheckBox
|
||||
{
|
||||
Content = $"{inc.Desc} ({inc.FilePath})",
|
||||
IsChecked = inc.Selected,
|
||||
FontSize = 11,
|
||||
Tag = inc
|
||||
};
|
||||
check.IsCheckedChanged += (_, _) =>
|
||||
{
|
||||
if (check.Tag is SceneryInclude s)
|
||||
s.Selected = check.IsChecked == true;
|
||||
};
|
||||
includesPanel.Children.Add(check);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenAttachment(string relativePath)
|
||||
{
|
||||
string full = Path.GetFullPath(relativePath);
|
||||
@@ -453,9 +409,12 @@ public partial class Scenarios : UserControl
|
||||
RefreshSelectedConsist();
|
||||
}
|
||||
|
||||
private bool _reloading;
|
||||
|
||||
private async void ReloadSceneries_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
reloadButton.IsEnabled = false;
|
||||
if (_reloading) return;
|
||||
_reloading = true;
|
||||
try
|
||||
{
|
||||
string? selected = AppState.Instance.CurrentScenery?.Path;
|
||||
@@ -471,22 +430,94 @@ public partial class Scenarios : UserControl
|
||||
}
|
||||
finally
|
||||
{
|
||||
reloadButton.IsEnabled = true;
|
||||
_reloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<Scenery>? RandomizeTexturesRequested;
|
||||
|
||||
private void RandomTexturesButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppState.Instance.CurrentScenery is { } scenery)
|
||||
RandomizeTexturesRequested?.Invoke(scenery);
|
||||
}
|
||||
|
||||
public void RefreshConsistView()
|
||||
{
|
||||
RefreshVehicleLabels();
|
||||
if (AppState.Instance.CurrentTrainset is { } trainset)
|
||||
ShowConsist(trainset);
|
||||
}
|
||||
|
||||
private static readonly Avalonia.Media.IBrush SelectedVehicleBrush =
|
||||
new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#41C400"));
|
||||
|
||||
private static readonly Avalonia.Media.IBrush MissingMiniBrush =
|
||||
new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#22808080"));
|
||||
|
||||
private static Control MissingMiniBox(string? label, int height) => new Border
|
||||
{
|
||||
Height = height,
|
||||
Width = height * 2,
|
||||
Background = MissingMiniBrush,
|
||||
CornerRadius = new Avalonia.CornerRadius(4),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 10,
|
||||
TextWrapping = Avalonia.Media.TextWrapping.Wrap,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = Avalonia.Media.TextAlignment.Center
|
||||
}
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, Bitmap?> _miniCache = new();
|
||||
|
||||
private Bitmap? LoadMini(string? name, int height)
|
||||
{
|
||||
string key = $"{name}@{height}";
|
||||
if (_miniCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
Bitmap? bmp = null;
|
||||
string? path = VehicleDatabase.MiniPath(name);
|
||||
if (path != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(path);
|
||||
bmp = Bitmap.DecodeToHeight(fs, height);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StarterNG.Infrastructure.Diagnostics.Log($"Mini {path}", ex);
|
||||
}
|
||||
}
|
||||
_miniCache[key] = bmp;
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private void OpenSceneryDir_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var scn = AppState.Instance.CurrentScenery;
|
||||
string dir = scn != null
|
||||
? (Path.GetDirectoryName(Path.GetFullPath(scn.Path)) ?? "scenery")
|
||||
: Path.GetFullPath("scenery");
|
||||
if (!Directory.Exists(dir)) return;
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(string.Format(App.Loc["FaultFileNotFound"], dir));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||
$"{dir}{Environment.NewLine}{Environment.NewLine}{App.Loc["FaultDetail"]} {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
@@ -518,11 +549,13 @@ public partial class Scenarios : UserControl
|
||||
{
|
||||
weatherForm.IsEnabled = true;
|
||||
weatherTime.SelectedTime = ParseTime(scenery.ScenarioTimeOverride);
|
||||
weatherDay.Value = scenery.Day;
|
||||
weatherDate.SelectedDate = DateOfDayOfYear(scenery.Day);
|
||||
weatherTemp.Value = scenery.Temperature;
|
||||
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 0, 10000);
|
||||
SelectByTag(weatherSeason, scenery.Day.ToString());
|
||||
SelectByTag(weatherOvercast, scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
||||
weatherFog.Value = SliderFromFog(scenery.FogEnd);
|
||||
SelectByTag(weatherSeason, scenery.Day.ToString(), excludeFromNearest: "0");
|
||||
SelectByTag(weatherOvercast, scenery.OvercastRandom
|
||||
? RandomTag
|
||||
: scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
ApplyTodayPreview(scenery.Day == 0);
|
||||
UpdateWeatherLabels();
|
||||
@@ -542,12 +575,16 @@ public partial class Scenarios : UserControl
|
||||
if (weatherTime.SelectedTime is { } t)
|
||||
scn.ScenarioTimeOverride = $"{t.Hours:D2}:{t.Minutes:D2}";
|
||||
|
||||
scn.Day = _todaySeason ? 0 : (int)(weatherDay.Value ?? 0);
|
||||
scn.Day = _todaySeason ? 0 : SelectedDayOfYear();
|
||||
scn.Temperature = weatherTemp.Value;
|
||||
scn.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.FogEnd = FogFromSlider(weatherFog.Value);
|
||||
if ((weatherOvercast.SelectedItem as ComboBoxItem)?.Tag is string ov)
|
||||
{
|
||||
scn.OvercastRandom = ov == RandomTag;
|
||||
if (!scn.OvercastRandom &&
|
||||
double.TryParse(ov, NumberStyles.Float, CultureInfo.InvariantCulture, out double oc))
|
||||
scn.Overcast = oc;
|
||||
}
|
||||
|
||||
scn.WeatherDirty = true;
|
||||
UpdateWeatherLabels();
|
||||
@@ -556,18 +593,85 @@ public partial class Scenarios : UserControl
|
||||
private void UpdateWeatherLabels()
|
||||
{
|
||||
weatherTempLabel.Text = $"{(int)weatherTemp.Value} °C";
|
||||
weatherFogLabel.Text = $"{(int)weatherFog.Value} m";
|
||||
weatherFogLabel.Text = FormatFog(FogFromSlider(weatherFog.Value));
|
||||
weatherDayDate.Text = string.Format(LanguageCulture, App.Loc["DayOfYear"], SelectedDayOfYear());
|
||||
|
||||
weatherRestore.IsEnabled = _currentScenery?.WeatherChanged ?? false;
|
||||
}
|
||||
|
||||
private static void SelectByTag(ComboBox combo, string tag)
|
||||
private static int FogFromSlider(double position)
|
||||
{
|
||||
double metres = Scenery.FogMin *
|
||||
System.Math.Pow((double)Scenery.FogMax / Scenery.FogMin, position / 1000.0);
|
||||
int step = metres < 100 ? 5 : metres < 1000 ? 10 : 100;
|
||||
return System.Math.Clamp((int)(System.Math.Round(metres / step) * step),
|
||||
Scenery.FogMin, Scenery.FogMax);
|
||||
}
|
||||
|
||||
private static double SliderFromFog(int metres)
|
||||
{
|
||||
double clamped = System.Math.Clamp(metres, Scenery.FogMin, Scenery.FogMax);
|
||||
return 1000.0 * System.Math.Log(clamped / Scenery.FogMin) /
|
||||
System.Math.Log((double)Scenery.FogMax / Scenery.FogMin);
|
||||
}
|
||||
|
||||
private static string FormatFog(int metres) =>
|
||||
metres < 1000
|
||||
? $"{metres} m"
|
||||
: string.Format(LanguageCulture, "{0:0.#} km", metres / 1000.0);
|
||||
|
||||
private static CultureInfo LanguageCulture
|
||||
{
|
||||
get
|
||||
{
|
||||
string code = App.Loc.CurrentLangCode;
|
||||
if (code is "en" or "")
|
||||
return CultureInfo.InvariantCulture;
|
||||
try
|
||||
{
|
||||
return CultureInfo.GetCultureInfo(code == "cz" ? "cs" : code);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
return CultureInfo.InvariantCulture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string RandomTag = "random";
|
||||
|
||||
private static void SelectByTag(ComboBox combo, string tag, string? excludeFromNearest = null)
|
||||
{
|
||||
ComboBoxItem? first = null;
|
||||
ComboBoxItem? nearest = null;
|
||||
double bestDistance = double.MaxValue;
|
||||
bool haveValue = double.TryParse(tag, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||
out double value);
|
||||
|
||||
foreach (var obj in combo.Items)
|
||||
if (obj is ComboBoxItem item && (item.Tag as string) == tag)
|
||||
{
|
||||
if (obj is not ComboBoxItem item)
|
||||
continue;
|
||||
first ??= item;
|
||||
if ((item.Tag as string) == tag)
|
||||
{
|
||||
combo.SelectedItem = item;
|
||||
return;
|
||||
}
|
||||
combo.SelectedItem = null;
|
||||
if (haveValue && item.Tag is string itemTag && itemTag != excludeFromNearest &&
|
||||
double.TryParse(itemTag, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||
out double itemValue))
|
||||
{
|
||||
double distance = System.Math.Abs(itemValue - value);
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestDistance = distance;
|
||||
nearest = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combo.SelectedItem = nearest ?? first;
|
||||
}
|
||||
|
||||
private void WeatherTime_OnChanged(object? sender, TimePickerSelectedValueChangedEventArgs e) => CaptureWeather();
|
||||
@@ -600,12 +704,11 @@ public partial class Scenarios : UserControl
|
||||
private void ApplyTodayPreview(bool today)
|
||||
{
|
||||
_todaySeason = today;
|
||||
weatherDay.IsEnabled = !today;
|
||||
if (today)
|
||||
{
|
||||
bool prev = _loadingWeather;
|
||||
_loadingWeather = true;
|
||||
weatherDay.Value = DateTime.Now.DayOfYear;
|
||||
weatherDate.SelectedDate = DateOfDayOfYear(0);
|
||||
_loadingWeather = prev;
|
||||
}
|
||||
}
|
||||
@@ -614,7 +717,28 @@ public partial class Scenarios : UserControl
|
||||
|
||||
private void Weather_OnSliderChanged(object? sender, Avalonia.Controls.Primitives.RangeBaseValueChangedEventArgs e) => CaptureWeather();
|
||||
|
||||
private void WeatherDay_OnChanged(object? sender, NumericUpDownValueChangedEventArgs e) => CaptureWeather();
|
||||
private void WeatherDate_OnChanged(object? sender, DatePickerSelectedValueChangedEventArgs e)
|
||||
{
|
||||
if (!_loadingWeather)
|
||||
{
|
||||
_todaySeason = false;
|
||||
_loadingWeather = true;
|
||||
SelectByTag(weatherSeason, SelectedDayOfYear().ToString(), excludeFromNearest: "0");
|
||||
_loadingWeather = false;
|
||||
}
|
||||
CaptureWeather();
|
||||
}
|
||||
|
||||
private static DateTimeOffset DateOfDayOfYear(int day)
|
||||
{
|
||||
int year = DateTime.Now.Year;
|
||||
int last = DateTime.IsLeapYear(year) ? 366 : 365;
|
||||
int d = System.Math.Clamp(day <= 0 ? DateTime.Now.DayOfYear : day, 1, last);
|
||||
return new DateTimeOffset(new DateTime(year, 1, 1).AddDays(d - 1));
|
||||
}
|
||||
|
||||
private int SelectedDayOfYear() =>
|
||||
weatherDate.SelectedDate is { } date ? date.DayOfYear : DateTime.Now.DayOfYear;
|
||||
|
||||
private void WeatherSeason_OnChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
@@ -631,7 +755,7 @@ public partial class Scenarios : UserControl
|
||||
{
|
||||
ApplyTodayPreview(false);
|
||||
_loadingWeather = true;
|
||||
weatherDay.Value = day;
|
||||
weatherDate.SelectedDate = DateOfDayOfYear(day);
|
||||
_loadingWeather = false;
|
||||
}
|
||||
}
|
||||
@@ -644,19 +768,33 @@ public partial class Scenarios : UserControl
|
||||
if (path is null)
|
||||
{
|
||||
timetableContent.Text = App.Loc["NoTimetable"];
|
||||
UpdateTimetableTab(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
timetableContent.Text = File.ReadAllText(path, Encoding.GetEncoding(1250));
|
||||
UpdateTimetableTab(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
timetableContent.Text = App.Loc["NoTimetable"];
|
||||
UpdateTimetableTab(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimetableTab(bool hasTimetable)
|
||||
{
|
||||
bool usable = hasTimetable || attachmentsPanel.Children.Count > 0;
|
||||
|
||||
timetableTab.IsEnabled = usable;
|
||||
ToolTip.SetTip(timetableTab, usable ? null : App.Loc["NoTimetable"]);
|
||||
|
||||
if (!usable && timetableTab.IsSelected && timetableTab.Parent is TabControl tabs)
|
||||
tabs.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private static string? ResolveTimetablePath(Scenery scenery, string? name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name) ||
|
||||
@@ -677,27 +815,127 @@ public partial class Scenarios : UserControl
|
||||
return candidates.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private const double ConsistDragThreshold = 6;
|
||||
|
||||
private Trainset? _stripTrainset;
|
||||
private int _pressIndex = -1;
|
||||
private Point _pressPoint;
|
||||
private bool _dragging;
|
||||
private bool _stripWired;
|
||||
|
||||
private int _dragBlockStart = -1;
|
||||
private int _dragBlockLength;
|
||||
private double _dragBlockWidth = 32;
|
||||
private double _grabOffset;
|
||||
private Control? _carried;
|
||||
|
||||
private readonly List<int> _reducedToVehicle = new();
|
||||
private readonly List<int> _unitStarts = new();
|
||||
|
||||
private readonly Infrastructure.DropGap _dropGap = new();
|
||||
private readonly Infrastructure.DropIndexTracker _dropTracker = new();
|
||||
private readonly Infrastructure.EdgeScroller _edgeScroller = new();
|
||||
|
||||
private void ShowConsist(Trainset trainset)
|
||||
{
|
||||
consistStack.Children.Clear();
|
||||
_stripTrainset = trainset;
|
||||
_dropGap.Forget();
|
||||
|
||||
if (!_stripWired)
|
||||
{
|
||||
_stripWired = true;
|
||||
consistStack.PointerMoved += Strip_OnPointerMoved;
|
||||
consistStack.PointerReleased += Strip_OnPointerReleased;
|
||||
consistStack.PointerCaptureLost += (_, _) => CancelConsistDrag();
|
||||
}
|
||||
|
||||
ContextMenu VehicleMenu(Dynamic vehicle) => ConsistContextMenu.Build(
|
||||
consistStack,
|
||||
() => trainset,
|
||||
vehicles =>
|
||||
{
|
||||
trainset.Vehicles = vehicles;
|
||||
RefreshVehicleLabels();
|
||||
ShowConsist(trainset);
|
||||
UpdateTrainStats(trainset);
|
||||
},
|
||||
removeAll: null,
|
||||
getVehicle: () => vehicle,
|
||||
removeVehicle: v =>
|
||||
{
|
||||
int gap = trainset.Vehicles.IndexOf(v);
|
||||
trainset.Vehicles.Remove(v);
|
||||
|
||||
bool wasPicked = !string.IsNullOrEmpty(v.Name) &&
|
||||
string.Equals(v.Name, AppState.Instance.StartingVehicleName,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
if (wasPicked && trainset.Vehicles.Count > 0)
|
||||
{
|
||||
int next = Math.Clamp(gap, 0, trainset.Vehicles.Count - 1);
|
||||
AppState.Instance.StartingVehicleName = trainset.Vehicles[next].Name;
|
||||
AppState.Instance.NotifyChanged();
|
||||
}
|
||||
|
||||
RefreshVehicleLabels();
|
||||
ShowConsist(trainset);
|
||||
UpdateTrainStats(trainset);
|
||||
});
|
||||
|
||||
var db = GameData.Instance.Vehicles;
|
||||
foreach (var train in trainset.Vehicles)
|
||||
{
|
||||
|
||||
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
||||
string? path = VehicleDatabase.MiniPath(miniName);
|
||||
if (path is null)
|
||||
continue;
|
||||
|
||||
if (!VehicleDatabase.HasMini(miniName) && VehicleDatabase.HasMini(train.SkinFile))
|
||||
miniName = train.SkinFile;
|
||||
int thumbH = StarterNG.Classes.Settings.Instance.LargeThumbnails ? 64 : 32;
|
||||
consistStack.Children.Add(new Image
|
||||
|
||||
var bmp = LoadMini(miniName, thumbH);
|
||||
Control visual = bmp is not null
|
||||
? new Image
|
||||
{
|
||||
Source = bmp,
|
||||
Height = thumbH,
|
||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
||||
Margin = new Thickness(0)
|
||||
}
|
||||
: MissingMiniBox(train.SkinFile, thumbH);
|
||||
|
||||
bool selected = !string.IsNullOrEmpty(train.Name) &&
|
||||
string.Equals(train.Name, AppState.Instance.StartingVehicleName,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var slot = new Border
|
||||
{
|
||||
Source = new Bitmap(path),
|
||||
Height = thumbH,
|
||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
||||
Margin = new Thickness(0)
|
||||
});
|
||||
Transitions = new Transitions
|
||||
{
|
||||
new DoubleTransition
|
||||
{
|
||||
Property = OpacityProperty,
|
||||
Duration = TimeSpan.FromMilliseconds(120)
|
||||
}
|
||||
},
|
||||
Child = visual,
|
||||
Padding = new Thickness(0),
|
||||
BorderThickness = new Thickness(0, 2),
|
||||
BorderBrush = selected ? SelectedVehicleBrush : Avalonia.Media.Brushes.Transparent,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand),
|
||||
ContextMenu = VehicleMenu(train)
|
||||
};
|
||||
ToolTip.SetTip(slot, train.Name);
|
||||
|
||||
int slotIndex = consistStack.Children.Count;
|
||||
|
||||
slot.PointerPressed += (_, ev) =>
|
||||
{
|
||||
if (!ev.GetCurrentPoint(slot).Properties.IsLeftButtonPressed) return;
|
||||
_pressIndex = slotIndex;
|
||||
_pressPoint = ev.GetPosition(consistStack);
|
||||
_dragging = false;
|
||||
};
|
||||
|
||||
consistStack.Children.Add(slot);
|
||||
}
|
||||
string desc = trainset.Description ?? "";
|
||||
if (desc.TrimStart().StartsWith('-'))
|
||||
@@ -706,22 +944,256 @@ public partial class Scenarios : UserControl
|
||||
UpdateTrainStats(trainset);
|
||||
}
|
||||
|
||||
private void Strip_OnPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
var point = e.GetPosition(consistStack);
|
||||
|
||||
if (!_dragging)
|
||||
{
|
||||
if (_pressIndex < 0 || !e.GetCurrentPoint(consistStack).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
var delta = point - _pressPoint;
|
||||
if (Math.Abs(delta.X) < ConsistDragThreshold && Math.Abs(delta.Y) < ConsistDragThreshold)
|
||||
return;
|
||||
|
||||
BeginConsistDrag(e);
|
||||
if (!_dragging) return;
|
||||
}
|
||||
|
||||
Canvas.SetLeft(_carried!, point.X - _grabOffset);
|
||||
|
||||
double pointerX = e.GetPosition(consistScroll).X;
|
||||
if (_dropTracker.Update(pointerX + consistScroll.Offset.X))
|
||||
{
|
||||
int reduced = ReducedTarget();
|
||||
_dropGap.Show(consistStack,
|
||||
reduced < _reducedToVehicle.Count
|
||||
? _reducedToVehicle[reduced]
|
||||
: consistStack.Children.Count,
|
||||
_dragBlockWidth);
|
||||
}
|
||||
|
||||
_edgeScroller.Update(consistScroll, pointerX);
|
||||
}
|
||||
|
||||
private void Strip_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (!_dragging)
|
||||
{
|
||||
int index = _pressIndex;
|
||||
_pressIndex = -1;
|
||||
|
||||
if (e.InitialPressMouseButton != MouseButton.Left ||
|
||||
index < 0 || _stripTrainset is not { } clicked ||
|
||||
index >= clicked.Vehicles.Count)
|
||||
return;
|
||||
|
||||
AppState.Instance.StartingVehicleName = clicked.Vehicles[index].Name;
|
||||
AppState.Instance.NotifyChanged();
|
||||
ShowConsist(clicked);
|
||||
return;
|
||||
}
|
||||
|
||||
int target = _dropTracker.Index >= 0 ? ReducedTarget() : -1;
|
||||
int start = _dragBlockStart;
|
||||
int length = _dragBlockLength;
|
||||
var trainset = _stripTrainset;
|
||||
|
||||
EndConsistDrag();
|
||||
|
||||
if (trainset != null && start >= 0 && target >= 0)
|
||||
MoveConsistBlock(trainset, start, length, target);
|
||||
}
|
||||
|
||||
private void BeginConsistDrag(PointerEventArgs e)
|
||||
{
|
||||
if (_stripTrainset is not { } trainset || _pressIndex < 0 ||
|
||||
_pressIndex >= trainset.Vehicles.Count)
|
||||
return;
|
||||
|
||||
_dragBlockStart = SetStart(trainset.Vehicles, _pressIndex);
|
||||
_dragBlockLength = SetLength(trainset.Vehicles, _dragBlockStart);
|
||||
if (_dragBlockStart + _dragBlockLength > consistStack.Children.Count)
|
||||
return;
|
||||
|
||||
var first = consistStack.Children[_dragBlockStart];
|
||||
var blockBounds = first.Bounds;
|
||||
_dragBlockWidth = 0;
|
||||
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||
_dragBlockWidth += consistStack.Children[i].Bounds.Width;
|
||||
_dragBlockWidth = Math.Max(_dragBlockWidth, 16);
|
||||
_grabOffset = Math.Clamp(_pressPoint.X - blockBounds.Left, 0, _dragBlockWidth);
|
||||
|
||||
CaptureDropTargets(trainset);
|
||||
|
||||
_carried = BuildCarriedVisual(blockBounds.Height);
|
||||
Canvas.SetTop(_carried, blockBounds.Top);
|
||||
Canvas.SetLeft(_carried, blockBounds.Left);
|
||||
consistOverlay.Children.Add(_carried);
|
||||
|
||||
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||
consistStack.Children[i].IsVisible = false;
|
||||
|
||||
_dragging = true;
|
||||
e.Pointer.Capture(consistStack);
|
||||
}
|
||||
|
||||
private void CaptureDropTargets(Trainset trainset)
|
||||
{
|
||||
var vehicles = trainset.Vehicles;
|
||||
|
||||
_reducedToVehicle.Clear();
|
||||
for (int i = 0; i < vehicles.Count && i < consistStack.Children.Count; i++)
|
||||
if (i < _dragBlockStart || i >= _dragBlockStart + _dragBlockLength)
|
||||
_reducedToVehicle.Add(i);
|
||||
|
||||
_unitStarts.Clear();
|
||||
var midpoints = new List<double>();
|
||||
|
||||
int r = 0;
|
||||
while (r < _reducedToVehicle.Count)
|
||||
{
|
||||
int length = 1;
|
||||
while (r + length < _reducedToVehicle.Count &&
|
||||
_reducedToVehicle[r + length] == _reducedToVehicle[r + length - 1] + 1 &&
|
||||
HoldsNext(vehicles[_reducedToVehicle[r + length - 1]]))
|
||||
length++;
|
||||
|
||||
double left = ChildEdge(_reducedToVehicle[r], right: false);
|
||||
double right = ChildEdge(_reducedToVehicle[r + length - 1], right: true);
|
||||
midpoints.Add((left + right) / 2);
|
||||
_unitStarts.Add(r);
|
||||
r += length;
|
||||
}
|
||||
|
||||
_dropTracker.Capture(midpoints);
|
||||
}
|
||||
|
||||
private double ChildEdge(int childIndex, bool right)
|
||||
{
|
||||
var bounds = consistStack.Children[childIndex].Bounds;
|
||||
double edge = right ? bounds.Right : bounds.Left;
|
||||
return childIndex > _dragBlockStart ? edge - _dragBlockWidth : edge;
|
||||
}
|
||||
|
||||
private int ReducedTarget()
|
||||
{
|
||||
int unit = _dropTracker.Index;
|
||||
if (unit < 0) return -1;
|
||||
return unit < _unitStarts.Count ? _unitStarts[unit] : _reducedToVehicle.Count;
|
||||
}
|
||||
|
||||
private Control BuildCarriedVisual(double height)
|
||||
{
|
||||
var panel = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal };
|
||||
|
||||
for (int i = _dragBlockStart; i < _dragBlockStart + _dragBlockLength; i++)
|
||||
{
|
||||
var source = consistStack.Children[i];
|
||||
Control copy = source is Border { Child: Image image }
|
||||
? new Image { Source = image.Source, Height = image.Height, Stretch = image.Stretch }
|
||||
: new Border
|
||||
{
|
||||
Width = source.Bounds.Width,
|
||||
Height = source.Bounds.Height,
|
||||
Background = MissingMiniBrush
|
||||
};
|
||||
panel.Children.Add(copy);
|
||||
}
|
||||
|
||||
return new Border
|
||||
{
|
||||
Child = panel,
|
||||
Height = height,
|
||||
Opacity = 0.85,
|
||||
BorderThickness = new Thickness(0, 2),
|
||||
BorderBrush = SelectedVehicleBrush
|
||||
};
|
||||
}
|
||||
|
||||
private void CancelConsistDrag()
|
||||
{
|
||||
if (!_dragging) return;
|
||||
EndConsistDrag();
|
||||
}
|
||||
|
||||
private void EndConsistDrag()
|
||||
{
|
||||
for (int i = _dragBlockStart; i >= 0 && i < _dragBlockStart + _dragBlockLength; i++)
|
||||
if (i < consistStack.Children.Count)
|
||||
consistStack.Children[i].IsVisible = true;
|
||||
|
||||
if (_carried != null)
|
||||
consistOverlay.Children.Remove(_carried);
|
||||
_carried = null;
|
||||
|
||||
_dropTracker.Reset();
|
||||
_edgeScroller.Stop();
|
||||
_dropGap.Hide(consistStack);
|
||||
|
||||
_dragBlockStart = -1;
|
||||
_dragBlockLength = 0;
|
||||
_pressIndex = -1;
|
||||
_dragging = false;
|
||||
}
|
||||
|
||||
private void MoveConsistBlock(Trainset trainset, int start, int length, int target)
|
||||
{
|
||||
var vehicles = trainset.Vehicles;
|
||||
if (start < 0 || length <= 0 || start + length > vehicles.Count)
|
||||
return;
|
||||
|
||||
var block = vehicles.GetRange(start, length);
|
||||
vehicles.RemoveRange(start, length);
|
||||
|
||||
target = Math.Clamp(target, 0, vehicles.Count);
|
||||
if (target == start)
|
||||
{
|
||||
vehicles.InsertRange(start, block);
|
||||
return;
|
||||
}
|
||||
|
||||
vehicles.InsertRange(target, block);
|
||||
|
||||
RefreshVehicleLabels();
|
||||
ShowConsist(trainset);
|
||||
UpdateTrainStats(trainset);
|
||||
}
|
||||
|
||||
private static bool HoldsNext(Dynamic car) =>
|
||||
car.Coupling.Has(Coupling.WorkshopLock) || car.Coupling.Locked;
|
||||
|
||||
private static int SetStart(List<Dynamic> vehicles, int index)
|
||||
{
|
||||
while (index > 0 && HoldsNext(vehicles[index - 1]))
|
||||
index--;
|
||||
return index;
|
||||
}
|
||||
|
||||
private static int SetLength(List<Dynamic> vehicles, int start)
|
||||
{
|
||||
int length = 1;
|
||||
while (start + length < vehicles.Count && HoldsNext(vehicles[start + length - 1]))
|
||||
length++;
|
||||
return length;
|
||||
}
|
||||
|
||||
private void UpdateTrainStats(Trainset? trainset)
|
||||
{
|
||||
if (trainset is null)
|
||||
{
|
||||
trainStats.Text = "";
|
||||
trainStats.Children.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var db = GameData.Instance.Vehicles;
|
||||
trainStats.Text = TrainsetDisplay.FormatStats(
|
||||
StarterNG.Infrastructure.StatsBar.Fill(trainStats, TrainsetDisplay.StatsFields(
|
||||
trainset,
|
||||
car => Physics.For(car.DataFolder, db.TextureForSkin(car.SkinFile)?.Model)
|
||||
?? Physics.For(car.DataFolder, car.MmdFile)
|
||||
?? Physics.For(car.DataFolder, car.SkinFile),
|
||||
App.Loc["Length"], App.Loc["Mass"], App.Loc["VehicleCount"], App.Loc["Track"],
|
||||
car => db.TextureForSkin(car.SkinFile)?.ResolvedCategory);
|
||||
car => db.TextureForSkin(car.SkinFile)?.ResolvedCategory));
|
||||
}
|
||||
|
||||
private void RefreshSelectedConsist()
|
||||
|
||||
@@ -7,377 +7,551 @@
|
||||
x:Class="StarterNG.Views.Settings"
|
||||
x:CompileBindings="True">
|
||||
<DockPanel>
|
||||
<!-- Save / reset bar -->
|
||||
<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"
|
||||
Content="{Binding [Reset], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button x:Name="SaveButton" Classes="Flat Accent" Click="SaveButton_OnClick"
|
||||
Content="{Binding [Save], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<TextBlock VerticalAlignment="Center" Opacity="0.6" FontSize="11"
|
||||
Text="{Binding [SettingsPath], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBlock x:Name="SettingsPathText" VerticalAlignment="Center" Opacity="0.8" FontSize="11"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
|
||||
<TextBlock x:Name="SaveStatus" VerticalAlignment="Center" Opacity="0.8" Text="" />
|
||||
<Button x:Name="AboutButton" Classes="Flat" Click="AboutButton_OnClick"
|
||||
Content="{Binding [About], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button x:Name="ResetButton" Classes="Flat" Click="ResetButton_OnClick"
|
||||
Content="{Binding [Reset], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button x:Name="SaveButton" Classes="Flat Accent" Click="SaveButton_OnClick"
|
||||
Content="{Binding [Save], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<TabControl TabStripPlacement="Left">
|
||||
<!-- General -->
|
||||
<TabControl TabStripPlacement="Top">
|
||||
|
||||
<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" />
|
||||
<!-- Filled from the lang/ folder in the constructor. -->
|
||||
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350"
|
||||
SelectionChanged="LanguageComboBox_OnSelectionChanged" />
|
||||
</StackPanel>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid ColumnDefinitions="*,10,*,10,200" Margin="10">
|
||||
|
||||
<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 Grid.Column="0" Spacing="10">
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [Language], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ChangeLanguageCb" HorizontalAlignment="Stretch"
|
||||
SelectionChanged="LanguageComboBox_OnSelectionChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<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" />
|
||||
</StackPanel>
|
||||
<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}}" />
|
||||
|
||||
<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>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock x:Name="cursorSensitivityValue" DockPanel.Dock="Right" Classes="FieldValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="CursorSensitivitySlider" Minimum="1" Maximum="5" TickFrequency="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsSnapToTickEnabled="True" Value="3"
|
||||
ValueChanged="QualitySlider_OnValueChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Communication -->
|
||||
<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}}" />
|
||||
<ComboBox x:Name="FeedbackCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="FeedbackPortRow" Spacing="8" Orientation="Horizontal" IsVisible="False">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [FeedbackPort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<NumericUpDown x:Name="FeedbackPortNud" Minimum="0" Maximum="65535" Value="888" Width="120" />
|
||||
</StackPanel>
|
||||
<Expander x:Name="UartExpander" IsVisible="False" Header="{Binding [UartSection], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<CheckBox x:Name="UartEnableCb" Content="{Binding [UartEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartPort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox x:Name="UartPortTb" MinWidth="150" PlaceholderText="COM3" />
|
||||
<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>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartTune], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox x:Name="UartTuneTb" MinWidth="150" />
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="GamepadIgnoreCb" Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [Feedback], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="FeedbackCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="FeedbackPortRow" Classes="Field" IsVisible="False">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [FeedbackPort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<NumericUpDown x:Name="FeedbackPortNud" Minimum="0" Maximum="65535" Value="888"
|
||||
Increment="1" FormatString="0"
|
||||
HorizontalAlignment="Left" Width="140" />
|
||||
</StackPanel>
|
||||
<Button x:Name="ComButton" Classes="Flat" Cursor="Hand"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
IsVisible="False" Click="ComButton_OnClick"
|
||||
Content="{Binding [UartSection], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="UartDebugCb" Content="{Binding [UartDebug], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartMainCb" Content="{Binding [UartMain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartScndCb" Content="{Binding [UartScnd], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartTrainCb" Content="{Binding [UartTrain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartLocalCb" Content="{Binding [UartLocal], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartRadioVolumeCb" Content="{Binding [UartRadioVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartRadioChannelCb" Content="{Binding [UartRadioChannel], Source={x:Static starterNg:App.Loc}}" />
|
||||
</HeaderedContentControl>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Spacing="10">
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="SelectExeCb" HorizontalAlignment="Stretch" SelectedIndex="0" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="LogMissingVehicleFilesCb" Content="{Binding [LogMissingVehicleFiles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="AutoCloseStarterCb" Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="LargeThumbnailsCb" Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="BatteryDefaultCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</StackPanel>
|
||||
|
||||
<HeaderedContentControl Grid.Column="4" VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [SettingsProfile], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="6">
|
||||
<ComboBox x:Name="ProfileCb" HorizontalAlignment="Stretch" />
|
||||
<Button x:Name="ProfileApplyBtn" Classes="Flat" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="{Binding [ProfileApply], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileApplyButton_OnClick" />
|
||||
<Button x:Name="ProfileSaveAsBtn" Classes="Flat" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Content="{Binding [ProfileSaveAs], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileSaveAsButton_OnClick" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Controls / key bindings -->
|
||||
<TabItem Header="{Binding [Controls], Source={x:Static starterNg:App.Loc}}">
|
||||
<Grid x:Name="ControlsHost" RowDefinitions="Auto,*,Auto" Margin="12">
|
||||
<!-- Built in code-behind (BuildControlsTab): a search bar, the
|
||||
scrollable binding list, and the keyboard visualisation. -->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- Others -->
|
||||
<TabItem Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="LogMissingVehicleFilesCb" Content="{Binding [LogMissingVehicleFiles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Graphic -->
|
||||
<TabItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
|
||||
<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}}" />
|
||||
<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}}" />
|
||||
</ComboBox>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||
<Grid Margin="10" ColumnDefinitions="250,10,250,10,250">
|
||||
|
||||
<HeaderedContentControl Grid.Column="0" VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [GfxColResolution], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ResolutionCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem>3840x2160</ComboBoxItem>
|
||||
<ComboBoxItem>2560x1440</ComboBoxItem>
|
||||
<ComboBoxItem>1920x1080</ComboBoxItem>
|
||||
<ComboBoxItem>1600x900</ComboBoxItem>
|
||||
<ComboBoxItem>1366x768</ComboBoxItem>
|
||||
<ComboBoxItem>1280x720</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="FullscreenWindowedCb" Content="{Binding [FullscreenWindowed], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #bufferScale.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="bufferScale" Minimum="50" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True" Value="100" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Name="texResolution" Text="4096 px" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="textureResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="TextureResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Name="cabTexResolution" Text="4096 px" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="cabTextureResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="CabTextureResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="texFilteringValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="TexFilteringSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="5" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="multisamplingValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="MultisamplingSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="3" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="trackCurvesValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="TrackCurvesSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="renderRangeValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="RenderRangeSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="3" TickFrequency="1" IsSnapToTickEnabled="True" Value="1" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Grid.Column="2" VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [GfxColRendering], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="RenderEngineCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [REFull], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [REOld], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RESimpShader], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RESimp], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [REOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="RenderAngleVulkanCb" Content="{Binding [RenderAngleVulkan], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="AdditionalShadersCb" Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ChromaticAberrationCb" Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="MotionBlurCb" Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="SkipPipelineCb" Content="SkipPipeline" />
|
||||
<CheckBox x:Name="RenderVBOCb" Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="VSyncCb" Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #FpsLimitSlider.Value, StringFormat='{}{0:0} FPS'}" />
|
||||
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="FpsLimitSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="1" IsSnapToTickEnabled="True" Value="30"
|
||||
IsEnabled="{Binding #FpsLimitEnableCb.IsChecked}" />
|
||||
</StackPanel>
|
||||
|
||||
<Separator />
|
||||
<CheckBox x:Name="SmokeDisplayCb" Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Classes="Field" IsEnabled="{Binding #SmokeDisplayCb.IsChecked}">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="smokeParticlesValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="SmokeParticlesSlider" ValueChanged="QualitySlider_OnValueChanged" Minimum="1" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="2" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #DynamicLightsSlider.Value, StringFormat='{}{0:0}'}" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [DynamicLights], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="DynamicLightsSlider" Minimum="0" Maximum="7" TickFrequency="1" IsSnapToTickEnabled="True" Value="7" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #fovSlider.Value, StringFormat='{}{0:0}°'}" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [FieldOfView], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="fovSlider" Minimum="5" Maximum="110" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<StackPanel Grid.Column="4" Spacing="10">
|
||||
<HeaderedContentControl VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [GfxColShadows], Source={x:Static starterNg:App.Loc}}">
|
||||
<HeaderedContentControl.Tag>
|
||||
<CheckBox x:Name="RenderShadowsCb" FontSize="11" MinHeight="0"
|
||||
Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}" />
|
||||
</HeaderedContentControl.Tag>
|
||||
|
||||
<StackPanel Spacing="8" IsEnabled="{Binding #RenderShadowsCb.IsChecked}">
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ShadowDisplayCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="shaderResolution" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="shaderResolutionSlider" Minimum="9" Maximum="14" TickFrequency="1" IsSnapToTickEnabled="True" Value="12" ValueChanged="shaderResolutionSlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="shaderRangeValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="shaderRange" Minimum="25" Maximum="400" TickFrequency="5" IsSnapToTickEnabled="True" Value="150"
|
||||
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="cabShadowRangeValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="cabShaderSourceRange" Minimum="0" Maximum="100" TickFrequency="10" IsSnapToTickEnabled="True" Value="30"
|
||||
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #ShadowAngleLimitSlider.Value, StringFormat='{}{0:0.0}'}" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ShadowAngleLimit], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="ShadowAngleLimitSlider" Minimum="0.2" Maximum="1.0" TickFrequency="0.1" Value="0.2" HorizontalAlignment="Stretch" />
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldHint"
|
||||
Text="{Binding [ShadowLimitShort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBlock Classes="FieldHint"
|
||||
Text="{Binding [ShadowLimitNone], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [GfxColReflections], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="ReflectionsCubeMapCb" Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ReflectionsDetailsCb" HorizontalAlignment="Stretch" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue" Name="reflectionsFramerateValue" />
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider Name="reflectionsFramerate" Minimum="5" Maximum="120" TickFrequency="5" IsSnapToTickEnabled="True" Value="30"
|
||||
ValueChanged="QualitySlider_OnValueChanged" HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [GfxColScripts], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Classes="Field">
|
||||
<TextBlock Classes="FieldLabel"
|
||||
Text="{Binding [PyScreenPriority], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="PyScreenPriorityCb" HorizontalAlignment="Stretch">
|
||||
<ComboBoxItem Tag="normal" Content="{Binding [PyPriorityNormal], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="lower" Content="{Binding [PyPriorityLower], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="lowest" Content="{Binding [PyPriorityLowest], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="idle" Content="{Binding [PyPriorityIdle], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Tag="off" Content="{Binding [PyPriorityOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</StackPanel>
|
||||
<StackPanel 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">
|
||||
<ComboBoxItem>3840x2160</ComboBoxItem>
|
||||
<ComboBoxItem>2560x1440</ComboBoxItem>
|
||||
<ComboBoxItem>1920x1080</ComboBoxItem>
|
||||
<ComboBoxItem>1600x900</ComboBoxItem>
|
||||
<ComboBoxItem>1366x768</ComboBoxItem>
|
||||
<ComboBoxItem>1280x720</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="bufferScale" Minimum="50" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True" Value="100" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding #bufferScale.Value, StringFormat='{}{0}%'}" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="textureResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="14" ValueChanged="TextureResolutionSlider_OnValueChanged"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
||||
<TextBlock Name="texResolution" VerticalAlignment="Center" Text="4096" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="cabTextureResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="14" ValueChanged="CabTextureResolutionSlider_OnValueChanged"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
||||
<TextBlock Name="cabTexResolution" VerticalAlignment="Center" Text="4096" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="TexFilteringSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="5" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="MultisamplingSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="3" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [DynamicLights], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="DynamicLightsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="7" TickFrequency="1" IsSnapToTickEnabled="True" Value="7" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="RenderRangeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3" Value="1" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="TrackCurvesSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="4" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="FpsLimitSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="1" IsSnapToTickEnabled="True" Value="30" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowAngleLimit], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="ShadowAngleLimitSlider" Minimum="0.2" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="1.0" TickFrequency="0.1" Value="0.2" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="FullscreenWindowedCb" Content="{Binding [FullscreenWindowed], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderAngleVulkanCb" Content="{Binding [RenderAngleVulkan], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="VSyncCb" Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="SmokeDisplayCb" Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="SmokeParticlesSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="4" TickFrequency="1" IsSnapToTickEnabled="True" Value="2" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="ChromaticAberrationCb" Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="MotionBlurCb" Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="AdditionalShadersCb" Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ReflectionsCubeMapCb" Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderVBOCb" Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderShadowsCb" Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="reflectionsFramerate" Minimum="5" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="120" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #reflectionsFramerate.Value, StringFormat='{}{0} FPS'}" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="shaderResolutionSlider" Minimum="9" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="14" ValueChanged="shaderResolutionSlider_OnValueChanged"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True" Value="12" />
|
||||
<TextBlock Name="shaderResolution" VerticalAlignment="Center" Text="4096 px" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="shaderRange" Minimum="25" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="400" TickFrequency="5" IsSnapToTickEnabled="True" Value="150" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #shaderRange.Value, StringFormat='{}{0} m'}" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="cabShaderSourceRange" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="10" IsSnapToTickEnabled="True" Value="30" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #cabShaderSourceRange.Value, StringFormat='{}{0} m'}" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ShadowDisplayCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ReflectionsDetailsCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [FieldOfView], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider Name="fovSlider" Minimum="5" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="110" TickFrequency="5" IsSnapToTickEnabled="True" Value="30" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #fovSlider.Value, StringFormat='{}{0}°'}" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Physics -->
|
||||
<TabItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<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 Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Friction], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="FrictionSlider" Minimum="0.1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeStep], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="BrakeStepSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeSpeed], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="BrakeSpeedSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<TabItem Header="{Binding [SoundPhysics], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||
<Grid ColumnDefinitions="360,10,360" Margin="10">
|
||||
|
||||
<HeaderedContentControl Grid.Column="0" VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="EnableSoundsCb" Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #VolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [Volume], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="VolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #RadioVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="RadioVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #VehiclesVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="VehiclesVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #PositionalVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="PositionalVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #AmbientVolumeSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="AmbientVolumeSlider" Minimum="1" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #VolumePausedSlider.Value, StringFormat='{}{0:0}%'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [VolumePaused], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="VolumePausedSlider" Minimum="0" HorizontalAlignment="Stretch"
|
||||
Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
<HeaderedContentControl Grid.Column="2" VerticalAlignment="Top"
|
||||
Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8">
|
||||
<CheckBox x:Name="PhysicsAccuracyCb" Content="{Binding [PhysicsAccuracyIncreased], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="PantographBreakCb" Content="{Binding [BreakingPantographPossibility], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="OverheadOnlyCb" Content="{Binding [PowerOverheadLineOnly], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="SpeedometerTapesCb" Content="{Binding [SaveSpeedometerTapes], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="SimLogsCb" Content="{Binding [SaveSimLogs], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="KeepLogsCb" Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="DisplaySimulationCb" Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="DebugLogVisibleCb" Content="{Binding [DebugLogVisible], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}" />
|
||||
|
||||
<Separator />
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #FrictionSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [Friction], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="FrictionSlider" Minimum="0.1" HorizontalAlignment="Stretch"
|
||||
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #BrakeStepSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [BrakeStep], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="BrakeStepSlider" Minimum="0.4" HorizontalAlignment="Stretch"
|
||||
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Classes="Field">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Classes="FieldValue"
|
||||
Text="{Binding #BrakeSpeedSlider.Value, StringFormat='{}{0:0.00}'}" />
|
||||
<TextBlock Classes="FieldLabel" Text="{Binding [BrakeSpeed], Source={x:Static starterNg:App.Loc}}" />
|
||||
</DockPanel>
|
||||
<Slider x:Name="BrakeSpeedSlider" Minimum="0.4" HorizontalAlignment="Stretch"
|
||||
Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</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" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="RadioVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="VehiclesVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="PositionalVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="AmbientVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="{Binding [Controls], Source={x:Static starterNg:App.Loc}}">
|
||||
<Grid x:Name="ControlsHost" RowDefinitions="Auto,*,Auto" Margin="10">
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- Advanced -->
|
||||
<TabItem Header="{Binding [Advanced], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<CheckBox x:Name="CompressTexturesCb" Content="{Binding [CompressTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ScaleSpecularsCb" Content="{Binding [ScaleSpeculars], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UseGLESCb" Content="{Binding [UseGLES], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ShaderGammaCb" Content="{Binding [ShaderGamma], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ScreenMipmapsCb" Content="{Binding [ScreenMipmaps], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ExtendedModelConversionCb" Content="{Binding [ExtendedModelConversion], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="GfxResourceMoveCb" Content="{Binding [GfxResourceMove], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="GfxResourceSweepCb" Content="{Binding [GfxResourceSweep], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrainPhysicsThreads], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="TrainPhysicsThreadsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="0" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #TrainPhysicsThreadsSlider.Value}" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="IgnoreIrrelevantTrainsCb" Content="{Binding [IgnoreIrrelevantTrains], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Starter -->
|
||||
<TabItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding [SettingsProfile], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ProfileCb" MinWidth="220" />
|
||||
<Button x:Name="ProfileApplyBtn" Content="{Binding [ProfileApply], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileApplyButton_OnClick" />
|
||||
<Button x:Name="ProfileSaveAsBtn" Content="{Binding [ProfileSaveAs], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileSaveAsButton_OnClick" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="AutoCloseStarterCb" Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="LargeThumbnailsCb" Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="HideArchivalVehiclesCb" Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="BatteryDefaultCb" MinWidth="220" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -26,18 +26,22 @@ public partial class Settings : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
TrainPhysicsThreadsSlider.Maximum = Environment.ProcessorCount;
|
||||
SettingsPathText.Text = StarterNG.Classes.Settings.Instance.LoadedFrom;
|
||||
ToolTip.SetTip(SettingsPathText, StarterNG.Classes.Settings.Instance.LoadedFrom);
|
||||
|
||||
this.AttachedToVisualTree += (_, _) =>
|
||||
{
|
||||
TextureResolutionSlider_OnValueChanged(null, null);
|
||||
CabTextureResolutionSlider_OnValueChanged(null, null);
|
||||
shaderResolutionSlider_OnValueChanged(null, null);
|
||||
UpdateQualityCaptions();
|
||||
};
|
||||
|
||||
FillLanguageCombo();
|
||||
|
||||
App.Loc.LanguageChanged += SelectActiveLanguage;
|
||||
App.Loc.LanguageChanged += UpdateQualityCaptions;
|
||||
App.Loc.LanguageChanged += () => shaderResolutionSlider_OnValueChanged(null, null);
|
||||
|
||||
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
|
||||
FpsLimitEnableCb.IsCheckedChanged += (_, _) => FpsLimitSlider.IsEnabled = IsChecked(FpsLimitEnableCb);
|
||||
@@ -52,6 +56,15 @@ public partial class Settings : UserControl
|
||||
AddHandler(KeyDownEvent, OnControlsKeyDown, RoutingStrategies.Tunnel);
|
||||
}
|
||||
|
||||
private static int IndexOfTag(ComboBox cb, string? tag)
|
||||
{
|
||||
for (int i = 0; i < cb.Items.Count; i++)
|
||||
if (cb.Items[i] is ComboBoxItem item &&
|
||||
string.Equals(item.Tag as string, tag, StringComparison.OrdinalIgnoreCase))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int FindComboIndexByContent(ComboBox cb, string content)
|
||||
{
|
||||
for (int i = 0; i < cb.Items.Count; i++)
|
||||
@@ -146,16 +159,6 @@ public partial class Settings : UserControl
|
||||
GamepadIgnoreCb.IsChecked = s.IgnoreGamepad;
|
||||
FeedbackCb.SelectedIndex = s.FeedbackMode;
|
||||
FeedbackPortNud.Value = (decimal)s.FeedbackPort;
|
||||
UartEnableCb.IsChecked = s.UartEnabled;
|
||||
UartPortTb.Text = s.UartPort;
|
||||
UartTuneTb.Text = s.UartTune;
|
||||
UartDebugCb.IsChecked = s.UartDebug;
|
||||
UartMainCb.IsChecked = s.UartMain;
|
||||
UartScndCb.IsChecked = s.UartScnd;
|
||||
UartTrainCb.IsChecked = s.UartTrain;
|
||||
UartLocalCb.IsChecked = s.UartLocal;
|
||||
UartRadioVolumeCb.IsChecked = s.UartRadioVolume;
|
||||
UartRadioChannelCb.IsChecked = s.UartRadioChannel;
|
||||
UpdateFeedbackDetailVisibility();
|
||||
|
||||
if (s.SelectExeAutomatically)
|
||||
@@ -229,28 +232,22 @@ public partial class Settings : UserControl
|
||||
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
||||
PositionalVolumeSlider.Value = s.PositionalVolume;
|
||||
AmbientVolumeSlider.Value = s.AmbientVolume;
|
||||
|
||||
CompressTexturesCb.IsChecked = s.CompressTextures;
|
||||
ScaleSpecularsCb.IsChecked = s.ScaleSpeculars;
|
||||
UseGLESCb.IsChecked = s.UseGLES;
|
||||
ShaderGammaCb.IsChecked = s.ShaderGamma;
|
||||
ScreenMipmapsCb.IsChecked = s.ScreenMipmaps;
|
||||
ExtendedModelConversionCb.IsChecked = s.ExtendedModelConversion;
|
||||
GfxResourceMoveCb.IsChecked = s.GfxResourceMove;
|
||||
GfxResourceSweepCb.IsChecked = s.GfxResourceSweep;
|
||||
TrainPhysicsThreadsSlider.Value = s.TrainPhysicsThreads;
|
||||
IgnoreIrrelevantTrainsCb.IsChecked = s.IgnoreIrrelevantTrains;
|
||||
VolumePausedSlider.Value = s.VolumePaused;
|
||||
SkipPipelineCb.IsChecked = s.SkipPipeline;
|
||||
DebugLogVisibleCb.IsChecked = s.DebugLogVisible;
|
||||
PyScreenPriorityCb.SelectedIndex = Math.Max(0, IndexOfTag(PyScreenPriorityCb, s.PyScreenPriority));
|
||||
|
||||
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
||||
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
||||
AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree;
|
||||
HideArchivalVehiclesCb.IsChecked = s.HideArchivalVehicles;
|
||||
BatteryDefaultCb.SelectedIndex = (int)s.BatteryDefault;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
UpdateQualityCaptions();
|
||||
}
|
||||
|
||||
private void ReadFromUi()
|
||||
@@ -269,16 +266,6 @@ public partial class Settings : UserControl
|
||||
s.IgnoreGamepad = IsChecked(GamepadIgnoreCb);
|
||||
s.FeedbackMode = Math.Max(0, FeedbackCb.SelectedIndex);
|
||||
s.FeedbackPort = (int)(FeedbackPortNud.Value ?? 888);
|
||||
s.UartEnabled = IsChecked(UartEnableCb);
|
||||
s.UartPort = UartPortTb.Text ?? "";
|
||||
s.UartTune = UartTuneTb.Text ?? "";
|
||||
s.UartDebug = IsChecked(UartDebugCb);
|
||||
s.UartMain = IsChecked(UartMainCb);
|
||||
s.UartScnd = IsChecked(UartScndCb);
|
||||
s.UartTrain = IsChecked(UartTrainCb);
|
||||
s.UartLocal = IsChecked(UartLocalCb);
|
||||
s.UartRadioVolume = IsChecked(UartRadioVolumeCb);
|
||||
s.UartRadioChannel = IsChecked(UartRadioChannelCb);
|
||||
|
||||
s.SelectExeAutomatically = SelectExeCb.SelectedIndex == 0;
|
||||
s.ExecutablePath = s.SelectExeAutomatically ? "eu07.exe"
|
||||
@@ -340,25 +327,29 @@ public partial class Settings : UserControl
|
||||
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
||||
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
||||
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
||||
|
||||
s.CompressTextures = IsChecked(CompressTexturesCb);
|
||||
s.ScaleSpeculars = IsChecked(ScaleSpecularsCb);
|
||||
s.UseGLES = IsChecked(UseGLESCb);
|
||||
s.ShaderGamma = IsChecked(ShaderGammaCb);
|
||||
s.ScreenMipmaps = IsChecked(ScreenMipmapsCb);
|
||||
s.ExtendedModelConversion = IsChecked(ExtendedModelConversionCb);
|
||||
s.GfxResourceMove = IsChecked(GfxResourceMoveCb);
|
||||
s.GfxResourceSweep = IsChecked(GfxResourceSweepCb);
|
||||
s.TrainPhysicsThreads = (int)TrainPhysicsThreadsSlider.Value;
|
||||
s.IgnoreIrrelevantTrains = IsChecked(IgnoreIrrelevantTrainsCb);
|
||||
s.VolumePaused = (int)VolumePausedSlider.Value;
|
||||
s.SkipPipeline = IsChecked(SkipPipelineCb);
|
||||
s.DebugLogVisible = IsChecked(DebugLogVisibleCb);
|
||||
s.PyScreenPriority = (PyScreenPriorityCb.SelectedItem as ComboBoxItem)?.Tag as string ?? "normal";
|
||||
|
||||
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
||||
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
||||
s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb);
|
||||
s.HideArchivalVehicles = IsChecked(HideArchivalVehiclesCb);
|
||||
s.BatteryDefault = (BatteryDefault)Math.Max(0, BatteryDefaultCb.SelectedIndex);
|
||||
}
|
||||
|
||||
private void ComButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this) is Window owner)
|
||||
new UartWindow().ShowDialog(owner);
|
||||
}
|
||||
|
||||
private void AboutButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this) is Window owner)
|
||||
AboutWindow.Create().ShowDialog(owner);
|
||||
}
|
||||
|
||||
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ReadFromUi();
|
||||
@@ -436,6 +427,7 @@ public partial class Settings : UserControl
|
||||
|
||||
int resolution = 1 << (int)shaderResolutionSlider.Value;
|
||||
shaderResolution.Text = $"{resolution} px";
|
||||
ToolTip.SetTip(shaderResolutionSlider, ShadowMapWord(resolution));
|
||||
}
|
||||
|
||||
private void FillLanguageCombo()
|
||||
@@ -475,7 +467,7 @@ public partial class Settings : UserControl
|
||||
private void UpdateFeedbackDetailVisibility()
|
||||
{
|
||||
FeedbackPortRow.IsVisible = FeedbackCb.SelectedIndex == 3;
|
||||
UartExpander.IsVisible = FeedbackCb.SelectedIndex == 5;
|
||||
ComButton.IsVisible = FeedbackCb.SelectedIndex == 5;
|
||||
}
|
||||
|
||||
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
||||
@@ -535,7 +527,7 @@ public partial class Settings : UserControl
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Foreground = FgDimBrush
|
||||
});
|
||||
_controlsSearch = new TextBox { Width = 240, Watermark = App.Loc["Search"] };
|
||||
_controlsSearch = new TextBox { Width = 240, PlaceholderText = App.Loc["Search"] };
|
||||
_controlsSearch.TextChanged += (_, _) => RebuildBindingList();
|
||||
topBar.Children.Add(_controlsSearch);
|
||||
|
||||
@@ -1068,4 +1060,103 @@ public partial class Settings : UserControl
|
||||
|
||||
private static string Capitalize(string s) =>
|
||||
string.IsNullOrEmpty(s) ? s : char.ToUpperInvariant(s[0]) + s.Substring(1);
|
||||
|
||||
private static string[] QualityWords => new[]
|
||||
{
|
||||
App.Loc["QualityVeryLow"], App.Loc["QualityLow"], App.Loc["QualityNormal"],
|
||||
App.Loc["QualityHigh"], App.Loc["QualityVeryHigh"]
|
||||
};
|
||||
|
||||
private void QualitySlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||
=> UpdateQualityCaptions();
|
||||
|
||||
private void UpdateQualityCaptions()
|
||||
{
|
||||
var q = QualityWords;
|
||||
|
||||
if (texFilteringValue != null)
|
||||
texFilteringValue.Text = q[Clamp((int)TexFilteringSlider.Value, 1, 5) - 1];
|
||||
|
||||
string[] four = { q[0], q[1], q[3], q[4] };
|
||||
if (trackCurvesValue != null)
|
||||
trackCurvesValue.Text = four[Clamp((int)TrackCurvesSlider.Value, 1, 4) - 1];
|
||||
if (smokeParticlesValue != null)
|
||||
smokeParticlesValue.Text = four[Clamp((int)SmokeParticlesSlider.Value, 1, 4) - 1];
|
||||
|
||||
string[] msaa = { App.Loc["MsaaNone"], "x2", "x4", "x8" };
|
||||
if (multisamplingValue != null)
|
||||
multisamplingValue.Text = msaa[Clamp((int)MultisamplingSlider.Value, 1, 4) - 1];
|
||||
|
||||
string[] range = { App.Loc["RangeNormal"], App.Loc["RangeHigh"], App.Loc["RangeVeryHigh"] };
|
||||
if (renderRangeValue != null)
|
||||
renderRangeValue.Text = range[Clamp((int)RenderRangeSlider.Value, 1, 3) - 1];
|
||||
|
||||
if (cursorSensitivityValue != null)
|
||||
cursorSensitivityValue.Text = (int)CursorSensitivitySlider.Value switch
|
||||
{
|
||||
<= 1 => q[1],
|
||||
2 => App.Loc["QualityStandard"],
|
||||
3 => q[3],
|
||||
_ => q[4]
|
||||
};
|
||||
|
||||
if (shaderRangeValue != null)
|
||||
{
|
||||
int metres = (int)shaderRange.Value;
|
||||
shaderRangeValue.Text = $"{metres} m";
|
||||
ToolTip.SetTip(shaderRange, ShadowRangeWord(metres));
|
||||
}
|
||||
|
||||
if (cabShadowRangeValue != null)
|
||||
{
|
||||
int metres = (int)cabShaderSourceRange.Value;
|
||||
cabShadowRangeValue.Text = metres <= 0 ? App.Loc["RangeDisabled"] : $"{metres} m";
|
||||
ToolTip.SetTip(cabShaderSourceRange, CabShadowRangeWord(metres));
|
||||
}
|
||||
|
||||
if (reflectionsFramerateValue != null)
|
||||
{
|
||||
int fps = (int)reflectionsFramerate.Value;
|
||||
string word = ReflectionsRefreshWord(fps);
|
||||
reflectionsFramerateValue.Text = $"{fps} FPS";
|
||||
ToolTip.SetTip(reflectionsFramerate, word.Length == 0 ? null : word);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ShadowMapWord(int pixels) => pixels switch
|
||||
{
|
||||
<= 512 => App.Loc["QualityVeryLow"],
|
||||
<= 1024 => App.Loc["QualityLow"],
|
||||
<= 2048 => App.Loc["QualityModerate"],
|
||||
<= 4096 => App.Loc["QualityHigh"],
|
||||
_ => App.Loc["QualityVeryHigh"]
|
||||
};
|
||||
|
||||
private static string ShadowRangeWord(int metres) => metres switch
|
||||
{
|
||||
<= 25 => App.Loc["RangeVeryLow"],
|
||||
<= 50 => App.Loc["RangeLow"],
|
||||
<= 150 => App.Loc["RangeModerate"],
|
||||
<= 250 => App.Loc["RangeHigh"],
|
||||
_ => App.Loc["RangeVeryHigh"]
|
||||
};
|
||||
|
||||
private static string CabShadowRangeWord(int metres) => metres switch
|
||||
{
|
||||
<= 10 => App.Loc["RangeVeryLow"],
|
||||
<= 20 => App.Loc["RangeLow"],
|
||||
<= 30 => App.Loc["RangeStandard"],
|
||||
<= 50 => App.Loc["RangeHigh"],
|
||||
_ => App.Loc["RangeVeryHigh"]
|
||||
};
|
||||
|
||||
private static string ReflectionsRefreshWord(int fps) => fps switch
|
||||
{
|
||||
<= 5 => App.Loc["QualityVeryLow"],
|
||||
<= 10 => App.Loc["QualityLow"],
|
||||
<= 25 => App.Loc["QualityHigh"],
|
||||
<= 60 => App.Loc["QualityVeryHigh"],
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="720" MinHeight="400">
|
||||
<DockPanel Margin="10">
|
||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*,*,*" RowDefinitions="Auto,Auto,Auto"
|
||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*,*,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto"
|
||||
ColumnSpacing="8" RowSpacing="6" Margin="0,0,0,8">
|
||||
<CheckBox Name="fNameOn" Grid.Row="0" Grid.Column="0" Content="{Binding [TextureInfo], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fName" Grid.Row="0" Grid.Column="1" Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fName" Grid.Row="0" Grid.Column="1" PlaceholderText="{Binding [Search], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox Name="fOpOn" Grid.Row="0" Grid.Column="2" Content="{Binding [TexOperator], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fOp" Grid.Row="0" Grid.Column="3" />
|
||||
|
||||
@@ -24,15 +24,44 @@
|
||||
<TextBox Name="fStation" Grid.Row="2" Grid.Column="1" />
|
||||
<CheckBox Name="fPhotoOn" Grid.Row="2" Grid.Column="2" Content="{Binding [TexPhoto], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fPhoto" Grid.Row="2" Grid.Column="3" />
|
||||
|
||||
<CheckBox Name="fModelOn" Grid.Row="3" Grid.Column="0" Content="{Binding [Model], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fModel" Grid.Row="3" Grid.Column="1" />
|
||||
|
||||
<CheckBox Name="fRevFromOn" Grid.Row="4" Grid.Column="0" Content="{Binding [RevisionFrom], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fRevFrom" Grid.Row="4" Grid.Column="1" PlaceholderText="RRRR-MM-DD" />
|
||||
<CheckBox Name="fRevToOn" Grid.Row="4" Grid.Column="2" Content="{Binding [RevisionTo], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fRevTo" Grid.Row="4" Grid.Column="3" PlaceholderText="RRRR-MM-DD" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Name="statusText" DockPanel.Dock="Bottom" Margin="0,6,0,0"
|
||||
FontSize="11" Opacity="0.75" />
|
||||
|
||||
<Grid DockPanel.Dock="Left" Width="260" RowDefinitions="*,4,*">
|
||||
<ListBox Name="typeList" Grid.Row="0" Classes="Compact" />
|
||||
<GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch"
|
||||
Background="Transparent" ResizeDirection="Rows" />
|
||||
<ListBox Name="modelList" Grid.Row="2" Classes="Compact">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="28,90,*" ColumnSpacing="4">
|
||||
<TextBlock Grid.Column="0" Text="{Binding No}" Opacity="0.6" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Klasa}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Sciezka}" Opacity="0.75"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Name="spSplitter" DockPanel.Dock="Left" Width="4"
|
||||
Background="Transparent" ResizeDirection="Columns" />
|
||||
|
||||
<ListBox Name="textureList" DoubleTapped="TextureList_OnDoubleTapped">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="220,*,160,100,140" ColumnSpacing="8">
|
||||
<Grid ColumnDefinitions="1.4*,0.9*,1.2*,90,*" ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Skin}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Mini}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Operator}" Opacity="0.85" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
@@ -28,21 +28,65 @@ public partial class TextureBaseWindow : Window
|
||||
_all.Add(new Row(t, _db));
|
||||
}
|
||||
|
||||
foreach (var box in new Control[] { fName, fOp, fMini, fAuthor, fStation, fPhoto })
|
||||
foreach (var box in new Control[] { fName, fOp, fMini, fAuthor, fStation, fPhoto, fModel })
|
||||
{
|
||||
if (box is TextBox tb)
|
||||
tb.TextChanged += (_, _) => Refresh();
|
||||
}
|
||||
foreach (var box in new Control[] { fNameOn, fOpOn, fMiniOn, fAuthorOn, fStationOn, fPhotoOn })
|
||||
foreach (var box in new Control[] { fNameOn, fOpOn, fMiniOn, fAuthorOn, fStationOn, fPhotoOn,
|
||||
fModelOn, fRevFromOn, fRevToOn })
|
||||
{
|
||||
if (box is CheckBox cb)
|
||||
cb.IsCheckedChanged += (_, _) => Refresh();
|
||||
}
|
||||
|
||||
fRevFrom.TextChanged += (_, _) => Refresh();
|
||||
fRevTo.TextChanged += (_, _) => Refresh();
|
||||
|
||||
BuildTypeList();
|
||||
typeList.SelectionChanged += (_, _) => { BuildModelList(); Refresh(); };
|
||||
modelList.SelectionChanged += (_, _) => Refresh();
|
||||
|
||||
textureList.ContextRequested += TextureList_OnContextRequested;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private sealed record ModelRow(string No, string Klasa, string Sciezka);
|
||||
|
||||
private void BuildModelList()
|
||||
{
|
||||
modelList.ItemsSource = null;
|
||||
|
||||
string? cat = (typeList.SelectedItem as ListBoxItem)?.Tag as string;
|
||||
var rows = _all
|
||||
.Where(r => cat == null || string.Equals(r.Category, cat, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(r => r.Mini.Length > 0)
|
||||
.GroupBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Select((g, i) => new ModelRow($"{i + 1}.", g.Key, g.First().Directory))
|
||||
.ToList();
|
||||
|
||||
modelList.ItemsSource = rows;
|
||||
}
|
||||
|
||||
private static DateTime? ParseDate(string? text) =>
|
||||
DateTime.TryParse(text, System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.None, out var d) ? d : null;
|
||||
|
||||
private void BuildTypeList()
|
||||
{
|
||||
typeList.Items.Add(new ListBoxItem { Content = App.Loc["CatAll"], Tag = null });
|
||||
|
||||
foreach (string cat in _all.Select(r => r.Category)
|
||||
.Where(c => c.Length > 0)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(c => c, StringComparer.OrdinalIgnoreCase))
|
||||
typeList.Items.Add(new ListBoxItem { Content = cat, Tag = cat });
|
||||
|
||||
typeList.SelectedIndex = 0;
|
||||
BuildModelList();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
IEnumerable<Row> q = _all;
|
||||
@@ -53,6 +97,18 @@ public partial class TextureBaseWindow : Window
|
||||
q = Filter(q, fAuthorOn, fAuthor, r => r.Author);
|
||||
q = Filter(q, fStationOn, fStation, r => r.Station);
|
||||
q = Filter(q, fPhotoOn, fPhoto, r => r.Photo);
|
||||
q = Filter(q, fModelOn, fModel, r => r.Model);
|
||||
|
||||
if ((typeList.SelectedItem as ListBoxItem)?.Tag is string cat)
|
||||
q = q.Where(r => string.Equals(r.Category, cat, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (modelList.SelectedItem is ModelRow mr)
|
||||
q = q.Where(r => string.Equals(r.Mini, mr.Klasa, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (fRevFromOn.IsChecked == true && ParseDate(fRevFrom.Text) is { } from)
|
||||
q = q.Where(r => r.RevisionDate is { } d && d >= from);
|
||||
if (fRevToOn.IsChecked == true && ParseDate(fRevTo.Text) is { } to)
|
||||
q = q.Where(r => r.RevisionDate is { } d && d <= to);
|
||||
|
||||
var list = q
|
||||
.OrderBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
||||
@@ -98,12 +154,23 @@ public partial class TextureBaseWindow : Window
|
||||
{
|
||||
string dir = Path.Combine(Directory.GetCurrentDirectory(), "dynamic",
|
||||
row.Texture.Directory.Replace('/', Path.DirectorySeparatorChar).TrimEnd('\\', '/'));
|
||||
if (!Directory.Exists(dir)) return;
|
||||
if (!Directory.Exists(dir))
|
||||
dir = Path.Combine(Directory.GetCurrentDirectory(), row.Texture.Directory);
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||
string.Format(App.Loc["FaultFileNotFound"], dir));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
StarterNG.Infrastructure.Diagnostics.ReportOnUiThread(
|
||||
$"{dir}{Environment.NewLine}{Environment.NewLine}{App.Loc["FaultDetail"]} {ex.Message}");
|
||||
}
|
||||
};
|
||||
menu.Items.Add(open);
|
||||
menu.Open(textureList);
|
||||
@@ -120,6 +187,10 @@ public partial class TextureBaseWindow : Window
|
||||
public string Author { get; }
|
||||
public string Station { get; }
|
||||
public string Photo { get; }
|
||||
public string Model { get; }
|
||||
public string Category { get; }
|
||||
public string Directory { get; }
|
||||
public DateTime? RevisionDate { get; }
|
||||
|
||||
public Row(VehicleTexture t, VehicleDatabase db)
|
||||
{
|
||||
@@ -131,6 +202,11 @@ public partial class TextureBaseWindow : Window
|
||||
Author = t.Meta?.TextureAuthor ?? "";
|
||||
Station = t.Meta?.Depot ?? "";
|
||||
Photo = t.Meta?.PhotoAuthor ?? "";
|
||||
Model = t.Model ?? "";
|
||||
Category = t.ResolvedCategory ?? "";
|
||||
Directory = t.Directory ?? "";
|
||||
RevisionDate = DateTime.TryParse(Revision, System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.None, out var rev) ? rev : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
StarterNG/Views/UartWindow.cs
Normal file
129
StarterNG/Views/UartWindow.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using SettingsModel = StarterNG.Classes.Settings;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public sealed class UartWindow : Window
|
||||
{
|
||||
private readonly CheckBox _enabled = new();
|
||||
private readonly TextBox _port = new() { PlaceholderText = "COM3", MinWidth = 170 };
|
||||
private readonly TextBox _tune = new() { MinWidth = 170 };
|
||||
private readonly CheckBox _debug = new();
|
||||
private readonly CheckBox _main = new();
|
||||
private readonly CheckBox _scnd = new();
|
||||
private readonly CheckBox _train = new();
|
||||
private readonly CheckBox _local = new();
|
||||
private readonly CheckBox _radioVolume = new();
|
||||
private readonly CheckBox _radioChannel = new();
|
||||
|
||||
public UartWindow()
|
||||
{
|
||||
Title = App.Loc["UartSection"];
|
||||
Width = 430;
|
||||
SizeToContent = SizeToContent.Height;
|
||||
CanResize = false;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
|
||||
_enabled.Content = App.Loc["UartEnable"];
|
||||
_debug.Content = App.Loc["UartDebug"];
|
||||
_main.Content = App.Loc["UartMain"];
|
||||
_scnd.Content = App.Loc["UartScnd"];
|
||||
_train.Content = App.Loc["UartTrain"];
|
||||
_local.Content = App.Loc["UartLocal"];
|
||||
_radioVolume.Content = App.Loc["UartRadioVolume"];
|
||||
_radioChannel.Content = App.Loc["UartRadioChannel"];
|
||||
|
||||
Load();
|
||||
|
||||
var root = new StackPanel { Margin = new Avalonia.Thickness(16), Spacing = 8 };
|
||||
root.Children.Add(_enabled);
|
||||
root.Children.Add(Row(App.Loc["UartPort"], _port));
|
||||
root.Children.Add(Row(App.Loc["UartTune"], _tune));
|
||||
root.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["UartHint"],
|
||||
FontSize = 11,
|
||||
Opacity = 0.65,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
});
|
||||
root.Children.Add(_debug);
|
||||
|
||||
root.Children.Add(new TextBlock
|
||||
{
|
||||
Text = App.Loc["UartFeature"],
|
||||
FontWeight = FontWeight.Bold,
|
||||
Margin = new Avalonia.Thickness(0, 6, 0, 0)
|
||||
});
|
||||
var top = new UniformGrid { Columns = 2, Margin = new Avalonia.Thickness(0, 0, 0, 2) };
|
||||
top.Children.Add(_main);
|
||||
top.Children.Add(_scnd);
|
||||
top.Children.Add(_train);
|
||||
top.Children.Add(_local);
|
||||
root.Children.Add(top);
|
||||
|
||||
var mid = new UniformGrid { Columns = 2 };
|
||||
mid.Children.Add(_radioVolume);
|
||||
mid.Children.Add(_radioChannel);
|
||||
root.Children.Add(mid);
|
||||
|
||||
var save = new Button
|
||||
{
|
||||
Content = App.Loc["UartSaveClose"],
|
||||
MinWidth = 140,
|
||||
Margin = new Avalonia.Thickness(0, 10, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand)
|
||||
};
|
||||
save.Classes.Add("Accent");
|
||||
save.Click += (_, _) => { Store(); Close(); };
|
||||
root.Children.Add(save);
|
||||
|
||||
Content = root;
|
||||
}
|
||||
|
||||
private static Control Row(string label, Control input)
|
||||
{
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
MinWidth = 110
|
||||
});
|
||||
sp.Children.Add(input);
|
||||
return sp;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
var s = SettingsModel.Instance;
|
||||
_enabled.IsChecked = s.UartEnabled;
|
||||
_port.Text = s.UartPort;
|
||||
_tune.Text = s.UartTune;
|
||||
_debug.IsChecked = s.UartDebug;
|
||||
_main.IsChecked = s.UartMain;
|
||||
_scnd.IsChecked = s.UartScnd;
|
||||
_train.IsChecked = s.UartTrain;
|
||||
_local.IsChecked = s.UartLocal;
|
||||
_radioVolume.IsChecked = s.UartRadioVolume;
|
||||
_radioChannel.IsChecked = s.UartRadioChannel;
|
||||
}
|
||||
|
||||
private void Store()
|
||||
{
|
||||
var s = SettingsModel.Instance;
|
||||
s.UartEnabled = _enabled.IsChecked == true;
|
||||
s.UartPort = _port.Text ?? "";
|
||||
s.UartTune = _tune.Text ?? "";
|
||||
s.UartDebug = _debug.IsChecked == true;
|
||||
s.UartMain = _main.IsChecked == true;
|
||||
s.UartScnd = _scnd.IsChecked == true;
|
||||
s.UartTrain = _train.IsChecked == true;
|
||||
s.UartLocal = _local.IsChecked == true;
|
||||
s.UartRadioVolume = _radioVolume.IsChecked == true;
|
||||
s.UartRadioChannel = _radioChannel.IsChecked == true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user