Refine editor inspector and drag painting

This commit is contained in:
2026-05-11 23:18:47 +02:00
parent dfe0cb3b6a
commit fbb7c0490c
3 changed files with 201 additions and 28 deletions

View File

@@ -93,22 +93,17 @@
</StackPanel>
</Grid>
<TextBlock Text="Global Systems" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock Text="Inventory And Objects" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="GlobalGrid">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#3C4650" BorderThickness="1" Padding="8" Margin="0,0,8,8"
Width="126" CornerRadius="3">
CornerRadius="3">
<StackPanel Spacing="2">
<TextBlock Text="{Binding Label}" Foreground="#9EA7AE" FontSize="11"
TextWrapping="Wrap" />
<TextBlock Text="{Binding Value}" Foreground="#F4F1E8" FontSize="15"
TextWrapping="WrapWholeWords" />
TextWrapping="Wrap" />
<TextBlock Text="{Binding Description}" Foreground="#9EA7AE" FontSize="11"
TextWrapping="Wrap" />
</StackPanel>
@@ -117,17 +112,36 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="CellGrid">
<TextBlock Text="Required" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="RequiredGrid">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="2" />
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="84" Margin="0,0,4,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Label}" Foreground="#9EA7AE" FontSize="11" />
<TextBlock Grid.Row="1" Text="{Binding Value}" Foreground="#F4F1E8"
FontSize="18" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock x:Name="SelectedCellTitleText" FontSize="19" FontWeight="SemiBold"
Foreground="#F4F1E8" TextWrapping="Wrap" />
<ItemsControl x:Name="CellGrid">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#3C4650" BorderThickness="1" Padding="8" Margin="0,0,8,8"
Width="126" CornerRadius="3">
CornerRadius="3">
<StackPanel Spacing="2">
<TextBlock Text="{Binding Label}" Foreground="#9EA7AE" FontSize="11"
TextWrapping="Wrap" />
@@ -141,6 +155,70 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Surface" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="SurfaceGrid">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="64" Margin="0,0,4,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Label}" Foreground="#9EA7AE" FontSize="11"
TextWrapping="NoWrap" />
<TextBlock Grid.Row="1" Text="{Binding Value}" Foreground="#F4F1E8"
FontSize="16" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Network" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="34" />
<ColumnDefinition Width="34" />
<ColumnDefinition Width="34" />
</Grid.ColumnDefinitions>
<TextBlock Text="Carrier" Foreground="#9EA7AE" FontSize="11" />
<TextBlock Grid.Column="1" Text="State" Foreground="#9EA7AE" FontSize="11" />
<TextBlock Grid.Column="2" Text="Amt" Foreground="#9EA7AE" FontSize="11" />
<TextBlock Grid.Column="3" Text="Int" Foreground="#9EA7AE" FontSize="11" />
<TextBlock Grid.Column="4" Text="HP" Foreground="#9EA7AE" FontSize="11" />
</Grid>
<ItemsControl x:Name="NetworkGrid">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,8" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="34" />
<ColumnDefinition Width="34" />
<ColumnDefinition Width="34" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Carrier}" Foreground="#F4F1E8" FontSize="12"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1" Text="{Binding State}" Foreground="#9EA7AE"
FontSize="12" TextWrapping="Wrap" />
<TextBlock Grid.Column="2" Text="{Binding Amount}" Foreground="#F4F1E8"
FontSize="12" />
<TextBlock Grid.Column="3" Text="{Binding Intensity}" Foreground="#F4F1E8"
FontSize="12" />
<TextBlock Grid.Column="4" Text="{Binding Integrity}" Foreground="#F4F1E8"
FontSize="12" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Forecasts" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="ForecastList">
<ItemsControl.ItemTemplate>

View File

