mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
Rebuild the depot consist editor
#9 category minis in the class list; #10 coupler after the last car; #11 drag and drop from the browser and consist reorder; #12 fix forced headdriver (occupancy); #13 hide archival vehicles; #14 randomise order; #15 randomise orientation; #16 pick starting vehicle; #18 collapse multi-unit sets; texture randomiser and stock database.
This commit is contained in:
@@ -24,6 +24,11 @@
|
||||
<HeaderedContentControl Grid.RowSpan="2" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [VehicleDatabase], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button Name="textureBaseButton" DockPanel.Dock="Top" Classes="Basic" FontSize="11"
|
||||
Margin="0,0,0,6" Padding="8,2" Cursor="Hand"
|
||||
HorizontalAlignment="Left"
|
||||
Click="TextureBaseButton_OnClick"
|
||||
Content="{Binding [OpenTextureBase], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*" ColumnSpacing="6" Margin="0,0,0,6">
|
||||
<ComboBox Name="categoryCombo" Grid.Column="0" HorizontalAlignment="Stretch"
|
||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||
@@ -34,12 +39,25 @@
|
||||
PointerWheelChanged="Combo_OnPointerWheel"
|
||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||
</Grid>
|
||||
<TextBox Name="searchBox" DockPanel.Dock="Top" Margin="0,0,0,8"
|
||||
<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.
|
||||
Both are docked at the bottom, under the flat vehicle list. -->
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="4" Margin="0,4,0,0">
|
||||
<Button Name="addAllCategoryButton" Classes="Flat" FontSize="11" Padding="6,2"
|
||||
Cursor="Hand" Click="AddAllCategoryButton_OnClick"
|
||||
Content="{Binding [AddAllCategory], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipAddAllCategory], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Button Name="addUniqueMmdButton" Classes="Flat" FontSize="11" Padding="6,2"
|
||||
Cursor="Hand" Click="AddUniqueMmdButton_OnClick"
|
||||
Content="{Binding [AddUniqueMmd], Source={x:Static starterNg:App.Loc}}"
|
||||
ToolTip.Tip="{Binding [TipAddUniqueMmd], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
<Button Name="addVehicleButton" DockPanel.Dock="Bottom" Classes="Flat Accent"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Margin="0,8,0,0" IsEnabled="False" Cursor="Hand"
|
||||
@@ -48,16 +66,19 @@
|
||||
<Border Name="miniPreviewPanel" DockPanel.Dock="Bottom" Height="62" Margin="0,6,0,0"
|
||||
Background="{StaticResource CarbonInputBg}"
|
||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||
CornerRadius="2">
|
||||
CornerRadius="2" Cursor="Hand"
|
||||
PointerPressed="MiniPreview_OnPointerPressed">
|
||||
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
||||
</Border>
|
||||
<StackPanel Name="textureInfoPanel" DockPanel.Dock="Bottom" Margin="0,4,0,0" Spacing="1" />
|
||||
|
||||
<!-- Flat list of vehicle entries (plain text, no expanders).
|
||||
Selecting one shows its mini and enables Add; double-click
|
||||
replaces the selected consist vehicle. -->
|
||||
<ListBox Name="vehicleListBox" Classes="Compact"
|
||||
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
||||
DoubleTapped="VehicleListBox_OnDoubleTapped" />
|
||||
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
||||
PointerPressed="VehicleListBox_OnPointerPressed" />
|
||||
</DockPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
@@ -78,6 +99,11 @@
|
||||
<HeaderedContentControl Grid.Column="1" Grid.Row="1" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [SelectedVehicle], Source={x:Static starterNg:App.Loc}}">
|
||||
<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>
|
||||
<TabItem Header="{Binding [Brakes], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
@@ -102,10 +128,26 @@
|
||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<!-- Train totals computed from the .fiz files -->
|
||||
<TextBlock Name="trainStats" DockPanel.Dock="Top" FontSize="12"
|
||||
Opacity="0.85" Margin="0,0,0,6" />
|
||||
<Panel>
|
||||
<!-- 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" />
|
||||
</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}}" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
46
StarterNG/Views/TextureBaseWindow.axaml
Normal file
46
StarterNG/Views/TextureBaseWindow.axaml
Normal file
@@ -0,0 +1,46 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:starterNg="clr-namespace:StarterNG"
|
||||
x:Class="StarterNG.Views.TextureBaseWindow"
|
||||
x:CompileBindings="False"
|
||||
Title="{Binding [TextureBaseTitle], Source={x:Static starterNg:App.Loc}}"
|
||||
Width="980" Height="560"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="720" MinHeight="400">
|
||||
<DockPanel Margin="10">
|
||||
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*,*,*" RowDefinitions="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}}" />
|
||||
<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" />
|
||||
|
||||
<CheckBox Name="fMiniOn" Grid.Row="1" Grid.Column="0" Content="{Binding [Class], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fMini" Grid.Row="1" Grid.Column="1" />
|
||||
<CheckBox Name="fAuthorOn" Grid.Row="1" Grid.Column="2" Content="{Binding [TexAuthor], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox Name="fAuthor" Grid.Row="1" Grid.Column="3" />
|
||||
|
||||
<CheckBox Name="fStationOn" Grid.Row="2" Grid.Column="0" Content="{Binding [TexStation], Source={x:Static starterNg:App.Loc}}" />
|
||||
<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" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Name="statusText" DockPanel.Dock="Bottom" Margin="0,6,0,0"
|
||||
FontSize="11" Opacity="0.75" />
|
||||
|
||||
<ListBox Name="textureList" DoubleTapped="TextureList_OnDoubleTapped">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="220,*,160,100,140" 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" />
|
||||
<TextBlock Grid.Column="3" Text="{Binding Revision}" Opacity="0.85" />
|
||||
<TextBlock Grid.Column="4" Text="{Binding Author}" Opacity="0.75" TextTrimming="CharacterEllipsis" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
137
StarterNG/Views/TextureBaseWindow.axaml.cs
Normal file
137
StarterNG/Views/TextureBaseWindow.axaml.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Interactivity;
|
||||
using StarterNG.Classes;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
public partial class TextureBaseWindow : Window
|
||||
{
|
||||
private readonly VehicleDatabase _db = GameData.Instance.Vehicles;
|
||||
private readonly List<Row> _all = new();
|
||||
|
||||
/// <summary>Texture chosen by double-click (null if the window was just closed).</summary>
|
||||
public VehicleTexture? Picked { get; private set; }
|
||||
|
||||
public TextureBaseWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
foreach (var t in _db.Textures)
|
||||
{
|
||||
if (_db.IsSetFollower(t)) continue;
|
||||
_all.Add(new Row(t, _db));
|
||||
}
|
||||
|
||||
foreach (var box in new Control[] { fName, fOp, fMini, fAuthor, fStation, fPhoto })
|
||||
{
|
||||
if (box is TextBox tb)
|
||||
tb.TextChanged += (_, _) => Refresh();
|
||||
}
|
||||
foreach (var box in new Control[] { fNameOn, fOpOn, fMiniOn, fAuthorOn, fStationOn, fPhotoOn })
|
||||
{
|
||||
if (box is CheckBox cb)
|
||||
cb.IsCheckedChanged += (_, _) => Refresh();
|
||||
}
|
||||
|
||||
textureList.ContextRequested += TextureList_OnContextRequested;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
IEnumerable<Row> q = _all;
|
||||
|
||||
q = Filter(q, fNameOn, fName, r => r.Skin);
|
||||
q = Filter(q, fOpOn, fOp, r => r.Operator);
|
||||
q = Filter(q, fMiniOn, fMini, r => r.Mini);
|
||||
q = Filter(q, fAuthorOn, fAuthor, r => r.Author);
|
||||
q = Filter(q, fStationOn, fStation, r => r.Station);
|
||||
q = Filter(q, fPhotoOn, fPhoto, r => r.Photo);
|
||||
|
||||
var list = q
|
||||
.OrderBy(r => r.Mini, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(r => r.Skin, StringComparer.OrdinalIgnoreCase)
|
||||
.Take(5000)
|
||||
.ToList();
|
||||
|
||||
textureList.ItemsSource = list;
|
||||
statusText.Text = $"{list.Count} / {_all.Count}";
|
||||
}
|
||||
|
||||
private static IEnumerable<Row> Filter(
|
||||
IEnumerable<Row> src, CheckBox? on, TextBox? box, Func<Row, string> field)
|
||||
{
|
||||
if (on?.IsChecked != true) return src;
|
||||
string needle = box?.Text?.Trim() ?? "";
|
||||
if (needle.Length < 1) return src;
|
||||
return src.Where(r => field(r).Contains(needle, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void TextureList_OnDoubleTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (textureList.SelectedItem is not Row row) return;
|
||||
Picked = row.Texture;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void TextureList_OnContextRequested(object? sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (textureList.SelectedItem is not Row row) return;
|
||||
|
||||
var menu = new ContextMenu();
|
||||
var copy = new MenuItem { Header = App.Loc["CopyTextureName"] };
|
||||
copy.Click += async (_, _) =>
|
||||
{
|
||||
if (Clipboard != null)
|
||||
await Clipboard.SetTextAsync(row.Skin);
|
||||
};
|
||||
menu.Items.Add(copy);
|
||||
|
||||
var open = new MenuItem { Header = App.Loc["OpenTextureFolder"] };
|
||||
open.Click += (_, _) =>
|
||||
{
|
||||
string dir = Path.Combine(Directory.GetCurrentDirectory(), "dynamic",
|
||||
row.Texture.Directory.Replace('/', Path.DirectorySeparatorChar).TrimEnd('\\', '/'));
|
||||
if (!Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
menu.Items.Add(open);
|
||||
menu.Open(textureList);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private sealed class Row
|
||||
{
|
||||
public VehicleTexture Texture { get; }
|
||||
public string Skin { get; }
|
||||
public string Mini { get; }
|
||||
public string Operator { get; }
|
||||
public string Revision { get; }
|
||||
public string Author { get; }
|
||||
public string Station { get; }
|
||||
public string Photo { get; }
|
||||
|
||||
public Row(VehicleTexture t, VehicleDatabase db)
|
||||
{
|
||||
Texture = t;
|
||||
Skin = Path.GetFileNameWithoutExtension(t.Skinfile);
|
||||
Mini = db.ResolveMiniName(t) ?? t.ResolvedClass ?? "";
|
||||
Operator = t.Meta?.Operator ?? "";
|
||||
Revision = t.Meta?.RevisionDate ?? "";
|
||||
Author = t.Meta?.TextureAuthor ?? "";
|
||||
Station = t.Meta?.Depot ?? "";
|
||||
Photo = t.Meta?.PhotoAuthor ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user