Refine editor inspector and drag painting
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -42,6 +42,21 @@ public sealed class LevelEditorTests
|
||||
Assert.Equal(EUndergroundState.Intact, next.GetUnderground(position, ECarrierType.Electricity).State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UndergroundToolCanPaintAdjacentCellsRepeatedly()
|
||||
{
|
||||
var level = LevelState.Create("Network editor", 6, 6);
|
||||
var command = new EditorToolCommand { Tool = EEditorTool.Underground, Carrier = ECarrierType.Fuel };
|
||||
|
||||
level = LevelEditor.Apply(level, new(1, 1), command);
|
||||
level = LevelEditor.Apply(level, new(2, 1), command);
|
||||
level = LevelEditor.Apply(level, new(3, 1), command);
|
||||
|
||||
Assert.Equal(EUndergroundState.Intact, level.GetUnderground(new(1, 1), ECarrierType.Fuel).State);
|
||||
Assert.Equal(EUndergroundState.Intact, level.GetUnderground(new(2, 1), ECarrierType.Fuel).State);
|
||||
Assert.Equal(EUndergroundState.Intact, level.GetUnderground(new(3, 1), ECarrierType.Fuel).State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumerToolPlacesCarrierAgnosticConsumer()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user