mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-06 05:38:35 +01: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}"
|
<HeaderedContentControl Grid.RowSpan="2" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [VehicleDatabase], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [VehicleDatabase], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<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">
|
<Grid DockPanel.Dock="Top" ColumnDefinitions="*,*" ColumnSpacing="6" Margin="0,0,0,6">
|
||||||
<ComboBox Name="categoryCombo" Grid.Column="0" HorizontalAlignment="Stretch"
|
<ComboBox Name="categoryCombo" Grid.Column="0" HorizontalAlignment="Stretch"
|
||||||
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [Category], Source={x:Static starterNg:App.Loc}}"
|
||||||
@@ -34,12 +39,25 @@
|
|||||||
PointerWheelChanged="Combo_OnPointerWheel"
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
SelectionChanged="ClassCombo_OnSelectionChanged" />
|
||||||
</Grid>
|
</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}}"
|
Watermark="{Binding [Search], Source={x:Static starterNg:App.Loc}}"
|
||||||
TextChanged="SearchBox_OnTextChanged" />
|
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.
|
<!-- Mini preview of the selected entry + the single Add button.
|
||||||
Both are docked at the bottom, under the flat vehicle list. -->
|
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"
|
<Button Name="addVehicleButton" DockPanel.Dock="Bottom" Classes="Flat Accent"
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
Margin="0,8,0,0" IsEnabled="False" Cursor="Hand"
|
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"
|
<Border Name="miniPreviewPanel" DockPanel.Dock="Bottom" Height="62" Margin="0,6,0,0"
|
||||||
Background="{StaticResource CarbonInputBg}"
|
Background="{StaticResource CarbonInputBg}"
|
||||||
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="1"
|
||||||
CornerRadius="2">
|
CornerRadius="2" Cursor="Hand"
|
||||||
|
PointerPressed="MiniPreview_OnPointerPressed">
|
||||||
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
<Image Name="miniPreview" Stretch="Uniform" Margin="6" />
|
||||||
</Border>
|
</Border>
|
||||||
|
<StackPanel Name="textureInfoPanel" DockPanel.Dock="Bottom" Margin="0,4,0,0" Spacing="1" />
|
||||||
|
|
||||||
<!-- 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"
|
||||||
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
SelectionChanged="VehicleListBox_OnSelectionChanged"
|
||||||
DoubleTapped="VehicleListBox_OnDoubleTapped" />
|
DoubleTapped="VehicleListBox_OnDoubleTapped"
|
||||||
|
PointerPressed="VehicleListBox_OnPointerPressed" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</HeaderedContentControl>
|
</HeaderedContentControl>
|
||||||
|
|
||||||
@@ -78,6 +99,11 @@
|
|||||||
<HeaderedContentControl Grid.Column="1" Grid.Row="1" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Column="1" Grid.Row="1" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [SelectedVehicle], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [SelectedVehicle], Source={x:Static starterNg:App.Loc}}">
|
||||||
<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">
|
||||||
@@ -102,10 +128,26 @@
|
|||||||
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
<HeaderedContentControl Grid.Row="2" Grid.ColumnSpan="2" Theme="{StaticResource GroupBox}"
|
||||||
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<!-- Train totals computed from the .fiz files -->
|
<!-- Train totals + randomize tools -->
|
||||||
<TextBlock Name="trainStats" DockPanel.Dock="Top" FontSize="12"
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||||||
Opacity="0.85" Margin="0,0,0,6" />
|
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="4">
|
||||||
<Panel>
|
<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"
|
<TextBlock Name="emptyHint" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
TextWrapping="Wrap" MaxWidth="520" Opacity="0.6" TextAlignment="Center"
|
||||||
Text="{Binding [ConsistEmpty], Source={x:Static starterNg:App.Loc}}" />
|
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