mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-18 00:19:18 +02:00
Show consist stats, keep empty trainsets and restore last scenery in Scenarios
#20 length/mass/vehicle/track status bar; #17 empty trainsets stay listed; #19 remember last scenery; #16 default starting vehicle; scenery i18n labels, attachments/includes/info tab, weather restore and reload.
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="78" />
|
||||
<RowDefinition Height="150" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Scenery tree (left) -->
|
||||
@@ -27,6 +27,14 @@
|
||||
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 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>
|
||||
<TreeView Name="sceneryList" Classes="Compact"
|
||||
SelectionChanged="SceneryList_OnSelectionChanged" />
|
||||
</DockPanel>
|
||||
@@ -85,6 +93,10 @@
|
||||
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"
|
||||
@@ -109,7 +121,7 @@
|
||||
<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="-20" Maximum="40" Width="220"
|
||||
<Slider Name="weatherTemp" Minimum="-50" Maximum="50" Width="220"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||
ValueChanged="Weather_OnSliderChanged" />
|
||||
<TextBlock Name="weatherTempLabel" VerticalAlignment="Center" MinWidth="48" />
|
||||
@@ -150,6 +162,13 @@
|
||||
FontSize="12" />
|
||||
</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="Consolas, monospace" FontSize="12" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
|
||||
@@ -169,18 +188,26 @@
|
||||
|
||||
<HeaderedContentControl Grid.Row="1" Theme="{StaticResource GroupBox}"
|
||||
Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock TextWrapping="Wrap" Name="sceneryDescription" />
|
||||
</ScrollViewer>
|
||||
<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}}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Name="consistStack" />
|
||||
</ScrollViewer>
|
||||
<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" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</HeaderedContentControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -9,9 +9,11 @@ using System.Text.RegularExpressions;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Domain;
|
||||
|
||||
namespace StarterNG.Views;
|
||||
|
||||
@@ -46,6 +48,7 @@ public partial class Scenarios : UserControl
|
||||
_loadingFilters = false;
|
||||
|
||||
BuildSceneryTree();
|
||||
RestoreLastScenery();
|
||||
|
||||
// refresh the consist preview when the view is shown again (e.g. after
|
||||
// editing the consist in the depot). Switching tabs only toggles IsVisible
|
||||
@@ -86,11 +89,15 @@ public partial class Scenarios : UserControl
|
||||
if (scenery.Archival && !showArchival)
|
||||
continue;
|
||||
|
||||
bool expand = StarterNG.Classes.Settings.Instance.AutoExpandSceneryTree;
|
||||
SceneryI18n.LoadFor(scenery, App.Loc.CurrentLangCode);
|
||||
string label = scenery.DisplayName;
|
||||
|
||||
if (string.IsNullOrEmpty(scenery.Group))
|
||||
{
|
||||
topLevel.Add(new TreeViewItem
|
||||
{
|
||||
Header = Path.GetFileNameWithoutExtension(scenery.Path),
|
||||
Header = label,
|
||||
Tag = i
|
||||
});
|
||||
continue;
|
||||
@@ -98,14 +105,18 @@ public partial class Scenarios : UserControl
|
||||
|
||||
if (!groupNodes.TryGetValue(scenery.Group, out var groupNode))
|
||||
{
|
||||
groupNode = new TreeViewItem { Header = scenery.Group, IsExpanded = false };
|
||||
groupNode = new TreeViewItem
|
||||
{
|
||||
Header = SceneryI18n.T(scenery.Group),
|
||||
IsExpanded = expand
|
||||
};
|
||||
groupNodes[scenery.Group] = groupNode;
|
||||
topLevel.Add(groupNode);
|
||||
}
|
||||
|
||||
groupNode.Items.Add(new TreeViewItem
|
||||
{
|
||||
Header = Path.GetFileNameWithoutExtension(scenery.Path),
|
||||
Header = label,
|
||||
Tag = i
|
||||
});
|
||||
}
|
||||
@@ -124,9 +135,102 @@ public partial class Scenarios : UserControl
|
||||
sceneryList.Items.Add(item);
|
||||
}
|
||||
|
||||
// Restores the scenery selected last session (starter.last.scenery).
|
||||
// Like Pascal ScenariosList: fall back to the first leaf when nothing matches.
|
||||
private void RestoreLastScenery()
|
||||
{
|
||||
string last = StarterNG.Classes.Settings.Instance.LastScenery;
|
||||
string want = Path.GetFileNameWithoutExtension(last);
|
||||
|
||||
TreeViewItem? MatchLeaf(TreeViewItem node)
|
||||
{
|
||||
if (node.Tag is not int idx || idx < 0 || idx >= Sceneries.Count)
|
||||
return null;
|
||||
var scn = Sceneries[idx];
|
||||
if (string.IsNullOrWhiteSpace(want)) return null;
|
||||
if (string.Equals(scn.DisplayName, want, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(Path.GetFileNameWithoutExtension(scn.Path), want, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(node.Header as string, want, StringComparison.OrdinalIgnoreCase))
|
||||
return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
TreeViewItem? firstLeaf = null;
|
||||
foreach (var obj in sceneryList.Items)
|
||||
{
|
||||
if (obj is not TreeViewItem node)
|
||||
continue;
|
||||
|
||||
if (node.Tag is int)
|
||||
{
|
||||
firstLeaf ??= node;
|
||||
if (MatchLeaf(node) is { } hit)
|
||||
{
|
||||
ExpandAncestors(hit);
|
||||
sceneryList.SelectedItem = hit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var childObj in node.Items)
|
||||
{
|
||||
if (childObj is not TreeViewItem { Tag: int } child)
|
||||
continue;
|
||||
firstLeaf ??= child;
|
||||
if (MatchLeaf(child) is { } hit)
|
||||
{
|
||||
node.IsExpanded = true;
|
||||
sceneryList.SelectedItem = hit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstLeaf != null)
|
||||
{
|
||||
if (firstLeaf.Parent is TreeViewItem parent)
|
||||
parent.IsExpanded = true;
|
||||
sceneryList.SelectedItem = firstLeaf;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExpandAncestors(TreeViewItem node)
|
||||
{
|
||||
var parent = node.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent is TreeViewItem tvi)
|
||||
tvi.IsExpanded = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void PersistLastScenery(Scenery scenery)
|
||||
{
|
||||
// Pascal InitSCN stores the tree node text (//$n or file name).
|
||||
string name = scenery.DisplayName;
|
||||
if (string.Equals(StarterNG.Classes.Settings.Instance.LastScenery, name, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
StarterNG.Classes.Settings.Instance.LastScenery = name;
|
||||
StarterNG.Classes.Settings.Instance.Save();
|
||||
}
|
||||
|
||||
private static string TrainsetListLabel(Trainset trainset) =>
|
||||
TrainsetDisplay.CompositionText(trainset)
|
||||
?? string.Format(App.Loc["EmptyTrainset"], trainset.Track ?? "");
|
||||
|
||||
private static Control TrainsetListContent(Trainset trainset) =>
|
||||
new Avalonia.Controls.TextBlock
|
||||
{
|
||||
Text = TrainsetListLabel(trainset),
|
||||
TextTrimming = Avalonia.Media.TextTrimming.CharacterEllipsis,
|
||||
TextWrapping = Avalonia.Media.TextWrapping.NoWrap
|
||||
};
|
||||
|
||||
// Fills the Vehicles list for a scenery, honouring the two filters:
|
||||
// - "AI vehicles" off hides consists whose //$o description starts with '-';
|
||||
// - "Drivable only" on hides decoration consists (the //$decor flag).
|
||||
// Empty trainsets stay visible (label shows track), matching the Pascal starter.
|
||||
private void PopulateVehicleList(Scenery scn)
|
||||
{
|
||||
vehicleList.Items.Clear();
|
||||
@@ -137,15 +241,13 @@ public partial class Scenarios : UserControl
|
||||
for (int i = 0; i < scn.Trainsets.Count; i++)
|
||||
{
|
||||
var trainset = scn.Trainsets[i];
|
||||
string trainsetName = string.Join(" + ", trainset.Vehicles.Select(dyn => dyn.Name));
|
||||
if (string.IsNullOrWhiteSpace(trainsetName)) continue; // skip empty
|
||||
|
||||
if (IsAiTrainset(trainset) && !showAi) continue;
|
||||
if (trainset.Decor && drivableOnly) continue;
|
||||
|
||||
var listItem = new ListBoxItem
|
||||
{
|
||||
Content = trainsetName,
|
||||
Content = TrainsetListContent(trainset),
|
||||
Tag = i
|
||||
};
|
||||
// right-click: save/load to the warehouse + clipboard copy/paste
|
||||
@@ -188,6 +290,7 @@ public partial class Scenarios : UserControl
|
||||
StarterNG.Classes.Settings.Instance.Save();
|
||||
|
||||
BuildSceneryTree();
|
||||
RestoreLastScenery();
|
||||
}
|
||||
|
||||
// Replaces a consist's vehicles (from a loaded warehouse preset or a pasted
|
||||
@@ -195,7 +298,7 @@ public partial class Scenarios : UserControl
|
||||
private void ApplyConsist(ListBoxItem item, Trainset trainset, List<Dynamic> vehicles)
|
||||
{
|
||||
trainset.Vehicles = vehicles;
|
||||
item.Content = string.Join(" + ", trainset.Vehicles.Select(d => d.Name));
|
||||
item.Content = TrainsetListContent(trainset);
|
||||
if (ReferenceEquals(AppState.Instance.CurrentTrainset, trainset))
|
||||
ShowConsist(trainset);
|
||||
}
|
||||
@@ -212,7 +315,7 @@ public partial class Scenarios : UserControl
|
||||
foreach (var obj in vehicleList.Items)
|
||||
{
|
||||
if (obj is ListBoxItem { Tag: int vi } lbi && vi < scn.Trainsets.Count)
|
||||
lbi.Content = string.Join(" + ", scn.Trainsets[vi].Vehicles.Select(d => d.Name));
|
||||
lbi.Content = TrainsetListContent(scn.Trainsets[vi]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +341,7 @@ public partial class Scenarios : UserControl
|
||||
// scenery-level info (distinct from the per-consist scenario description)
|
||||
ShowSceneryInfo(selectedScn);
|
||||
ShowWeather(selectedScn);
|
||||
PersistLastScenery(selectedScn);
|
||||
|
||||
// no consist chosen yet for this scenery
|
||||
missionDescription.Text = "";
|
||||
@@ -250,7 +354,8 @@ public partial class Scenarios : UserControl
|
||||
// Shows the selected scenery's description (//$d) and 1:1 image (//$i).
|
||||
private void ShowSceneryInfo(Scenery scenery)
|
||||
{
|
||||
sceneryDescription.Text = scenery.Description;
|
||||
SceneryI18n.LoadFor(scenery, App.Loc.CurrentLangCode);
|
||||
sceneryDescription.Text = scenery.LocalizedDescription;
|
||||
|
||||
string? imagePath = scenery.ImagePath;
|
||||
if (imagePath is not null)
|
||||
@@ -261,13 +366,154 @@ public partial class Scenarios : UserControl
|
||||
}
|
||||
catch
|
||||
{
|
||||
sceneryImage.Source = null; // unreadable / unsupported image
|
||||
sceneryImage.Source = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneryImage.Source = null;
|
||||
}
|
||||
|
||||
PopulateAttachments(scenery);
|
||||
PopulateIncludes(scenery);
|
||||
PopulateFaults(scenery);
|
||||
}
|
||||
|
||||
private void PopulateFaults(Scenery scenery)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
lines.AddRange(scenery.Faults);
|
||||
|
||||
var db = GameData.Instance.Vehicles;
|
||||
foreach (var ts in scenery.Trainsets)
|
||||
{
|
||||
if (!ts.Parsed)
|
||||
lines.Add($"# unparsed trainset: {ts.Name}");
|
||||
foreach (var v in ts.Vehicles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(v.SkinFile)) continue;
|
||||
if (db.TextureForSkin(v.SkinFile) is null)
|
||||
lines.Add($"# unknown texture: {v.Name} ({v.SkinFile})");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var v in scenery.LooseVehicles)
|
||||
{
|
||||
if (db.TextureForSkin(v.SkinFile) is null)
|
||||
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);
|
||||
}
|
||||
|
||||
private void PopulateAttachments(Scenery scenery)
|
||||
{
|
||||
attachmentsPanel.Children.Clear();
|
||||
foreach (var att in scenery.Attachments)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Content = att.Label,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
HorizontalContentAlignment = HorizontalAlignment.Left,
|
||||
Cursor = new Avalonia.Input.Cursor(Avalonia.Input.StandardCursorType.Hand),
|
||||
Tag = att.FilePath
|
||||
};
|
||||
btn.Classes.Add("Flat");
|
||||
ToolTip.SetTip(btn, att.FilePath);
|
||||
btn.Click += (_, _) => OpenAttachment(att.FilePath);
|
||||
attachmentsPanel.Children.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (!File.Exists(full))
|
||||
full = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath));
|
||||
if (!File.Exists(full))
|
||||
return;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(full) { UseShellExecute = true });
|
||||
}
|
||||
catch { /* no handler */ }
|
||||
}
|
||||
|
||||
/// <summary>Rebuild tree labels after UI / scenery i18n language change.</summary>
|
||||
public void RebuildAfterLanguageChange()
|
||||
{
|
||||
string? selected = AppState.Instance.CurrentScenery?.Path;
|
||||
BuildSceneryTree();
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
StarterNG.Classes.Settings.Instance.LastScenery =
|
||||
Path.GetFileNameWithoutExtension(selected);
|
||||
RestoreLastScenery();
|
||||
}
|
||||
if (AppState.Instance.CurrentScenery != null)
|
||||
ShowSceneryInfo(AppState.Instance.CurrentScenery);
|
||||
}
|
||||
|
||||
private void ReloadSceneries_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
string? selected = AppState.Instance.CurrentScenery?.Path;
|
||||
GameData.Instance.ReloadSceneries();
|
||||
Sceneries = GameData.Instance.Sceneries;
|
||||
BuildSceneryTree();
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
StarterNG.Classes.Settings.Instance.LastScenery =
|
||||
Path.GetFileNameWithoutExtension(selected);
|
||||
}
|
||||
RestoreLastScenery();
|
||||
}
|
||||
|
||||
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;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(dir) { UseShellExecute = true });
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
@@ -287,6 +533,7 @@ public partial class Scenarios : UserControl
|
||||
// share the selection so the depot edits this consist in place
|
||||
AppState.Instance.CurrentScenery = selectedScn;
|
||||
AppState.Instance.CurrentTrainset = selectedTrainset;
|
||||
AppState.Instance.StartingVehicleName = TrainsetDisplay.DefaultStartingVehicle(selectedTrainset);
|
||||
|
||||
ShowConsist(selectedTrainset);
|
||||
ShowTimetable(selectedScn, selectedTrainset);
|
||||
@@ -302,7 +549,7 @@ public partial class Scenarios : UserControl
|
||||
try
|
||||
{
|
||||
weatherForm.IsEnabled = true;
|
||||
weatherTime.SelectedTime = ParseTime(scenery.WeatherTime);
|
||||
weatherTime.SelectedTime = ParseTime(scenery.ScenarioTimeOverride);
|
||||
weatherDay.Value = scenery.Day;
|
||||
weatherTemp.Value = scenery.Temperature;
|
||||
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 0, 10000);
|
||||
@@ -326,7 +573,7 @@ public partial class Scenarios : UserControl
|
||||
|
||||
var scn = _currentScenery;
|
||||
if (weatherTime.SelectedTime is { } t)
|
||||
scn.WeatherTime = $"{t.Hours:D2}:{t.Minutes:D2}";
|
||||
scn.ScenarioTimeOverride = $"{t.Hours:D2}:{t.Minutes:D2}";
|
||||
// In "Today" mode the day field is only a preview, so keep the stored day
|
||||
// at 0 (the scenery then follows the live date).
|
||||
scn.Day = _todaySeason ? 0 : (int)(weatherDay.Value ?? 0);
|
||||
@@ -368,6 +615,14 @@ public partial class Scenarios : UserControl
|
||||
weatherTime.SelectedTime = new TimeSpan(now.Hour, now.Minute, 0);
|
||||
}
|
||||
|
||||
// Pascal actRestoreWeather — reload weather fields from the SCN snapshot.
|
||||
private void WeatherRestore_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_currentScenery is null) return;
|
||||
_currentScenery.RestoreWeather();
|
||||
ShowWeather(_currentScenery);
|
||||
}
|
||||
|
||||
// Parses a stored "hh:mm" string into a TimeSpan, or null when it is missing
|
||||
// or malformed (so the picker simply shows no selection).
|
||||
private static TimeSpan? ParseTime(string? text)
|
||||
@@ -484,15 +739,38 @@ public partial class Scenarios : UserControl
|
||||
if (path is null)
|
||||
continue; // fallback: no mini
|
||||
|
||||
int thumbH = StarterNG.Classes.Settings.Instance.LargeThumbnails ? 64 : 32;
|
||||
consistStack.Children.Add(new Image
|
||||
{
|
||||
Source = new Bitmap(path),
|
||||
Height = 32,
|
||||
Height = thumbH,
|
||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
||||
Margin = new Thickness(0)
|
||||
});
|
||||
}
|
||||
missionDescription.Text = trainset.Description;
|
||||
string desc = trainset.Description ?? "";
|
||||
if (desc.TrimStart().StartsWith('-'))
|
||||
desc = desc.TrimStart()[1..].TrimStart();
|
||||
missionDescription.Text = SceneryI18n.T(desc);
|
||||
UpdateTrainStats(trainset);
|
||||
}
|
||||
|
||||
private void UpdateTrainStats(Trainset? trainset)
|
||||
{
|
||||
if (trainset is null)
|
||||
{
|
||||
trainStats.Text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
var db = GameData.Instance.Vehicles;
|
||||
trainStats.Text = TrainsetDisplay.FormatStats(
|
||||
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);
|
||||
}
|
||||
|
||||
// When returning to this view, refresh the preview of the selected consist
|
||||
|
||||
Reference in New Issue
Block a user