mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-30 19:50:28 +01:00
Rework the depot browser layout and confirm a crewless start
The vehicle database panel loses its header bar and packs the search box and the archival filter into one line, the class combo takes a full-width line of its own with a miniature (big in the drop-down, one line when closed) and the bulk / texture-base actions move into the Add button's drop-down. The mini preview sits beside the texture data and stays hidden until an entry is picked, so the list keeps the space. Picking a class or a vehicle now fills both combos in, and a class alone is a valid filter (it used to leave the list empty without a category). The combos and the list are rebuilt on the next dispatcher pass, since clearing Items from inside the SelectionChanged that asked for it throws. Vehicle removal becomes a per-card tool next to flip / crew, the scenery and vehicle names move into their panel headers, and the texture randomizer dialog sizes to its content instead of clipping its last button. Start no longer hides behind Ctrl: a consist with no crew and no passenger says so on the button and asks for confirmation before launching.
This commit is contained in:
@@ -19,8 +19,6 @@ namespace StarterNG;
|
|||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
// Tracks Ctrl for Start bypass (Pascal btnStart.Tag while Ctrl held).
|
|
||||||
private KeyModifiers _mods;
|
|
||||||
private DateTime _openedUtc = DateTime.UtcNow;
|
private DateTime _openedUtc = DateTime.UtcNow;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
@@ -48,13 +46,6 @@ public partial class MainWindow : Window
|
|||||||
UpdateStartButton();
|
UpdateStartButton();
|
||||||
};
|
};
|
||||||
Activated += (_, _) => CheckExternalSettings();
|
Activated += (_, _) => CheckExternalSettings();
|
||||||
AddHandler(InputElement.KeyDownEvent, (_, e) => { _mods = e.KeyModifiers; UpdateStartButton(); }, handledEventsToo: true);
|
|
||||||
AddHandler(InputElement.KeyUpEvent, (_, e) => { _mods = e.KeyModifiers; UpdateStartButton(); }, handledEventsToo: true);
|
|
||||||
AddHandler(InputElement.PointerMovedEvent, (_, e) =>
|
|
||||||
{
|
|
||||||
var m = e.KeyModifiers;
|
|
||||||
if (m != _mods) { _mods = m; UpdateStartButton(); }
|
|
||||||
}, handledEventsToo: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lets the user drag the window by the top caption strip.
|
// Lets the user drag the window by the top caption strip.
|
||||||
@@ -185,15 +176,14 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pascal actStartUpdate: Start needs scenery + trainset + staffed/passenger
|
// Pascal actStartUpdate: Start needs a scenery and a trainset. A consist with
|
||||||
// vehicle. Hold Ctrl to force START (bypass crew check).
|
// nobody aboard still starts, after the confirmation in StartButton_OnClick.
|
||||||
private void UpdateStartButton()
|
private void UpdateStartButton()
|
||||||
{
|
{
|
||||||
if (startButton is null) return;
|
if (startButton is null) return;
|
||||||
|
|
||||||
var scenery = AppState.Instance.CurrentScenery;
|
var scenery = AppState.Instance.CurrentScenery;
|
||||||
var trainset = AppState.Instance.CurrentTrainset;
|
var trainset = AppState.Instance.CurrentTrainset;
|
||||||
bool ctrl = _mods.HasFlag(KeyModifiers.Control);
|
|
||||||
|
|
||||||
if (scenery is null)
|
if (scenery is null)
|
||||||
{
|
{
|
||||||
@@ -207,14 +197,11 @@ public partial class MainWindow : Window
|
|||||||
startButton.IsEnabled = false;
|
startButton.IsEnabled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!HasStartableVehicle(trainset) && !ctrl)
|
// No driver and no passenger: still startable, but say so on the button
|
||||||
{
|
// and ask before launching.
|
||||||
startButton.Content = App.Loc["StartNoStaff"];
|
bool staffed = HasStartableVehicle(trainset);
|
||||||
startButton.IsEnabled = false;
|
startButton.Content = staffed ? App.Loc["Start"] : App.Loc["StartNoStaff"];
|
||||||
return;
|
ToolTip.SetTip(startButton, staffed ? null : App.Loc["StartNoStaffConfirm"]);
|
||||||
}
|
|
||||||
|
|
||||||
startButton.Content = App.Loc["Start"];
|
|
||||||
startButton.IsEnabled = true;
|
startButton.IsEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +223,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
// ── START: export the (possibly depot-modified) scenery and launch the sim.
|
// ── START: export the (possibly depot-modified) scenery and launch the sim.
|
||||||
// Global on the main window, like the original Starter's bottom START button.
|
// Global on the main window, like the original Starter's bottom START button.
|
||||||
private void StartButton_OnClick(object? sender, RoutedEventArgs e)
|
private async void StartButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var scenery = AppState.Instance.CurrentScenery;
|
var scenery = AppState.Instance.CurrentScenery;
|
||||||
if (scenery is null)
|
if (scenery is null)
|
||||||
@@ -245,7 +232,11 @@ public partial class MainWindow : Window
|
|||||||
if (trainset is null)
|
if (trainset is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!HasStartableVehicle(trainset) && !_mods.HasFlag(KeyModifiers.Control))
|
// Launching a consist with no driver and no passenger is legal (a scenery
|
||||||
|
// can be watched from the outside), so confirm instead of blocking it.
|
||||||
|
if (!HasStartableVehicle(trainset) &&
|
||||||
|
!await MessageBox.Show(this, App.Loc["StartNoStaffConfirm"],
|
||||||
|
App.Loc["StartNoStaffTitle"], MessageBoxButtons.YesNo))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (trainset.Vehicles.Count > 0)
|
if (trainset.Vehicles.Count > 0)
|
||||||
|
|||||||
@@ -13,97 +13,106 @@
|
|||||||
<ColumnDefinition />
|
<ColumnDefinition />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- Consists list and the brakes/loads editor share the upper area
|
<!-- Consists list and the brakes/loads editor share the upper area,
|
||||||
evenly, so the editor has room for all its controls. -->
|
the editor taking a bit more of it. -->
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" MinHeight="210" />
|
<RowDefinition Height="1.25*" MinHeight="250" />
|
||||||
<RowDefinition Height="190" />
|
<RowDefinition Height="190" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Vehicle database browser (left, rows 0-1) -->
|
<!-- Vehicle database browser (left, rows 0-1) -->
|
||||||
<HeaderedContentControl Grid.RowSpan="2" Theme="{StaticResource GroupBox}"
|
<Border Grid.RowSpan="2" Background="{StaticResource CarbonPanelBg}"
|
||||||
Header="{Binding [VehicleDatabase], Source={x:Static starterNg:App.Loc}}">
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||||
|
CornerRadius="2" Padding="8">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<Button Name="textureBaseButton" DockPanel.Dock="Top" Classes="Basic" FontSize="11"
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6" LastChildFill="True">
|
||||||
Margin="0,0,0,6" Padding="8,2" Cursor="Hand"
|
<CheckBox Name="hideArchivalCheck" DockPanel.Dock="Right" FontSize="11"
|
||||||
HorizontalAlignment="Left"
|
VerticalAlignment="Center" Margin="8,0,0,0"
|
||||||
Click="TextureBaseButton_OnClick"
|
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
||||||
Content="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
||||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*" ColumnSpacing="6" Margin="0,0,0,6">
|
<TextBox Name="searchBox" VerticalAlignment="Center"
|
||||||
<ComboBox Name="categoryCombo" Grid.Column="0" HorizontalAlignment="Stretch"
|
Watermark="{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}}"
|
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||||
PointerWheelChanged="Combo_OnPointerWheel"
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
||||||
<ComboBox Name="classCombo" Grid.Column="1" HorizontalAlignment="Stretch"
|
<ComboBox Name="classCombo" Grid.Row="1" HorizontalAlignment="Stretch"
|
||||||
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
||||||
PointerWheelChanged="Combo_OnPointerWheel"
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBox Name="searchBox" DockPanel.Dock="Top" Margin="0,0,0,6"
|
|
||||||
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
TextChanged="SearchBox_OnTextChanged" />
|
|
||||||
<CheckBox Name="hideArchivalCheck" DockPanel.Dock="Top" Margin="0,0,0,8"
|
|
||||||
Content="{Binding [HideArchivalVehicles], Source={x:Static starterNg:App.Loc}}"
|
|
||||||
IsCheckedChanged="HideArchivalCheck_OnChanged" />
|
|
||||||
|
|
||||||
<!-- Mini preview of the selected entry + the single Add button.
|
<!-- The arrow stays enabled with nothing selected - adding a whole
|
||||||
Both are docked at the bottom, under the flat vehicle list. -->
|
category needs no highlighted entry. -->
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="4" Margin="0,4,0,0">
|
<DockPanel DockPanel.Dock="Bottom" Margin="0,8,0,0">
|
||||||
<Button Name="addAllCategoryButton" Classes="Flat" FontSize="11" Padding="6,2"
|
<Button Name="addMoreButton" DockPanel.Dock="Right" Classes="Flat Accent"
|
||||||
Cursor="Hand" Click="AddAllCategoryButton_OnClick"
|
Width="28" Margin="4,0,0,0" Cursor="Hand" Content="▾"
|
||||||
Content="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}"
|
HorizontalContentAlignment="Center"
|
||||||
ToolTip.Tip="{Binding [TipAddAllCategory], Source={x:Static starterNg:App.Loc}}" />
|
ToolTip.Tip="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}">
|
||||||
<Button Name="addUniqueMmdButton" Classes="Flat" FontSize="11" Padding="6,2"
|
<Button.Flyout>
|
||||||
Cursor="Hand" Click="AddUniqueMmdButton_OnClick"
|
<MenuFlyout Placement="TopEdgeAlignedRight">
|
||||||
Content="{Binding [AddUniqueMmd], Source={x:Static starterNg:App.Loc}}"
|
<MenuItem Click="AddAllCategoryButton_OnClick"
|
||||||
ToolTip.Tip="{Binding [TipAddUniqueMmd], Source={x:Static starterNg:App.Loc}}" />
|
Header="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}"
|
||||||
</StackPanel>
|
ToolTip.Tip="{Binding [TipAddAllCategory], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<Button Name="addVehicleButton" DockPanel.Dock="Bottom" Classes="Flat Accent"
|
<MenuItem Click="AddUniqueMmdButton_OnClick"
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
Header="{Binding [AddUniqueMmd], Source={x:Static starterNg:App.Loc}}"
|
||||||
Margin="0,8,0,0" IsEnabled="False" Cursor="Hand"
|
ToolTip.Tip="{Binding [TipAddUniqueMmd], Source={x:Static starterNg:App.Loc}}" />
|
||||||
Click="AddVehicleButton_OnClick"
|
<Separator />
|
||||||
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
<MenuItem Click="TextureBaseButton_OnClick"
|
||||||
<Border Name="miniPreviewPanel" DockPanel.Dock="Bottom" Height="62" Margin="0,6,0,0"
|
Header="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||||
Background="{StaticResource CarbonInputBg}"
|
</MenuFlyout>
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
</Button.Flyout>
|
||||||
CornerRadius="2" Cursor="Hand"
|
</Button>
|
||||||
PointerPressed="MiniPreview_OnPointerPressed">
|
<Button Name="addVehicleButton" Classes="Flat Accent"
|
||||||
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
</Border>
|
IsEnabled="False" Cursor="Hand"
|
||||||
<StackPanel Name="textureInfoPanel" DockPanel.Dock="Bottom" Margin="0,4,0,0" Spacing="1" />
|
Click="AddVehicleButton_OnClick"
|
||||||
|
Content="{Binding [AddVehicle], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</DockPanel>
|
||||||
|
<Grid Name="browserDetails" DockPanel.Dock="Bottom" ColumnDefinitions="130,*"
|
||||||
|
ColumnSpacing="6" Margin="0,6,0,0" IsVisible="False">
|
||||||
|
<Border Name="miniPreviewPanel" Grid.Column="0" Height="66"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{StaticResource CarbonInputBg}"
|
||||||
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||||
|
CornerRadius="2" Cursor="Hand"
|
||||||
|
PointerPressed="MiniPreview_OnPointerPressed">
|
||||||
|
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
||||||
|
</Border>
|
||||||
|
<StackPanel Name="textureInfoPanel" Grid.Column="1" Spacing="1"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<!-- Flat list of vehicle entries (plain text, no expanders).
|
<!-- Flat list of vehicle entries (plain text, no expanders).
|
||||||
Selecting one shows its mini and enables Add; double-click
|
Selecting one shows its mini and enables Add; double-click
|
||||||
replaces the selected consist vehicle. -->
|
replaces the selected consist vehicle. -->
|
||||||
<ListBox Name="vehicleListBox" Classes="Compact"
|
<ListBox Name="vehicleListBox" Classes="Compact" MinHeight="120"
|
||||||
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
||||||
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
||||||
PointerPressed="VehicleListBox_OnPointerPressed" />
|
PointerPressed="VehicleListBox_OnPointerPressed" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</HeaderedContentControl>
|
</Border>
|
||||||
|
|
||||||
<!-- Consists present on the scenery selected in the Scenarios tab.
|
<!-- Consists present on the scenery selected in the Scenarios tab.
|
||||||
Selecting one loads it into the editor below. -->
|
Selecting one loads it into the editor below; the header carries the
|
||||||
<HeaderedContentControl Grid.Column="1" Grid.Row="0" Theme="{StaticResource GroupBox}"
|
scenery name (see PopulateSceneryConsists). -->
|
||||||
Header="{Binding [MapConsists], Source={x:Static starterNg:App.Loc}}">
|
<HeaderedContentControl Name="mapConsistsPanel" Grid.Column="1" Grid.Row="0"
|
||||||
|
Theme="{StaticResource GroupBox}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<TextBlock Name="mapSceneryLabel" DockPanel.Dock="Top" FontSize="12"
|
|
||||||
Opacity="0.8" Margin="0,0,0,6" TextTrimming="CharacterEllipsis" />
|
|
||||||
<ListBox Name="sceneryConsistList" Classes="Compact"
|
<ListBox Name="sceneryConsistList" Classes="Compact"
|
||||||
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
SelectionChanged="SceneryConsistList_OnSelectionChanged"
|
||||||
KeyDown="SceneryConsistList_OnKeyDown" />
|
KeyDown="SceneryConsistList_OnKeyDown" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
<!-- Brakes / Loads editor (middle-right) -->
|
<!-- Brakes / Loads editor (middle-right). Header carries the vehicle
|
||||||
<HeaderedContentControl Grid.Column="1" Grid.Row="1" Theme="{StaticResource GroupBox}"
|
name (see UpdateDetails). -->
|
||||||
Header="{Binding [SelectedVehicle], Source={x:Static starterNg:App.Loc}}">
|
<HeaderedContentControl Name="selectedVehiclePanel" Grid.Column="1" Grid.Row="1"
|
||||||
|
Theme="{StaticResource GroupBox}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<Button Name="removeVehicleButton" DockPanel.Dock="Bottom" Classes="Flat"
|
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
|
||||||
Margin="0,8,0,0" IsEnabled="False" Cursor="Hand"
|
|
||||||
Click="RemoveVehicleButton_OnClick"
|
|
||||||
Content="{Binding [RemoveSelectedVehicle], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
<TabControl>
|
<TabControl>
|
||||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Input.Platform;
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
@@ -37,6 +38,12 @@ public partial class Depot : UserControl
|
|||||||
private int ConsistThumbHeight =>
|
private int ConsistThumbHeight =>
|
||||||
StarterNG.Classes.Settings.Instance.LargeThumbnails ? 68 : 34;
|
StarterNG.Classes.Settings.Instance.LargeThumbnails ? 68 : 34;
|
||||||
|
|
||||||
|
// Display height of the mini under the vehicle list; the class combo uses
|
||||||
|
// the same size.
|
||||||
|
private const int MiniPreviewHeight = 54;
|
||||||
|
|
||||||
|
private const int CompactThumbHeight = 24;
|
||||||
|
|
||||||
private readonly Cursor _hand = new(StandardCursorType.Hand);
|
private readonly Cursor _hand = new(StandardCursorType.Hand);
|
||||||
private int _nameCounter;
|
private int _nameCounter;
|
||||||
|
|
||||||
@@ -45,6 +52,7 @@ public partial class Depot : UserControl
|
|||||||
|
|
||||||
// suppresses combo SelectionChanged handlers while we populate them
|
// suppresses combo SelectionChanged handlers while we populate them
|
||||||
private bool _suppress;
|
private bool _suppress;
|
||||||
|
private bool _syncingCombos; // set while the combos mirror a pick
|
||||||
|
|
||||||
// debounces search input so we don't rebuild on every keystroke
|
// debounces search input so we don't rebuild on every keystroke
|
||||||
private DispatcherTimer? _searchTimer;
|
private DispatcherTimer? _searchTimer;
|
||||||
@@ -166,6 +174,7 @@ public partial class Depot : UserControl
|
|||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
hideArchivalCheck.IsChecked = StarterNG.Classes.Settings.Instance.HideArchivalVehicles;
|
hideArchivalCheck.IsChecked = StarterNG.Classes.Settings.Instance.HideArchivalVehicles;
|
||||||
|
InitClassComboTemplates();
|
||||||
PopulateCategoryCombo(); // -> populates class combo -> builds browser
|
PopulateCategoryCombo(); // -> populates class combo -> builds browser
|
||||||
PopulateSceneryConsists(); // -> lists the current scenery's consists
|
PopulateSceneryConsists(); // -> lists the current scenery's consists
|
||||||
RebuildConsist();
|
RebuildConsist();
|
||||||
@@ -366,12 +375,8 @@ public partial class Depot : UserControl
|
|||||||
|
|
||||||
private void RebuildClassCombo()
|
private void RebuildClassCombo()
|
||||||
{
|
{
|
||||||
// No "All" entry: an unselected class means "every class in the category".
|
|
||||||
// Each class row shows its category mini thumbnail (like the Pascal starter).
|
|
||||||
_suppress = true;
|
_suppress = true;
|
||||||
classCombo.Items.Clear();
|
FillClassCombo();
|
||||||
foreach (string cls in ClassesForCategory(_categoryFilter))
|
|
||||||
classCombo.Items.Add(new ComboBoxItem { Content = ClassComboContent(cls), Tag = cls });
|
|
||||||
classCombo.SelectedIndex = -1;
|
classCombo.SelectedIndex = -1;
|
||||||
_suppress = false;
|
_suppress = false;
|
||||||
|
|
||||||
@@ -379,27 +384,88 @@ public partial class Depot : UserControl
|
|||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Control ClassComboContent(string cls)
|
// The dropdown shows the big stacked miniature, the closed combo a one-line
|
||||||
|
// row. Plain "=": SelectionBoxItemTemplate coerces to ItemTemplate while
|
||||||
|
// unset, so "??=" would never assign.
|
||||||
|
private void InitClassComboTemplates()
|
||||||
{
|
{
|
||||||
var row = new StackPanel
|
classCombo.ItemTemplate = new FuncDataTemplate<string>(
|
||||||
|
(cls, _) => ClassComboContent(cls, large: true), false);
|
||||||
|
classCombo.SelectionBoxItemTemplate = new FuncDataTemplate<string>(
|
||||||
|
(cls, _) => ClassComboContent(cls, large: false), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills the class combo for the current category filter; the caller owns the
|
||||||
|
// selection and _classFilter. Runs under _suppress.
|
||||||
|
// No "All" entry: an unselected class means "every class in the category".
|
||||||
|
private void FillClassCombo()
|
||||||
|
{
|
||||||
|
classCombo.Items.Clear();
|
||||||
|
foreach (string cls in ClassesForCategory(_categoryFilter))
|
||||||
|
classCombo.Items.Add(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Points both combos at the given category/class pair without firing their
|
||||||
|
// handlers. Returns false when they already matched.
|
||||||
|
private bool ApplyFilters(string? category, string? cls)
|
||||||
|
{
|
||||||
|
var catItem = categoryCombo.Items.OfType<ComboBoxItem>()
|
||||||
|
.FirstOrDefault(it => it.Tag is Func<string?, bool> f && f(category));
|
||||||
|
|
||||||
|
if (catItem != null && ReferenceEquals(categoryCombo.SelectedItem, catItem) &&
|
||||||
|
string.Equals(_classFilter, cls, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_suppress = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
if (catItem != null)
|
||||||
Spacing = 8,
|
{
|
||||||
|
categoryCombo.SelectedItem = catItem;
|
||||||
|
_categoryFilter = catItem.Tag as Func<string?, bool>;
|
||||||
|
}
|
||||||
|
FillClassCombo();
|
||||||
|
classCombo.SelectedItem = classCombo.Items.OfType<string>()
|
||||||
|
.FirstOrDefault(s => string.Equals(s, cls, StringComparison.OrdinalIgnoreCase));
|
||||||
|
_classFilter = classCombo.SelectedItem as string;
|
||||||
|
}
|
||||||
|
finally { _suppress = false; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every texture of a class shares its category.
|
||||||
|
private string? CategoryOfClass(string cls) =>
|
||||||
|
_db.Textures.FirstOrDefault(t => string.Equals(ClassOf(t), cls, StringComparison.OrdinalIgnoreCase))
|
||||||
|
is { } t ? CategoryOf(t) : null;
|
||||||
|
|
||||||
|
private Control ClassComboContent(string cls, bool large)
|
||||||
|
{
|
||||||
|
int height = large ? MiniPreviewHeight : CompactThumbHeight;
|
||||||
|
var cell = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = large ? Orientation.Vertical : Orientation.Horizontal,
|
||||||
|
Spacing = large ? 2 : 8,
|
||||||
|
HorizontalAlignment = large ? HorizontalAlignment.Center : HorizontalAlignment.Left,
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
};
|
};
|
||||||
var bmp = GetMiniBitmap(cls, 28);
|
var bmp = GetMiniBitmap(cls, height);
|
||||||
if (bmp != null)
|
if (bmp != null)
|
||||||
row.Children.Add(new Image
|
cell.Children.Add(new Image
|
||||||
{
|
{
|
||||||
Source = bmp, Height = 28, Width = 56,
|
Source = bmp, Height = height,
|
||||||
Stretch = Stretch.Uniform, VerticalAlignment = VerticalAlignment.Center
|
Stretch = Stretch.Uniform,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
});
|
});
|
||||||
row.Children.Add(new TextBlock
|
cell.Children.Add(new TextBlock
|
||||||
{
|
{
|
||||||
Text = cls,
|
Text = cls,
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
HorizontalAlignment = large ? HorizontalAlignment.Center : HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
TextAlignment = TextAlignment.Center,
|
||||||
|
TextTrimming = TextTrimming.CharacterEllipsis
|
||||||
});
|
});
|
||||||
return row;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> ClassesForCategory(Func<string?, bool>? category) =>
|
private IEnumerable<string> ClassesForCategory(Func<string?, bool>? category) =>
|
||||||
@@ -422,7 +488,14 @@ public partial class Depot : UserControl
|
|||||||
private void ClassCombo_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void ClassCombo_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_suppress) return;
|
if (_suppress) return;
|
||||||
_classFilter = (classCombo.SelectedItem as ComboBoxItem)?.Tag as string;
|
string? cls = classCombo.SelectedItem as string;
|
||||||
|
_classFilter = cls;
|
||||||
|
|
||||||
|
// ApplyFilters refills this very combo, so it cannot run from here.
|
||||||
|
if (cls != null && _categoryFilter == null &&
|
||||||
|
PostSyncing(() => { ApplyFilters(CategoryOfClass(cls), cls); BuildList(); }))
|
||||||
|
return;
|
||||||
|
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,22 +526,25 @@ public partial class Depot : UserControl
|
|||||||
// ----------------------------------------------------------- vehicle browser
|
// ----------------------------------------------------------- vehicle browser
|
||||||
|
|
||||||
// Flat list of plain-text vehicle entries (no expanders, like the original
|
// Flat list of plain-text vehicle entries (no expanders, like the original
|
||||||
// Starter). Empty by default: an entry only appears once a category is chosen
|
// Starter). Empty by default: an entry only appears once a category or class
|
||||||
// or a search is typed. Picking one drives the mini preview and Add button.
|
// is chosen or a search is typed. Picking one drives the mini preview and the
|
||||||
|
// Add button.
|
||||||
private void BuildList()
|
private void BuildList()
|
||||||
{
|
{
|
||||||
// reset selection-dependent UI
|
// reset selection-dependent UI
|
||||||
_browserSelected = null;
|
_browserSelected = null;
|
||||||
miniPreview.Source = null;
|
miniPreview.Source = null;
|
||||||
addVehicleButton.IsEnabled = false;
|
addVehicleButton.IsEnabled = false;
|
||||||
|
browserDetails.IsVisible = false;
|
||||||
|
|
||||||
vehicleListBox.Items.Clear();
|
vehicleListBox.Items.Clear();
|
||||||
|
|
||||||
string search = searchBox.Text?.Trim() ?? "";
|
string search = searchBox.Text?.Trim() ?? "";
|
||||||
bool hasSearch = search.Length > 0;
|
bool hasSearch = search.Length > 0;
|
||||||
|
|
||||||
// nothing selected and nothing searched -> keep the list empty
|
// no filter and nothing searched -> keep the list empty (a class counts
|
||||||
if (_categoryFilter == null && !hasSearch)
|
// as a filter: it can be picked without a category)
|
||||||
|
if (_categoryFilter == null && _classFilter == null && !hasSearch)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var matched = _db.Textures
|
var matched = _db.Textures
|
||||||
@@ -502,9 +578,11 @@ public partial class Depot : UserControl
|
|||||||
if (vehicleListBox.SelectedItem is ListBoxItem { Tag: VehicleTexture texture })
|
if (vehicleListBox.SelectedItem is ListBoxItem { Tag: VehicleTexture texture })
|
||||||
{
|
{
|
||||||
_browserSelected = texture;
|
_browserSelected = texture;
|
||||||
miniPreview.Source = GetMiniBitmap(_db.ResolveMiniName(texture), 54);
|
miniPreview.Source = GetMiniBitmap(_db.ResolveMiniName(texture), MiniPreviewHeight);
|
||||||
addVehicleButton.IsEnabled = true;
|
addVehicleButton.IsEnabled = true;
|
||||||
ShowTextureInfo(texture);
|
ShowTextureInfo(texture);
|
||||||
|
browserDetails.IsVisible = true;
|
||||||
|
SyncCombosTo(texture);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -512,6 +590,7 @@ public partial class Depot : UserControl
|
|||||||
miniPreview.Source = null;
|
miniPreview.Source = null;
|
||||||
addVehicleButton.IsEnabled = false;
|
addVehicleButton.IsEnabled = false;
|
||||||
textureInfoPanel.Children.Clear();
|
textureInfoPanel.Children.Clear();
|
||||||
|
browserDetails.IsVisible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,12 +630,16 @@ public partial class Depot : UserControl
|
|||||||
private void ShowTextureInfo(VehicleTexture texture)
|
private void ShowTextureInfo(VehicleTexture texture)
|
||||||
{
|
{
|
||||||
textureInfoPanel.Children.Clear();
|
textureInfoPanel.Children.Clear();
|
||||||
|
// The rows are trimmed, so the tooltip carries the full text.
|
||||||
|
var full = new List<string>();
|
||||||
void Row(string label, string? value)
|
void Row(string label, string? value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value)) return;
|
if (string.IsNullOrWhiteSpace(value)) return;
|
||||||
|
string line = $"{label}: {value}";
|
||||||
|
full.Add(line);
|
||||||
textureInfoPanel.Children.Add(new TextBlock
|
textureInfoPanel.Children.Add(new TextBlock
|
||||||
{
|
{
|
||||||
Text = $"{label}: {value}",
|
Text = line,
|
||||||
FontSize = 10,
|
FontSize = 10,
|
||||||
Opacity = 0.85,
|
Opacity = 0.85,
|
||||||
TextTrimming = TextTrimming.CharacterEllipsis
|
TextTrimming = TextTrimming.CharacterEllipsis
|
||||||
@@ -565,12 +648,15 @@ public partial class Depot : UserControl
|
|||||||
|
|
||||||
Row(App.Loc["TextureInfo"], Base(texture.Skinfile));
|
Row(App.Loc["TextureInfo"], Base(texture.Skinfile));
|
||||||
var meta = texture.Meta;
|
var meta = texture.Meta;
|
||||||
if (meta == null) return;
|
if (meta != null)
|
||||||
Row(App.Loc["TexOperator"], meta.Operator);
|
{
|
||||||
Row(App.Loc["TexStation"], meta.Depot);
|
Row(App.Loc["TexOperator"], meta.Operator);
|
||||||
Row(App.Loc["TexRevision"], meta.RevisionDate);
|
Row(App.Loc["TexStation"], meta.Depot);
|
||||||
Row(App.Loc["TexAuthor"], meta.TextureAuthor);
|
Row(App.Loc["TexRevision"], meta.RevisionDate);
|
||||||
Row(App.Loc["TexPhoto"], meta.PhotoAuthor);
|
Row(App.Loc["TexAuthor"], meta.TextureAuthor);
|
||||||
|
Row(App.Loc["TexPhoto"], meta.PhotoAuthor);
|
||||||
|
}
|
||||||
|
ToolTip.SetTip(textureInfoPanel, string.Join("\n", full));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-click an entry to replace the selected consist vehicle.
|
// Double-click an entry to replace the selected consist vehicle.
|
||||||
@@ -763,8 +849,8 @@ public partial class Depot : UserControl
|
|||||||
sceneryConsistList.Items.Clear();
|
sceneryConsistList.Items.Clear();
|
||||||
|
|
||||||
_listScenery = AppState.Instance.CurrentScenery ?? _sceneries.FirstOrDefault();
|
_listScenery = AppState.Instance.CurrentScenery ?? _sceneries.FirstOrDefault();
|
||||||
mapSceneryLabel.Text = _listScenery != null
|
mapConsistsPanel.Header = _listScenery != null
|
||||||
? Path.GetFileNameWithoutExtension(_listScenery.Path)
|
? $"{App.Loc["MapConsists"]}: {Path.GetFileNameWithoutExtension(_listScenery.Path)}"
|
||||||
: App.Loc["NoSceneryConsists"];
|
: App.Loc["NoSceneryConsists"];
|
||||||
|
|
||||||
if (_listScenery != null)
|
if (_listScenery != null)
|
||||||
@@ -1179,6 +1265,11 @@ public partial class Depot : UserControl
|
|||||||
controls.Children.Add(SmallButton("⇄", App.Loc["TipFlip"], () => FlipItem(item)));
|
controls.Children.Add(SmallButton("⇄", App.Loc["TipFlip"], () => FlipItem(item)));
|
||||||
controls.Children.Add(SmallButton("▶", App.Loc["TipMoveRight"], () => MoveRight(item)));
|
controls.Children.Add(SmallButton("▶", App.Loc["TipMoveRight"], () => MoveRight(item)));
|
||||||
|
|
||||||
|
var remove = SmallButton("✕", App.Loc["RemoveSelectedVehicle"], () => RemoveItem(item));
|
||||||
|
remove.Foreground = new SolidColorBrush(Color.Parse("#E05252"));
|
||||||
|
remove.FontWeight = FontWeight.Bold;
|
||||||
|
controls.Children.Add(remove);
|
||||||
|
|
||||||
var inner = new StackPanel { Spacing = 4 };
|
var inner = new StackPanel { Spacing = 4 };
|
||||||
inner.Children.Add(minis);
|
inner.Children.Add(minis);
|
||||||
inner.Children.Add(nameBlock);
|
inner.Children.Add(nameBlock);
|
||||||
@@ -1268,37 +1359,51 @@ public partial class Depot : UserControl
|
|||||||
var set = _db.ResolveSet(texture);
|
var set = _db.ResolveSet(texture);
|
||||||
var browserTex = set is { Count: > 0 } ? set[0] : texture;
|
var browserTex = set is { Count: > 0 } ? set[0] : texture;
|
||||||
|
|
||||||
string? category = CategoryOf(browserTex);
|
WhileSyncing(() =>
|
||||||
var catItem = categoryCombo.Items.OfType<ComboBoxItem>()
|
|
||||||
.FirstOrDefault(it => it.Tag is Func<string?, bool> f && f(category));
|
|
||||||
|
|
||||||
if (catItem != null)
|
|
||||||
{
|
{
|
||||||
if (!ReferenceEquals(categoryCombo.SelectedItem, catItem))
|
ApplyFilters(CategoryOf(browserTex), ClassOf(browserTex));
|
||||||
categoryCombo.SelectedItem = catItem; // -> rebuilds class combo + list
|
|
||||||
else
|
|
||||||
RebuildClassCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select the vehicle's class (type) in the class combo.
|
|
||||||
string cls = ClassOf(browserTex);
|
|
||||||
var classItem = classCombo.Items.OfType<ComboBoxItem>()
|
|
||||||
.FirstOrDefault(it => it.Tag is string s &&
|
|
||||||
string.Equals(s, cls, StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (classItem != null)
|
|
||||||
{
|
|
||||||
_suppress = true;
|
|
||||||
classCombo.SelectedItem = classItem;
|
|
||||||
_classFilter = cls;
|
|
||||||
_suppress = false;
|
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
SelectListEntry(browserTex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// highlight the matching entry (selection drives the mini + Add button)
|
// Mirrors a list pick in the combos, then restores the selection that the
|
||||||
|
// rebuilt list dropped.
|
||||||
|
private void SyncCombosTo(VehicleTexture texture)
|
||||||
|
{
|
||||||
|
if (_syncingCombos || !ApplyFilters(CategoryOf(texture), ClassOf(texture)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
PostSyncing(() => { BuildList(); SelectListEntry(texture); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs work with the sync flag up, so the selections it makes do not come
|
||||||
|
// back as another sync.
|
||||||
|
private void WhileSyncing(Action work)
|
||||||
|
{
|
||||||
|
_syncingCombos = true;
|
||||||
|
try { work(); }
|
||||||
|
finally { _syncingCombos = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same, on the next dispatcher pass: clearing Items from inside a
|
||||||
|
// SelectionChanged throws. False when a sync is already pending.
|
||||||
|
private bool PostSyncing(Action work)
|
||||||
|
{
|
||||||
|
if (_syncingCombos)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_syncingCombos = true;
|
||||||
|
Dispatcher.UIThread.Post(() => WhileSyncing(work), DispatcherPriority.Background);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectListEntry(VehicleTexture texture)
|
||||||
|
{
|
||||||
foreach (var obj in vehicleListBox.Items)
|
foreach (var obj in vehicleListBox.Items)
|
||||||
if (obj is ListBoxItem { Tag: VehicleTexture t } entry &&
|
if (obj is ListBoxItem { Tag: VehicleTexture t } entry &&
|
||||||
string.Equals(t.Skinfile, browserTex.Skinfile, StringComparison.OrdinalIgnoreCase) &&
|
string.Equals(t.Skinfile, texture.Skinfile, StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(t.Directory, browserTex.Directory, StringComparison.OrdinalIgnoreCase))
|
string.Equals(t.Directory, texture.Directory, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
vehicleListBox.SelectedItem = entry;
|
vehicleListBox.SelectedItem = entry;
|
||||||
entry.BringIntoView();
|
entry.BringIntoView();
|
||||||
@@ -1485,7 +1590,11 @@ public partial class Depot : UserControl
|
|||||||
brakesPanel.Children.Clear();
|
brakesPanel.Children.Clear();
|
||||||
loadsPanel.Children.Clear();
|
loadsPanel.Children.Clear();
|
||||||
damagePanel.Children.Clear();
|
damagePanel.Children.Clear();
|
||||||
removeVehicleButton.IsEnabled = _selected != null;
|
|
||||||
|
selectedVehiclePanel.Header = _selected is null
|
||||||
|
? App.Loc["SelectedVehicle"]
|
||||||
|
: $"{App.Loc["SelectedVehicle"]}: " +
|
||||||
|
(_selected.Grouped ? UnitLabel(_selected) : _selected.Cars[0].Name);
|
||||||
|
|
||||||
// whole-consist load tools sit at the top of the Loads tab, always available
|
// whole-consist load tools sit at the top of the Loads tab, always available
|
||||||
loadsPanel.Children.Add(BuildConsistLoadTools());
|
loadsPanel.Children.Add(BuildConsistLoadTools());
|
||||||
@@ -1542,11 +1651,6 @@ public partial class Depot : UserControl
|
|||||||
private Control BuildConsistLoadTools()
|
private Control BuildConsistLoadTools()
|
||||||
{
|
{
|
||||||
var panel = new StackPanel { Spacing = 4, Margin = new Thickness(0, 0, 0, 4) };
|
var panel = new StackPanel { Spacing = 4, Margin = new Thickness(0, 0, 0, 4) };
|
||||||
panel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = App.Loc["ConsistLoadTools"], FontWeight = FontWeight.Bold, FontSize = 12
|
|
||||||
});
|
|
||||||
|
|
||||||
var row = new WrapPanel { Orientation = Orientation.Horizontal };
|
var row = new WrapPanel { Orientation = Orientation.Horizontal };
|
||||||
row.Children.Add(LoadToolButton(App.Loc["ConsistRandomType"], RandomLoadTypeForConsist));
|
row.Children.Add(LoadToolButton(App.Loc["ConsistRandomType"], RandomLoadTypeForConsist));
|
||||||
row.Children.Add(LoadToolButton(App.Loc["ConsistMaxAmount"], MaxAmountForConsist));
|
row.Children.Add(LoadToolButton(App.Loc["ConsistMaxAmount"], MaxAmountForConsist));
|
||||||
@@ -1681,12 +1785,6 @@ public partial class Depot : UserControl
|
|||||||
Text = text, Opacity = 0.6, FontSize = 12, TextWrapping = TextWrapping.Wrap
|
Text = text, Opacity = 0.6, FontSize = 12, TextWrapping = TextWrapping.Wrap
|
||||||
};
|
};
|
||||||
|
|
||||||
private static TextBlock UnitTitle(ConsistItem item) => new()
|
|
||||||
{
|
|
||||||
Text = item.Grouped ? UnitLabel(item) : item.Cars[0].Name,
|
|
||||||
FontWeight = FontWeight.Bold, FontSize = 12
|
|
||||||
};
|
|
||||||
|
|
||||||
// Row of "<label> <control>".
|
// Row of "<label> <control>".
|
||||||
private static Control LabeledRow(string label, Control control)
|
private static Control LabeledRow(string label, Control control)
|
||||||
{
|
{
|
||||||
@@ -1745,7 +1843,6 @@ public partial class Depot : UserControl
|
|||||||
loadCombo.SelectionChanged += (_, _) => Apply();
|
loadCombo.SelectionChanged += (_, _) => Apply();
|
||||||
switchCombo.SelectionChanged += (_, _) => Apply();
|
switchCombo.SelectionChanged += (_, _) => Apply();
|
||||||
|
|
||||||
brakesPanel.Children.Add(UnitTitle(item));
|
|
||||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeMode"], modeCombo));
|
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeMode"], modeCombo));
|
||||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeLoad"], loadCombo));
|
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeLoad"], loadCombo));
|
||||||
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeSwitch"], switchCombo));
|
brakesPanel.Children.Add(LabeledRow(App.Loc["BrakeSwitch"], switchCombo));
|
||||||
@@ -1789,7 +1886,7 @@ public partial class Depot : UserControl
|
|||||||
NumericUpDown Spin(int value, int max) => new()
|
NumericUpDown Spin(int value, int max) => new()
|
||||||
{
|
{
|
||||||
FontSize = 12, Minimum = 0, Maximum = max, Increment = 1,
|
FontSize = 12, Minimum = 0, Maximum = max, Increment = 1,
|
||||||
Value = value, MinWidth = 120, FormatString = "0"
|
Value = value, MinWidth = 0, Width = 100, FormatString = "0"
|
||||||
};
|
};
|
||||||
|
|
||||||
var sway = Spin(w.Sway, 100);
|
var sway = Spin(w.Sway, 100);
|
||||||
@@ -1815,11 +1912,39 @@ public partial class Depot : UserControl
|
|||||||
flatRand.ValueChanged += (_, _) => Apply();
|
flatRand.ValueChanged += (_, _) => Apply();
|
||||||
flatProb.ValueChanged += (_, _) => Apply();
|
flatProb.ValueChanged += (_, _) => Apply();
|
||||||
|
|
||||||
damagePanel.Children.Add(UnitTitle(item));
|
var grid = new Grid
|
||||||
damagePanel.Children.Add(LabeledRow(App.Loc["DamageSway"], sway));
|
{
|
||||||
damagePanel.Children.Add(LabeledRow(App.Loc["DamageFlatness"], flat));
|
ColumnDefinitions = new ColumnDefinitions("Auto,*"),
|
||||||
damagePanel.Children.Add(LabeledRow(App.Loc["DamageFlatnessRand"], flatRand));
|
RowSpacing = 6,
|
||||||
damagePanel.Children.Add(LabeledRow(App.Loc["DamageFlatnessProb"], flatProb));
|
ColumnSpacing = 8
|
||||||
|
};
|
||||||
|
void Row(string label, Control control)
|
||||||
|
{
|
||||||
|
int r = grid.RowDefinitions.Count;
|
||||||
|
grid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
||||||
|
|
||||||
|
var caption = new TextBlock
|
||||||
|
{
|
||||||
|
Text = label, FontSize = 12,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
Grid.SetRow(caption, r);
|
||||||
|
Grid.SetColumn(caption, 0);
|
||||||
|
|
||||||
|
control.HorizontalAlignment = HorizontalAlignment.Left;
|
||||||
|
Grid.SetRow(control, r);
|
||||||
|
Grid.SetColumn(control, 1);
|
||||||
|
|
||||||
|
grid.Children.Add(caption);
|
||||||
|
grid.Children.Add(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(App.Loc["DamageSway"], sway);
|
||||||
|
Row(App.Loc["DamageFlatness"], flat);
|
||||||
|
Row(App.Loc["DamageFlatnessRand"], flatRand);
|
||||||
|
Row(App.Loc["DamageFlatnessProb"], flatProb);
|
||||||
|
damagePanel.Children.Add(grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargo type + amount, written into each car's node::dynamic trailing params.
|
// Cargo type + amount, written into each car's node::dynamic trailing params.
|
||||||
@@ -1884,13 +2009,8 @@ public partial class Depot : UserControl
|
|||||||
typeCombo.SelectionChanged += (_, _) => Apply();
|
typeCombo.SelectionChanged += (_, _) => Apply();
|
||||||
countBox.ValueChanged += (_, _) => Apply();
|
countBox.ValueChanged += (_, _) => Apply();
|
||||||
|
|
||||||
loadsPanel.Children.Add(UnitTitle(item));
|
|
||||||
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadType"], typeCombo));
|
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadType"], typeCombo));
|
||||||
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadCount"], countBox));
|
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadCount"], countBox));
|
||||||
loadsPanel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = App.Loc["LoadHint"], Opacity = 0.6, FontSize = 11, TextWrapping = TextWrapping.Wrap
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargo names + per-unit weights parsed once from data/load_weights.txt (the
|
// Cargo names + per-unit weights parsed once from data/load_weights.txt (the
|
||||||
@@ -1992,12 +2112,6 @@ public partial class Depot : UserControl
|
|||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveVehicleButton_OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_selected != null)
|
|
||||||
RemoveItem(_selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void TextureBaseButton_OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void TextureBaseButton_OnClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (TopLevel.GetTopLevel(this) is not Window owner) return;
|
if (TopLevel.GetTopLevel(this) is not Window owner) return;
|
||||||
@@ -2032,38 +2146,54 @@ public partial class Depot : UserControl
|
|||||||
|
|
||||||
private Window BuildTextureRandomizerDialog()
|
private Window BuildTextureRandomizerDialog()
|
||||||
{
|
{
|
||||||
|
static Control Docked(Control c, Dock side)
|
||||||
|
{
|
||||||
|
DockPanel.SetDock(c, side);
|
||||||
|
c.Margin = new Thickness(8, 0, 0, 0);
|
||||||
|
c.VerticalAlignment = VerticalAlignment.Center;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextBlock Caption(string text) => new()
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
FontSize = 12,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
var revDiff = new CheckBox
|
var revDiff = new CheckBox
|
||||||
{
|
{
|
||||||
Content = App.Loc["RandomizeTexturesRevDiff"],
|
Content = Caption(App.Loc["RandomizeTexturesRevDiff"]),
|
||||||
IsChecked = true,
|
IsChecked = true,
|
||||||
FontSize = 12
|
FontSize = 12
|
||||||
};
|
};
|
||||||
var age = new NumericUpDown
|
var age = new NumericUpDown
|
||||||
{
|
{
|
||||||
Minimum = 2, Maximum = 8, Value = 3, Width = 56,
|
Minimum = 2, Maximum = 8, Value = 3, Width = 110,
|
||||||
FormatString = "0", ShowButtonSpinner = true
|
FormatString = "0", ShowButtonSpinner = true
|
||||||
};
|
};
|
||||||
var vsCurrent = new RadioButton
|
var vsCurrent = new RadioButton
|
||||||
{
|
{
|
||||||
Content = App.Loc["RandomizeTexturesVsCurrent"],
|
Content = Caption(App.Loc["RandomizeTexturesVsCurrent"]),
|
||||||
IsChecked = true,
|
IsChecked = true,
|
||||||
FontSize = 12,
|
FontSize = 12,
|
||||||
GroupName = "revBase"
|
GroupName = "revBase"
|
||||||
};
|
};
|
||||||
var vsYear = new RadioButton
|
var vsYear = new RadioButton
|
||||||
{
|
{
|
||||||
Content = App.Loc["RandomizeTexturesVsYear"],
|
Content = Caption(App.Loc["RandomizeTexturesVsYear"]),
|
||||||
FontSize = 12,
|
FontSize = 12,
|
||||||
GroupName = "revBase"
|
GroupName = "revBase"
|
||||||
};
|
};
|
||||||
var year = new NumericUpDown
|
var year = new NumericUpDown
|
||||||
{
|
{
|
||||||
Minimum = 1950, Maximum = 2050, Value = DateTime.Now.Year, Width = 72,
|
Minimum = 1950, Maximum = 2050, Value = DateTime.Now.Year, Width = 150,
|
||||||
FormatString = "0", ShowButtonSpinner = true, IsEnabled = false
|
FormatString = "0", ShowButtonSpinner = true, IsEnabled = false
|
||||||
};
|
};
|
||||||
var noArch = new CheckBox
|
var noArch = new CheckBox
|
||||||
{
|
{
|
||||||
Content = App.Loc["RandomizeTexturesNoArchival"],
|
Content = Caption(App.Loc["RandomizeTexturesNoArchival"]),
|
||||||
IsChecked = true,
|
IsChecked = true,
|
||||||
FontSize = 12
|
FontSize = 12
|
||||||
};
|
};
|
||||||
@@ -2074,8 +2204,8 @@ public partial class Depot : UserControl
|
|||||||
var win = new Window
|
var win = new Window
|
||||||
{
|
{
|
||||||
Title = App.Loc["RandomizeTexturesTitle"],
|
Title = App.Loc["RandomizeTexturesTitle"],
|
||||||
Width = 320,
|
Width = 420,
|
||||||
Height = 300,
|
SizeToContent = SizeToContent.Height,
|
||||||
CanResize = false,
|
CanResize = false,
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||||
};
|
};
|
||||||
@@ -2092,7 +2222,7 @@ public partial class Depot : UserControl
|
|||||||
var ok = new Button
|
var ok = new Button
|
||||||
{
|
{
|
||||||
Content = App.Loc["RandomizeTexturesApply"],
|
Content = App.Loc["RandomizeTexturesApply"],
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
MinWidth = 90,
|
||||||
Cursor = _hand
|
Cursor = _hand
|
||||||
};
|
};
|
||||||
ok.Classes.Add("Accent");
|
ok.Classes.Add("Accent");
|
||||||
@@ -2107,7 +2237,7 @@ public partial class Depot : UserControl
|
|||||||
win.Close(true);
|
win.Close(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
var cancel = new Button { Content = App.Loc["Cancel"], Cursor = _hand };
|
var cancel = new Button { Content = App.Loc["Cancel"], MinWidth = 90, Cursor = _hand };
|
||||||
cancel.Classes.Add("Flat");
|
cancel.Classes.Add("Flat");
|
||||||
cancel.Click += (_, _) => win.Close(false);
|
cancel.Click += (_, _) => win.Close(false);
|
||||||
|
|
||||||
@@ -2123,23 +2253,36 @@ public partial class Depot : UserControl
|
|||||||
FontWeight = FontWeight.Bold,
|
FontWeight = FontWeight.Bold,
|
||||||
HorizontalAlignment = HorizontalAlignment.Center
|
HorizontalAlignment = HorizontalAlignment.Center
|
||||||
},
|
},
|
||||||
new StackPanel
|
// DockPanel: in a horizontal StackPanel the caption gets infinite
|
||||||
|
// width, never wraps and shoves the spinner off the edge.
|
||||||
|
new DockPanel
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
LastChildFill = true,
|
||||||
Spacing = 8,
|
Children =
|
||||||
Children = { revDiff, age }
|
{
|
||||||
|
Docked(age, Dock.Right),
|
||||||
|
revDiff
|
||||||
|
}
|
||||||
},
|
},
|
||||||
vsCurrent,
|
vsCurrent,
|
||||||
new StackPanel
|
new DockPanel
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
LastChildFill = true,
|
||||||
Spacing = 8,
|
Children =
|
||||||
Children = { vsYear, year }
|
{
|
||||||
|
Docked(year, Dock.Right),
|
||||||
|
vsYear
|
||||||
|
}
|
||||||
},
|
},
|
||||||
noArch,
|
noArch,
|
||||||
rulesBtn,
|
rulesBtn,
|
||||||
ok,
|
new StackPanel
|
||||||
cancel
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Spacing = 8,
|
||||||
|
Children = { ok, cancel }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return win;
|
return win;
|
||||||
@@ -2161,7 +2304,7 @@ public partial class Depot : UserControl
|
|||||||
Text = text,
|
Text = text,
|
||||||
AcceptsReturn = true,
|
AcceptsReturn = true,
|
||||||
TextWrapping = TextWrapping.NoWrap,
|
TextWrapping = TextWrapping.NoWrap,
|
||||||
FontFamily = new FontFamily("Consolas,Courier New,monospace"),
|
FontFamily = StarterNG.Infrastructure.MonoFont.Family,
|
||||||
FontSize = 12
|
FontSize = 12
|
||||||
};
|
};
|
||||||
ScrollViewer.SetHorizontalScrollBarVisibility(box, Avalonia.Controls.Primitives.ScrollBarVisibility.Auto);
|
ScrollViewer.SetHorizontalScrollBarVisibility(box, Avalonia.Controls.Primitives.ScrollBarVisibility.Auto);
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
<String key="Start">Spustit</String>
|
<String key="Start">Spustit</String>
|
||||||
<String key="StartNoScenery">Select a scenery</String>
|
<String key="StartNoScenery">Select a scenery</String>
|
||||||
<String key="StartNoTrain">Select a consist</String>
|
<String key="StartNoTrain">Select a consist</String>
|
||||||
<String key="StartNoStaff">No crew / passenger (hold Ctrl to start)</String>
|
<String key="StartNoStaff">No crew / passenger</String>
|
||||||
|
<String key="StartNoStaffTitle">Consist without crew</String>
|
||||||
|
<String key="StartNoStaffConfirm">This consist has no crew and no passenger — there will be nothing to drive. Start anyway?</String>
|
||||||
<String key="RestoreWeather">Restore weather</String>
|
<String key="RestoreWeather">Restore weather</String>
|
||||||
<String key="ReloadSceneries">Reload</String>
|
<String key="ReloadSceneries">Reload</String>
|
||||||
<String key="OpenSceneryFolder">Open folder</String>
|
<String key="OpenSceneryFolder">Open folder</String>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
<String key="Start">Start</String>
|
<String key="Start">Start</String>
|
||||||
<String key="StartNoScenery">Select a scenery</String>
|
<String key="StartNoScenery">Select a scenery</String>
|
||||||
<String key="StartNoTrain">Select a consist</String>
|
<String key="StartNoTrain">Select a consist</String>
|
||||||
<String key="StartNoStaff">No crew / passenger (hold Ctrl to start)</String>
|
<String key="StartNoStaff">No crew / passenger</String>
|
||||||
|
<String key="StartNoStaffTitle">Consist without crew</String>
|
||||||
|
<String key="StartNoStaffConfirm">This consist has no crew and no passenger — there will be nothing to drive. Start anyway?</String>
|
||||||
<String key="RestoreWeather">Restore weather</String>
|
<String key="RestoreWeather">Restore weather</String>
|
||||||
<String key="ReloadSceneries">Reload</String>
|
<String key="ReloadSceneries">Reload</String>
|
||||||
<String key="OpenSceneryFolder">Open folder</String>
|
<String key="OpenSceneryFolder">Open folder</String>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
<String key="Start">Indítás</String>
|
<String key="Start">Indítás</String>
|
||||||
<String key="StartNoScenery">Select a scenery</String>
|
<String key="StartNoScenery">Select a scenery</String>
|
||||||
<String key="StartNoTrain">Select a consist</String>
|
<String key="StartNoTrain">Select a consist</String>
|
||||||
<String key="StartNoStaff">No crew / passenger (hold Ctrl to start)</String>
|
<String key="StartNoStaff">No crew / passenger</String>
|
||||||
|
<String key="StartNoStaffTitle">Consist without crew</String>
|
||||||
|
<String key="StartNoStaffConfirm">This consist has no crew and no passenger — there will be nothing to drive. Start anyway?</String>
|
||||||
<String key="RestoreWeather">Restore weather</String>
|
<String key="RestoreWeather">Restore weather</String>
|
||||||
<String key="ReloadSceneries">Reload</String>
|
<String key="ReloadSceneries">Reload</String>
|
||||||
<String key="OpenSceneryFolder">Open folder</String>
|
<String key="OpenSceneryFolder">Open folder</String>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
<String key="Start">Uruchom</String>
|
<String key="Start">Uruchom</String>
|
||||||
<String key="StartNoScenery">Wybierz scenerię</String>
|
<String key="StartNoScenery">Wybierz scenerię</String>
|
||||||
<String key="StartNoTrain">Wybierz skład</String>
|
<String key="StartNoTrain">Wybierz skład</String>
|
||||||
<String key="StartNoStaff">Brak obsady / pasażera (Ctrl = start)</String>
|
<String key="StartNoStaff">Brak obsady / pasażera</String>
|
||||||
|
<String key="StartNoStaffTitle">Skład bez obsady</String>
|
||||||
|
<String key="StartNoStaffConfirm">Skład nie ma obsady ani pasażera — nie będzie czym kierować. Uruchomić mimo to?</String>
|
||||||
<String key="RestoreWeather">Przywróć pogodę</String>
|
<String key="RestoreWeather">Przywróć pogodę</String>
|
||||||
<String key="ReloadSceneries">Przeładuj</String>
|
<String key="ReloadSceneries">Przeładuj</String>
|
||||||
<String key="OpenSceneryFolder">Otwórz folder</String>
|
<String key="OpenSceneryFolder">Otwórz folder</String>
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
<String key="Start">Запуск</String>
|
<String key="Start">Запуск</String>
|
||||||
<String key="StartNoScenery">Select a scenery</String>
|
<String key="StartNoScenery">Select a scenery</String>
|
||||||
<String key="StartNoTrain">Select a consist</String>
|
<String key="StartNoTrain">Select a consist</String>
|
||||||
<String key="StartNoStaff">No crew / passenger (hold Ctrl to start)</String>
|
<String key="StartNoStaff">No crew / passenger</String>
|
||||||
|
<String key="StartNoStaffTitle">Consist without crew</String>
|
||||||
|
<String key="StartNoStaffConfirm">This consist has no crew and no passenger — there will be nothing to drive. Start anyway?</String>
|
||||||
<String key="RestoreWeather">Restore weather</String>
|
<String key="RestoreWeather">Restore weather</String>
|
||||||
<String key="ReloadSceneries">Reload</String>
|
<String key="ReloadSceneries">Reload</String>
|
||||||
<String key="OpenSceneryFolder">Open folder</String>
|
<String key="OpenSceneryFolder">Open folder</String>
|
||||||
|
|||||||
Reference in New Issue
Block a user