mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-07-17 17:09:19 +02:00
Fix loads, some UI improvements
This commit is contained in:
@@ -29,13 +29,25 @@
|
|||||||
|
|
||||||
<!-- Row 0: title / caption strip. Transparent background makes the
|
<!-- Row 0: title / caption strip. Transparent background makes the
|
||||||
whole strip a drag handle (PointerPressed -> BeginMoveDrag),
|
whole strip a drag handle (PointerPressed -> BeginMoveDrag),
|
||||||
and the OS buttons render at its top-right. -->
|
and the OS buttons render at its top-right. The log button sits
|
||||||
<Border Grid.Row="0" Background="Transparent"
|
on top of the strip, just left of the OS min/max/close buttons. -->
|
||||||
PointerPressed="TitleBar_OnPointerPressed">
|
<StackPanel Orientation="Horizontal" Spacing="16" Grid.Row="0">
|
||||||
<TextBlock Text="Starter MaSzyna" VerticalAlignment="Center"
|
<Border Background="Transparent"
|
||||||
Margin="14,0,0,0" FontSize="11"
|
PointerPressed="TitleBar_OnPointerPressed">
|
||||||
Foreground="{StaticResource CarbonFgDim}" />
|
<TextBlock Text="Starter MaSzyna" VerticalAlignment="Center"
|
||||||
</Border>
|
Margin="14,0,0,0" FontSize="11"
|
||||||
|
Foreground="{StaticResource CarbonFgDim}" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Reserve ~140px on the right for the OS caption buttons so the
|
||||||
|
log button lands immediately to their left. -->
|
||||||
|
<Button Classes="Basic" Click="openLastLogClick"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||||
|
Margin="0,0,0,0" Padding="10,0" Cursor="Hand"
|
||||||
|
ToolTip.Tip="{Binding [ShowLatestLogTooltip], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<materialIcons:MaterialIcon Kind="FileDocument" Height="16" Width="16" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Row 1: logo + navigation (left) and the right cluster (right) -->
|
<!-- Row 1: logo + navigation (left) and the right cluster (right) -->
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*,Auto">
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,*,Auto">
|
||||||
@@ -65,7 +77,7 @@
|
|||||||
HorizontalContentAlignment="Center" />
|
HorizontalContentAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Right cluster: version, language, log, social links.
|
<!-- Right cluster: version, language, social links.
|
||||||
In the toolbar row, so it sits below the OS caption buttons. -->
|
In the toolbar row, so it sits below the OS caption buttons. -->
|
||||||
<StackPanel Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
|
<StackPanel Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
Margin="0,0,8,0" Spacing="4">
|
Margin="0,0,8,0" Spacing="4">
|
||||||
@@ -82,10 +94,6 @@
|
|||||||
<ComboBoxItem>English</ComboBoxItem>
|
<ComboBoxItem>English</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
||||||
<Button Classes="Basic" Click="openLastLogClick"
|
|
||||||
ToolTip.Tip="{Binding [ShowLatestLogTooltip], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<materialIcons:MaterialIcon Kind="FileDocument" />
|
|
||||||
</Button>
|
|
||||||
<Button Classes="Basic" Cursor="Hand" Tag="http://eu07.pl/" Click="linkClick">
|
<Button Classes="Basic" Cursor="Hand" Tag="http://eu07.pl/" Click="linkClick">
|
||||||
<materialIcons:MaterialIcon Kind="Web" />
|
<materialIcons:MaterialIcon Kind="Web" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -181,8 +181,12 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- ── Compact list (tight rows that fill the available space) ─────────── -->
|
<!-- ── Compact list (tight rows that fill the available space) ─────────── -->
|
||||||
|
<!-- Transparent surface so the list blends into the GroupBox panel instead
|
||||||
|
of showing the Fluent theme's default light-grey ListBox background. -->
|
||||||
<Style Selector="ListBox.Compact">
|
<Style Selector="ListBox.Compact">
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="ListBox.Compact ListBoxItem">
|
<Style Selector="ListBox.Compact ListBoxItem">
|
||||||
<Setter Property="Padding" Value="6,2" />
|
<Setter Property="Padding" Value="6,2" />
|
||||||
|
|||||||
@@ -27,9 +27,11 @@
|
|||||||
<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}}"
|
||||||
|
PointerWheelChanged="Combo_OnPointerWheel"
|
||||||
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
SelectionChanged="CategoryCombo_OnSelectionChanged" />
|
||||||
<ComboBox Name="classCombo" Grid.Column="1" HorizontalAlignment="Stretch"
|
<ComboBox Name="classCombo" Grid.Column="1" HorizontalAlignment="Stretch"
|
||||||
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
PlaceholderText="{Binding [Class], Source={x:Static starterNg:App.Loc}}"
|
||||||
|
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,8"
|
||||||
|
|||||||
@@ -337,6 +337,30 @@ public partial class Depot : UserControl
|
|||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hovering over the Type/Class combos and scrolling cycles the selection
|
||||||
|
// without having to open the dropdown (scroll up = previous, down = next).
|
||||||
|
// When the dropdown is open the wheel scrolls the list as usual.
|
||||||
|
private void Combo_OnPointerWheel(object? sender, PointerWheelEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ComboBox combo || combo.IsDropDownOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int count = combo.ItemCount;
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = combo.SelectedIndex;
|
||||||
|
if (e.Delta.Y > 0) // scroll up -> previous
|
||||||
|
index = index <= 0 ? 0 : index - 1;
|
||||||
|
else if (e.Delta.Y < 0) // scroll down -> next
|
||||||
|
index = index < 0 ? 0 : Math.Min(index + 1, count - 1);
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
combo.SelectedIndex = index;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------- 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
|
||||||
@@ -1270,20 +1294,33 @@ public partial class Depot : UserControl
|
|||||||
var phys = PhysicsFor(lead);
|
var phys = PhysicsFor(lead);
|
||||||
|
|
||||||
// Prefer the cargo this vehicle actually accepts (.fiz LoadAccepted);
|
// Prefer the cargo this vehicle actually accepts (.fiz LoadAccepted);
|
||||||
// otherwise suggest everything from data/load_weights.txt. Free text is
|
// otherwise offer everything from data/load_weights.txt.
|
||||||
// still allowed.
|
var available = phys != null && !string.IsNullOrWhiteSpace(phys.LoadAccepted)
|
||||||
var accepted = phys != null && !string.IsNullOrWhiteSpace(phys.LoadAccepted)
|
|
||||||
? phys.LoadAccepted.Split(',', ';').Select(s => s.Trim()).Where(s => s.Length > 0).ToList()
|
? phys.LoadAccepted.Split(',', ';').Select(s => s.Trim()).Where(s => s.Length > 0).ToList()
|
||||||
: null;
|
: LoadTypes().ToList();
|
||||||
|
|
||||||
var typeBox = new AutoCompleteBox
|
// Keep a load already set on the vehicle selectable even when it is not in
|
||||||
{
|
// the accepted/known list (e.g. a custom load read from the scenery).
|
||||||
FontSize = 12,
|
if (!string.IsNullOrEmpty(lead.LoadType) &&
|
||||||
MinWidth = 200,
|
!available.Contains(lead.LoadType!, StringComparer.OrdinalIgnoreCase))
|
||||||
Text = lead.LoadType ?? "",
|
available.Insert(0, lead.LoadType!);
|
||||||
ItemsSource = accepted ?? (IEnumerable<string>)LoadTypes(),
|
|
||||||
FilterMode = AutoCompleteFilterMode.ContainsOrdinal
|
// Dropdown of available cargo: the leading "(none)" entry clears the load.
|
||||||
};
|
var typeCombo = new ComboBox { FontSize = 12, MinWidth = 200 };
|
||||||
|
typeCombo.Items.Add(new ComboBoxItem { Content = App.Loc["None"], Tag = null });
|
||||||
|
foreach (var name in available)
|
||||||
|
typeCombo.Items.Add(new ComboBoxItem { Content = name, Tag = name });
|
||||||
|
|
||||||
|
// Pre-select the vehicle's current load (or "(none)").
|
||||||
|
typeCombo.SelectedIndex = 0;
|
||||||
|
if (!string.IsNullOrEmpty(lead.LoadType))
|
||||||
|
foreach (var obj in typeCombo.Items)
|
||||||
|
if (obj is ComboBoxItem ci && ci.Tag is string t &&
|
||||||
|
string.Equals(t, lead.LoadType, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
typeCombo.SelectedItem = ci;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Cap the amount at the .fiz MaxLoad when it is defined.
|
// Cap the amount at the .fiz MaxLoad when it is defined.
|
||||||
int maxLoad = phys != null && phys.MaxLoad > 0 ? phys.MaxLoad : 1000;
|
int maxLoad = phys != null && phys.MaxLoad > 0 ? phys.MaxLoad : 1000;
|
||||||
@@ -1295,22 +1332,25 @@ public partial class Depot : UserControl
|
|||||||
|
|
||||||
void Apply()
|
void Apply()
|
||||||
{
|
{
|
||||||
string type = typeBox.Text?.Trim() ?? "";
|
string? type = (typeCombo.SelectedItem as ComboBoxItem)?.Tag as string;
|
||||||
int count = (int)(countBox.Value ?? 0);
|
int count = (int)(countBox.Value ?? 0);
|
||||||
foreach (var c in item.Cars)
|
foreach (var c in item.Cars)
|
||||||
{
|
{
|
||||||
c.LoadCount = count;
|
// "(none)" -> no cargo at all; otherwise store the chosen type/amount.
|
||||||
c.LoadType = count > 0 && !string.IsNullOrEmpty(type) ? type : c.LoadType;
|
c.LoadType = string.IsNullOrEmpty(type) ? null : type;
|
||||||
if (count > 0)
|
c.LoadCount = string.IsNullOrEmpty(type) ? 0 : count;
|
||||||
|
if (c.LoadCount > 0)
|
||||||
c.HasVelocity = true; // velocity token must precede loadcount
|
c.HasVelocity = true; // velocity token must precede loadcount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeBox.LostFocus += (_, _) => Apply();
|
// Wire the handlers only after the initial selection so we don't apply
|
||||||
|
// (and mark the consist dirty) while merely populating the editor.
|
||||||
|
typeCombo.SelectionChanged += (_, _) => Apply();
|
||||||
countBox.ValueChanged += (_, _) => Apply();
|
countBox.ValueChanged += (_, _) => Apply();
|
||||||
|
|
||||||
loadsPanel.Children.Add(UnitTitle(item));
|
loadsPanel.Children.Add(UnitTitle(item));
|
||||||
loadsPanel.Children.Add(LabeledRow(App.Loc["LoadType"], typeBox));
|
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
|
loadsPanel.Children.Add(new TextBlock
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,9 +59,15 @@
|
|||||||
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
|
||||||
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [Time], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<TextBox Grid.Row="0" Grid.Column="1" Name="weatherTime" Width="90"
|
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
||||||
HorizontalAlignment="Left" Watermark="hh:mm"
|
<TimePicker Name="weatherTime" ClockIdentifier="24HourClock"
|
||||||
LostFocus="Weather_OnChanged" />
|
MinuteIncrement="1" VerticalAlignment="Center"
|
||||||
|
SelectedTimeChanged="WeatherTime_OnChanged" />
|
||||||
|
<Button Name="weatherTimeNow" Classes="Flat" Cursor="Hand"
|
||||||
|
VerticalAlignment="Center" Padding="10,4"
|
||||||
|
Click="WeatherTimeNow_OnClick"
|
||||||
|
Content="{Binding [CurrentTime], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
|
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
|
||||||
Text="{Binding [Season], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [Season], Source={x:Static starterNg:App.Loc}}" />
|
||||||
@@ -94,8 +100,8 @@
|
|||||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
|
||||||
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
Text="{Binding [Visibility], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
||||||
<Slider Name="weatherFog" Minimum="50" Maximum="2500" Width="220"
|
<Slider Name="weatherFog" Minimum="0" Maximum="10000" Width="220"
|
||||||
TickFrequency="50" IsSnapToTickEnabled="True"
|
TickFrequency="250" IsSnapToTickEnabled="True"
|
||||||
ValueChanged="Weather_OnSliderChanged" />
|
ValueChanged="Weather_OnSliderChanged" />
|
||||||
<TextBlock Name="weatherFogLabel" VerticalAlignment="Center" MinWidth="56" />
|
<TextBlock Name="weatherFogLabel" VerticalAlignment="Center" MinWidth="56" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
@@ -21,6 +23,9 @@ public partial class Scenarios : UserControl
|
|||||||
private Scenery? _currentScenery;
|
private Scenery? _currentScenery;
|
||||||
// suppresses weather change handlers while the form is being populated
|
// suppresses weather change handlers while the form is being populated
|
||||||
private bool _loadingWeather;
|
private bool _loadingWeather;
|
||||||
|
// "Today" season: the day field only previews the current day-of-year; the
|
||||||
|
// stored Day stays 0 so the scenery keeps using the live date.
|
||||||
|
private bool _todaySeason;
|
||||||
|
|
||||||
public Scenarios()
|
public Scenarios()
|
||||||
{
|
{
|
||||||
@@ -30,6 +35,7 @@ public partial class Scenarios : UserControl
|
|||||||
Sceneries = GameData.Instance.Sceneries;
|
Sceneries = GameData.Instance.Sceneries;
|
||||||
|
|
||||||
var groupNodes = new Dictionary<string, TreeViewItem>();
|
var groupNodes = new Dictionary<string, TreeViewItem>();
|
||||||
|
var topLevel = new List<TreeViewItem>();
|
||||||
|
|
||||||
for (int i = 0; i < Sceneries.Count; i++)
|
for (int i = 0; i < Sceneries.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -38,7 +44,7 @@ public partial class Scenarios : UserControl
|
|||||||
// case 1: no group - put without parent
|
// case 1: no group - put without parent
|
||||||
if (string.IsNullOrEmpty(scenery.Group))
|
if (string.IsNullOrEmpty(scenery.Group))
|
||||||
{
|
{
|
||||||
sceneryList.Items.Add(new TreeViewItem
|
topLevel.Add(new TreeViewItem
|
||||||
{
|
{
|
||||||
Header = Path.GetFileNameWithoutExtension(scenery.Path),
|
Header = Path.GetFileNameWithoutExtension(scenery.Path),
|
||||||
Tag = i
|
Tag = i
|
||||||
@@ -57,7 +63,7 @@ public partial class Scenarios : UserControl
|
|||||||
};
|
};
|
||||||
|
|
||||||
groupNodes[scenery.Group] = groupNode;
|
groupNodes[scenery.Group] = groupNode;
|
||||||
sceneryList.Items.Add(groupNode);
|
topLevel.Add(groupNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
groupNode.Items.Add(new TreeViewItem
|
groupNode.Items.Add(new TreeViewItem
|
||||||
@@ -67,17 +73,59 @@ public partial class Scenarios : UserControl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the scenery tree alphabetically: group folders' inner sceneries
|
||||||
|
// first, then the top-level (group folders + ungrouped sceneries). The Tag
|
||||||
|
// keeps each node's original scenery index, so sorting only reorders the
|
||||||
|
// display - the vehicles/trainsets list stays in scenery order.
|
||||||
|
foreach (var node in groupNodes.Values)
|
||||||
|
{
|
||||||
|
var children = node.Items.Cast<TreeViewItem>()
|
||||||
|
.OrderBy(c => c.Header as string, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
node.Items.Clear();
|
||||||
|
foreach (var child in children)
|
||||||
|
node.Items.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in topLevel.OrderBy(t => t.Header as string, StringComparer.OrdinalIgnoreCase))
|
||||||
|
sceneryList.Items.Add(item);
|
||||||
|
|
||||||
// refresh the consist preview when the view is shown again (e.g. after
|
// refresh the consist preview when the view is shown again (e.g. after
|
||||||
// editing the consist in the depot). Switching tabs only toggles IsVisible
|
// editing the consist in the depot). Switching tabs only toggles IsVisible
|
||||||
// (the view stays in the tree), so listen for that too.
|
// (the view stays in the tree), so listen for that too.
|
||||||
AttachedToVisualTree += (_, _) => RefreshSelectedConsist();
|
AttachedToVisualTree += (_, _) => OnReentered();
|
||||||
PropertyChanged += (_, e) =>
|
PropertyChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
if (e.Property == IsVisibleProperty && IsVisible)
|
if (e.Property == IsVisibleProperty && IsVisible)
|
||||||
RefreshSelectedConsist();
|
OnReentered();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called whenever this view is shown again (tab switch / re-attach). Edits made
|
||||||
|
// in the depot mutate the same Trainset objects, so refresh both the consist
|
||||||
|
// preview and the vehicle-list captions that depend on them.
|
||||||
|
private void OnReentered()
|
||||||
|
{
|
||||||
|
RefreshVehicleLabels();
|
||||||
|
RefreshSelectedConsist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuilds the caption of each entry in the Vehicles list from its trainset's
|
||||||
|
// current vehicles, so a consist edited in the depot is reflected here too.
|
||||||
|
// Selection and order are preserved (only the text is updated).
|
||||||
|
private void RefreshVehicleLabels()
|
||||||
|
{
|
||||||
|
if (sceneryList.SelectedItem is not TreeViewItem { Tag: int tag } || tag >= Sceneries.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var scn = Sceneries[tag];
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SceneryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void SceneryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var item = sceneryList.SelectedItem as TreeViewItem;
|
var item = sceneryList.SelectedItem as TreeViewItem;
|
||||||
@@ -183,12 +231,14 @@ public partial class Scenarios : UserControl
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
weatherForm.IsEnabled = true;
|
weatherForm.IsEnabled = true;
|
||||||
weatherTime.Text = scenery.WeatherTime;
|
weatherTime.SelectedTime = ParseTime(scenery.WeatherTime);
|
||||||
weatherDay.Value = scenery.Day;
|
weatherDay.Value = scenery.Day;
|
||||||
weatherTemp.Value = scenery.Temperature;
|
weatherTemp.Value = scenery.Temperature;
|
||||||
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 50, 2500);
|
weatherFog.Value = System.Math.Clamp(scenery.FogEnd, 0, 10000);
|
||||||
SelectByTag(weatherSeason, scenery.Day.ToString());
|
SelectByTag(weatherSeason, scenery.Day.ToString());
|
||||||
SelectByTag(weatherOvercast, scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
SelectByTag(weatherOvercast, scenery.Overcast.ToString(CultureInfo.InvariantCulture));
|
||||||
|
// Day 0 maps to the "Today" season entry: preview the live day-of-year.
|
||||||
|
ApplyTodayPreview(scenery.Day == 0);
|
||||||
UpdateWeatherLabels();
|
UpdateWeatherLabels();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -204,9 +254,11 @@ public partial class Scenarios : UserControl
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var scn = _currentScenery;
|
var scn = _currentScenery;
|
||||||
if (!string.IsNullOrWhiteSpace(weatherTime.Text))
|
if (weatherTime.SelectedTime is { } t)
|
||||||
scn.WeatherTime = weatherTime.Text!.Trim();
|
scn.WeatherTime = $"{t.Hours:D2}:{t.Minutes:D2}";
|
||||||
scn.Day = (int)(weatherDay.Value ?? 0);
|
// 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);
|
||||||
scn.Temperature = weatherTemp.Value;
|
scn.Temperature = weatherTemp.Value;
|
||||||
scn.FogEnd = (int)weatherFog.Value;
|
scn.FogEnd = (int)weatherFog.Value;
|
||||||
if ((weatherOvercast.SelectedItem as ComboBoxItem)?.Tag is string ov &&
|
if ((weatherOvercast.SelectedItem as ComboBoxItem)?.Tag is string ov &&
|
||||||
@@ -235,7 +287,44 @@ public partial class Scenarios : UserControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- weather form event handlers ---
|
// --- weather form event handlers ---
|
||||||
private void Weather_OnChanged(object? sender, RoutedEventArgs e) => CaptureWeather();
|
private void WeatherTime_OnChanged(object? sender, TimePickerSelectedValueChangedEventArgs e) => CaptureWeather();
|
||||||
|
|
||||||
|
// "Current time" button: drop the live wall-clock time into the picker (the
|
||||||
|
// SelectedTimeChanged handler then captures it onto the scenery).
|
||||||
|
private void WeatherTimeNow_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
weatherTime.SelectedTime = new TimeSpan(now.Hour, now.Minute, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
return null;
|
||||||
|
var m = Regex.Match(text, @"^\s*(\d{1,2})[:.](\d{2})");
|
||||||
|
if (m.Success &&
|
||||||
|
int.TryParse(m.Groups[1].Value, out int h) && h is >= 0 and <= 23 &&
|
||||||
|
int.TryParse(m.Groups[2].Value, out int min) && min is >= 0 and <= 59)
|
||||||
|
return new TimeSpan(h, min, 0);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggles "Today" preview: the day field shows the live day-of-year (read-only)
|
||||||
|
// and CaptureWeather keeps the stored Day at 0; any other season re-enables it.
|
||||||
|
private void ApplyTodayPreview(bool today)
|
||||||
|
{
|
||||||
|
_todaySeason = today;
|
||||||
|
weatherDay.IsEnabled = !today;
|
||||||
|
if (today)
|
||||||
|
{
|
||||||
|
bool prev = _loadingWeather;
|
||||||
|
_loadingWeather = true;
|
||||||
|
weatherDay.Value = DateTime.Now.DayOfYear;
|
||||||
|
_loadingWeather = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Weather_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) => CaptureWeather();
|
private void Weather_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) => CaptureWeather();
|
||||||
|
|
||||||
@@ -251,9 +340,17 @@ public partial class Scenarios : UserControl
|
|||||||
if ((weatherSeason.SelectedItem as ComboBoxItem)?.Tag is string tag &&
|
if ((weatherSeason.SelectedItem as ComboBoxItem)?.Tag is string tag &&
|
||||||
int.TryParse(tag, out int day))
|
int.TryParse(tag, out int day))
|
||||||
{
|
{
|
||||||
_loadingWeather = true;
|
if (day == 0) // "Today" - preview the live day-of-year, keep stored day at 0
|
||||||
weatherDay.Value = day;
|
{
|
||||||
_loadingWeather = false;
|
ApplyTodayPreview(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApplyTodayPreview(false);
|
||||||
|
_loadingWeather = true;
|
||||||
|
weatherDay.Value = day;
|
||||||
|
_loadingWeather = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CaptureWeather();
|
CaptureWeather();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<String key="NoWeatherData">No weather data in the scenery.</String>
|
<String key="NoWeatherData">No weather data in the scenery.</String>
|
||||||
<String key="NoTimetable">No timetable for the selected consist.</String>
|
<String key="NoTimetable">No timetable for the selected consist.</String>
|
||||||
<String key="Time">Time</String>
|
<String key="Time">Time</String>
|
||||||
|
<String key="CurrentTime">Now</String>
|
||||||
<String key="Day">Day of year</String>
|
<String key="Day">Day of year</String>
|
||||||
<String key="Sky">Sky</String>
|
<String key="Sky">Sky</String>
|
||||||
<String key="Fog">Fog / visibility</String>
|
<String key="Fog">Fog / visibility</String>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<String key="NoWeatherData">Brak danych o pogodzie w scenerii.</String>
|
<String key="NoWeatherData">Brak danych o pogodzie w scenerii.</String>
|
||||||
<String key="NoTimetable">Brak rozkładu jazdy dla wybranego składu.</String>
|
<String key="NoTimetable">Brak rozkładu jazdy dla wybranego składu.</String>
|
||||||
<String key="Time">Godzina</String>
|
<String key="Time">Godzina</String>
|
||||||
|
<String key="CurrentTime">Czas aktualny</String>
|
||||||
<String key="Day">Dzień roku</String>
|
<String key="Day">Dzień roku</String>
|
||||||
<String key="Sky">Niebo</String>
|
<String key="Sky">Niebo</String>
|
||||||
<String key="Fog">Mgła / widoczność</String>
|
<String key="Fog">Mgła / widoczność</String>
|
||||||
|
|||||||
Reference in New Issue
Block a user