@@ -45,6 +45,7 @@ public sealed partial class MainWindow
private sealed record ForecastViewModel(string Message);
private sealed record InspectorItemViewModel(string Label, string Value, string Description);
private sealed record NetworkInspectionViewModel(string Carrier, string State, string Amount, string Intensity, string Integrity);
private sealed class EditorToolViewModel(EditorToolCommand command, string label) : INotifyPropertyChanged
{
@@ -260,12 +261,13 @@ public sealed partial class MainWindow
m_DragExceededClickThreshold = false;
m_IsPanning = e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift);
m_CursorDragStartCell = null;
m_CursorDragStartRejected = false;
m_DragPreviewDestination = null;
m_InvalidDragCell = null;
m_EditorFeedback = string.Empty;
m_LastPaintedCell = null;
if (!m_IsPanning && m_SelectedTool.Tool == EEditorTool.Cursor && TryGetGridPosition(point.Position, out var position))
m_CursorDragStartCell = position;
StartCursorDrag(position);
else if (!m_IsPanning && IsDragPaintTool(m_SelectedTool.Tool) && TryGetGridPosition(point.Position, out position))
{
m_LastPaintedCell = position;
@@ -337,7 +339,7 @@ public sealed partial class MainWindow
RefreshInspector();
LevelCanvas.Invalidate();
}
else if (!m_DragExceededClickThreshold && !IsDragPaintTool(m_SelectedTool.Tool))
else if (!m_DragExceededClickThreshold && !m_CursorDragStartRejected && !IsDragPaintTool(m_SelectedTool.Tool))
{
SelectOrPaintAt(point.Position);
}
@@ -346,6 +348,7 @@ public sealed partial class MainWindow
m_LeftPointerDown = false;
m_IsPanning = false;
m_CursorDragStartCell = null;
m_CursorDragStartRejected = false;
m_DragPreviewDestination = null;
m_LastPaintedCell = null;
m_DragExceededClickThreshold = false;
@@ -358,6 +361,7 @@ public sealed partial class MainWindow
m_LeftPointerDown = false;
m_IsPanning = false;
m_CursorDragStartCell = null;
m_CursorDragStartRejected = false;
m_DragPreviewDestination = null;
m_LastPaintedCell = null;
m_DragExceededClickThreshold = false;
@@ -413,7 +417,25 @@ public sealed partial class MainWindow
private static bool IsDragPaintTool(EEditorTool tool)
{
return tool is EEditorTool.Floor or EEditorTool.Wall;
return tool is EEditorTool.Floor or EEditorTool.Wall or EEditorTool.Underground;
}
private void StartCursorDrag(GridPosition position)
{
m_SelectedCell = position;
if (HasMovableAt(position))
{
m_CursorDragStartCell = position;
RefreshInspector();
LevelCanvas.Invalidate();
return;
}
m_CursorDragStartRejected = true;
m_InvalidDragCell = position;
m_EditorFeedback = "No movable robot, prop, source, or leak starts here.";
RefreshInspector();
LevelCanvas.Invalidate();
}
private void ClearAt(Point point)
@@ -788,13 +810,29 @@ public sealed partial class MainWindow
GlobalGrid.ItemsSource = new[] {
new InspectorItemViewModel("Inventory", $"F {m_Level.Robot.FuelNeutralizers} / C {m_Level.Robot.CoolantNeutralizers} / E {m_Level.Robot.ElectricityNeutralizers} / H {m_Level.Robot.HeatShields}", "Remedies carried by the robot."),
new InspectorItemViewModel("Heat Shield", m_Level.Robot.HeatImmunitySteps.ToString(CultureInfo.InvariantCulture), "Remaining protected movement steps."),
new InspectorItemViewModel("Required F/C/E", $"{m_Level.RequiredFuelConsumers}/{m_Level.RequiredCoolantConsumers}/{m_Level.RequiredElectricityConsumers}", "Producing consumers needed for readiness."),
new InspectorItemViewModel("Objects", $"R {m_Level.Reactors.Count} / L {m_Level.Leaks.Count} / D {doorCount}", "Reactors, active leaks, and doors.")
};
RequiredGrid.ItemsSource = new[] {
new InspectorItemViewModel("Fuel", m_Level.RequiredFuelConsumers.ToString(CultureInfo.InvariantCulture), "Producing consumers needed for readiness."),
new InspectorItemViewModel("Coolant", m_Level.RequiredCoolantConsumers.ToString(CultureInfo.InvariantCulture), "Producing consumers needed for readiness."),
new InspectorItemViewModel("Electric", m_Level.RequiredElectricityConsumers.ToString(CultureInfo.InvariantCulture), "Producing consumers needed for readiness.")
};
if (m_SelectedCell is { } position && m_Level.InBounds(position))
{
SelectedCellTitleText.Text = SelectedCellTitle(position);
CellGrid.ItemsSource = CellInspectionItems(position);
SurfaceGrid.ItemsSource = SurfaceInspectionItems(position);
NetworkGrid.ItemsSource = NetworkInspectionItems(position);
}
else
{
SelectedCellTitleText.Text = "None";
CellGrid.ItemsSource = new[] { new InspectorItemViewModel("Selection", "None", "Select a grid cell to inspect it.") };
SurfaceGrid.ItemsSource = Array.Empty<InspectorItemViewModel>();
NetworkGrid.ItemsSource = Array.Empty<NetworkInspectionViewModel>();
}
CellGrid.ItemsSource = m_SelectedCell is { } position && m_Level.InBounds(position)
? CellInspectionItems(position)
: new[] { new InspectorItemViewModel("Selection", "None", "Select a grid cell to inspect it.") };
ForecastList.ItemsSource = m_Level.Forecasts.Select(forecast => new ForecastViewModel($"{forecast.Turns}: {forecast.Message}")).ToArray();
}
@@ -827,25 +865,61 @@ public sealed partial class MainWindow
{
var prop = m_Level.GetProp(position);
var surface = m_Level.GetSurface(position);
var fuel = m_Level.GetUnderground(position, ECarrierType.Fuel);
var coolant = m_Level.GetUnderground(position, ECarrierType.Coolant);
var electricity = m_Level.GetUnderground(position, ECarrierType.Electricity);
return [
new("Position", $"{position.X},{position.Y}", "Grid coordinate."),
new("Terrain", m_Level.GetTerrain(position).ToString(), "Static surface cell type."),
new("Prop", $"{prop.Type} {prop.SwitchState}", "Placed surface object and switch state."),
new("Services F/C/E", $"{prop.FuelServiceState}/{prop.CoolantServiceState}/{prop.ElectricityServiceState}", "Consumer service status."),
new("Fuel Net", UndergroundText(fuel), "Amount, intensity, and integrity."),
new("Coolant Net", UndergroundText(coolant), "Amount, intensity, and integrity."),
new("Electric Net", UndergroundText(electricity), "Amount, intensity, and integrity."),
new("Surface F/C/E/H", $"{Format(surface.Fuel)} / {Format(surface.Coolant)} / {Format(surface.Electricity)} / {Format(surface.Heat)}", "Visible hazards and heat."),
new("Blocks F/C/E", $"{surface.FuelBlockTurns}/{surface.CoolantBlockTurns}/{surface.ElectricityBlockTurns}", "Temporary remedy entry blocks.")
];
}
private static string UndergroundText(UndergroundCell cell)
private InspectorItemViewModel[] SurfaceInspectionItems(GridPosition position)
{
return $"{cell.State} amount {Format(cell.Amount)} intensity {Format(cell.Intensity)} integrity {cell.StructuralIntegrity}";
var surface = m_Level.GetSurface(position);
return [
new("Fuel", Format(surface.Fuel), "Visible fuel hazard."),
new("Coolant", Format(surface.Coolant), "Visible coolant hazard."),
new("Electric", Format(surface.Electricity), "Visible electricity hazard."),
new("Heat", Format(surface.Heat), "Visible heat.")
];
}
private NetworkInspectionViewModel[] NetworkInspectionItems(GridPosition position)
{
return [
NetworkInspectionItem("Fuel", m_Level.GetUnderground(position, ECarrierType.Fuel)),
NetworkInspectionItem("Coolant", m_Level.GetUnderground(position, ECarrierType.Coolant)),
NetworkInspectionItem("Electric", m_Level.GetUnderground(position, ECarrierType.Electricity))
];
}
private static NetworkInspectionViewModel NetworkInspectionItem(string carrier, UndergroundCell cell)
{
return new(carrier, cell.State.ToString(), Format(cell.Amount), Format(cell.Intensity), cell.StructuralIntegrity.ToString(CultureInfo.InvariantCulture));
}
private string SelectedCellTitle(GridPosition position)
{
var prop = m_Level.GetProp(position);
if (prop.Type != EPropType.None)
return $"{prop.Type} {prop.SwitchState}";
if (m_Level.Robot.Position == position)
return "Robot";
var leak = m_Level.Leaks.FirstOrDefault(leak => !leak.Repaired && (leak.AccessPosition == position || leak.UndergroundPosition == position));
if (leak is not null)
return $"{leak.Carrier} Leak";
var surface = m_Level.GetSurface(position);
if (surface.Fuel > 0 || surface.Coolant > 0 || surface.Electricity > 0)
return "Surface Hazard";
if (surface.Heat > 0)
return "Heat";
return m_Level.GetTerrain(position).ToString();
}
private static string Format(float value)
@@ -957,6 +1031,11 @@ public sealed partial class MainWindow
return m_Level.Robot.Position == source ? "robot" : null;
}
private bool HasMovableAt(GridPosition source)
{
return MovableImageKey(source) is not null;
}
private float SurfaceOpacity()
{
return m_ActiveLayer == EEditorLayer.Surface ? 1.0f : 0.5f;
@@ -1217,6 +1296,7 @@ public sealed partial class MainWindow
private EEditorLayer m_ActiveLayer = EEditorLayer.Surface;
private StorageFile? m_CurrentFile;
private GridPosition? m_CursorDragStartCell;
private bool m_CursorDragStartRejected;
private bool m_DragExceededClickThreshold;
private GridPosition? m_DragPreviewDestination;
private string m_EditorFeedback = string.Empty;