Improve Win2D editor hover feedback

This commit is contained in:
2026-05-09 03:01:26 +02:00
parent e90609bcee
commit 4b581d60b5
3 changed files with 71 additions and 49 deletions

View File

@@ -25,6 +25,7 @@ This repository follows the local `.editorconfig` and the style visible in the c
## Braces And Blocks ## Braces And Blocks
- Use braces for multi-line bodies. - Use braces for multi-line bodies.
- If nesting a for-loop under another for-loop, always include curly braces in the parent for-loop.
- Omit braces for simple single-line embedded statements when readability stays clear. - Omit braces for simple single-line embedded statements when readability stays clear.
- Nested control flow with multi-line bodies should use braces at every multi-line level. - Nested control flow with multi-line bodies should use braces at every multi-line level.
- Keep opening braces on the next line for types, methods, properties, accessors, and control blocks. - Keep opening braces on the next line for types, methods, properties, accessors, and control blocks.

View File

@@ -21,7 +21,7 @@
<Grid Grid.Row="1" ColumnSpacing="0"> <Grid Grid.Row="1" ColumnSpacing="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="220" /> <ColumnDefinition Width="260" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="300" /> <ColumnDefinition Width="300" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -30,19 +30,18 @@
<StackPanel Padding="12" Spacing="10"> <StackPanel Padding="12" Spacing="10">
<TextBlock Text="Tools" FontSize="18" FontWeight="SemiBold" Foreground="#F4F1E8" /> <TextBlock Text="Tools" FontSize="18" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="ToolPicker"> <ItemsControl x:Name="ToolPicker">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<RadioButton GroupName="EditorTools" IsChecked="{Binding IsSelected, Mode=TwoWay}" <ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay}"
Checked="ToolRadio_Checked"> Checked="ToolToggle_Checked" ToolTipService.ToolTip="{Binding Label}"
<Grid ColumnSpacing="8"> Padding="5" Margin="0,0,8,8">
<Grid.ColumnDefinitions> <Image Width="96" Height="96" Source="{Binding Icon}" Stretch="Uniform" />
<ColumnDefinition Width="28" /> </ToggleButton>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Width="24" Height="24" Source="{Binding Icon}" />
<TextBlock Grid.Column="1" Text="{Binding Label}" />
</Grid>
</RadioButton>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@@ -61,6 +60,7 @@
PointerPressed="LevelCanvas_PointerPressed" PointerPressed="LevelCanvas_PointerPressed"
PointerMoved="LevelCanvas_PointerMoved" PointerMoved="LevelCanvas_PointerMoved"
PointerReleased="LevelCanvas_PointerReleased" PointerReleased="LevelCanvas_PointerReleased"
PointerExited="LevelCanvas_PointerExited"
PointerWheelChanged="LevelCanvas_PointerWheelChanged" /> PointerWheelChanged="LevelCanvas_PointerWheelChanged" />
</Grid> </Grid>

View File

@@ -2,7 +2,6 @@
using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Input;
@@ -12,9 +11,7 @@ using System.Globalization;
using Windows.Foundation; using Windows.Foundation;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.Pickers; using Windows.Storage.Pickers;
using Windows.System;
using Windows.UI; using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Popups; using Windows.UI.Popups;
using WinRT.Interop; using WinRT.Interop;
@@ -86,7 +83,7 @@ public sealed partial class MainWindow
return await CanvasBitmap.LoadAsync(sender, path); return await CanvasBitmap.LoadAsync(sender, path);
} }
private void ToolRadio_Checked(object sender, RoutedEventArgs e) private void ToolToggle_Checked(object sender, RoutedEventArgs e)
{ {
if ((sender as FrameworkElement)?.DataContext is EditorToolViewModel tool) if ((sender as FrameworkElement)?.DataContext is EditorToolViewModel tool)
{ {
@@ -176,15 +173,6 @@ public sealed partial class MainWindow
private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e) private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{ {
var point = e.GetCurrentPoint(LevelCanvas); var point = e.GetCurrentPoint(LevelCanvas);
if (point.Properties.IsLeftButtonPressed && IsShiftDown())
{
m_Panning = true;
m_LastPanPoint = point.Position;
_ = LevelCanvas.CapturePointer(e.Pointer);
e.Handled = true;
return;
}
if (point.Properties.IsRightButtonPressed) if (point.Properties.IsRightButtonPressed)
{ {
RemovePropAt(point.Position); RemovePropAt(point.Position);
@@ -195,8 +183,10 @@ public sealed partial class MainWindow
if (point.Properties.IsLeftButtonPressed) if (point.Properties.IsLeftButtonPressed)
{ {
_ = LevelCanvas.CapturePointer(e.Pointer); _ = LevelCanvas.CapturePointer(e.Pointer);
SelectOrPaintAt(point.Position); m_LeftPointerDown = true;
m_Painting = m_SelectedTool != EEditorTool.Cursor; m_LeftPointerDownPoint = point.Position;
m_LastPanPoint = point.Position;
m_DragExceededClickThreshold = false;
e.Handled = true; e.Handled = true;
} }
} }
@@ -204,11 +194,17 @@ public sealed partial class MainWindow
private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e) private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{ {
var point = e.GetCurrentPoint(LevelCanvas); var point = e.GetCurrentPoint(LevelCanvas);
if (m_Panning) if (m_LeftPointerDown)
{ {
var deltaX = point.Position.X - m_LastPanPoint.X; var deltaX = point.Position.X - m_LastPanPoint.X;
var deltaY = point.Position.Y - m_LastPanPoint.Y; var deltaY = point.Position.Y - m_LastPanPoint.Y;
m_LastPanPoint = point.Position; m_LastPanPoint = point.Position;
var totalDeltaX = point.Position.X - m_LeftPointerDownPoint.X;
var totalDeltaY = point.Position.Y - m_LeftPointerDownPoint.Y;
if (Math.Sqrt((totalDeltaX * totalDeltaX) + (totalDeltaY * totalDeltaY)) > c_ClickPixelThreshold)
m_DragExceededClickThreshold = true;
m_PanX += deltaX; m_PanX += deltaX;
m_PanY += deltaY; m_PanY += deltaY;
ClampPan(); ClampPan();
@@ -217,21 +213,26 @@ public sealed partial class MainWindow
return; return;
} }
if (m_Painting) SetHoveredCell(point.Position);
{
PaintAt(point.Position);
e.Handled = true;
}
} }
private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e) private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
{ {
m_Painting = false; var point = e.GetCurrentPoint(LevelCanvas);
m_Panning = false; if (m_LeftPointerDown && !m_DragExceededClickThreshold)
SelectOrPaintAt(point.Position);
m_LeftPointerDown = false;
m_DragExceededClickThreshold = false;
LevelCanvas.ReleasePointerCapture(e.Pointer); LevelCanvas.ReleasePointerCapture(e.Pointer);
e.Handled = true; e.Handled = true;
} }
private void LevelCanvas_PointerExited(object sender, PointerRoutedEventArgs e)
{
ClearHoveredCell();
}
private void LevelCanvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e) private void LevelCanvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{ {
var point = e.GetCurrentPoint(LevelCanvas); var point = e.GetCurrentPoint(LevelCanvas);
@@ -243,11 +244,6 @@ public sealed partial class MainWindow
e.Handled = true; e.Handled = true;
} }
private static bool IsShiftDown()
{
return InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
}
private void ZoomAt(Point point, double zoomFactor) private void ZoomAt(Point point, double zoomFactor)
{ {
var oldLayout = GetLayout(); var oldLayout = GetLayout();
@@ -314,7 +310,7 @@ public sealed partial class MainWindow
drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21)); drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21));
DrawTerrain(drawing, layout); DrawTerrain(drawing, layout);
DrawCellOverlays(drawing, layout); DrawCellOverlays(drawing, layout);
DrawGrid(drawing, layout); //DrawGrid(drawing, layout);
DrawRobot(drawing, layout); DrawRobot(drawing, layout);
} }
@@ -350,10 +346,13 @@ public sealed partial class MainWindow
if (cell.Hazards.Fire) if (cell.Hazards.Fire)
DrawImage(drawing, m_FireSprite, Inset(rect, 0.08)); DrawImage(drawing, m_FireSprite, Inset(rect, 0.08));
DrawCellProp(drawing, cell, rect);
if (m_HoveredCell == position)
drawing.FillRectangle(rect, ColorHelper.FromArgb(72, 255, 255, 255));
if (m_SelectedCell == position) if (m_SelectedCell == position)
drawing.DrawRectangle(rect, Colors.White, 3); drawing.DrawRectangle(rect, Colors.White, 3);
DrawCellProp(drawing, cell, rect);
} }
} }
} }
@@ -364,7 +363,7 @@ public sealed partial class MainWindow
return; return;
var sourceRect = PipeTileSourceRect(GetPipeConnectionMask(position, cell.Pipe)); var sourceRect = PipeTileSourceRect(GetPipeConnectionMask(position, cell.Pipe));
drawing.DrawImage(tilemap, rect, sourceRect); drawing.DrawImage(tilemap, rect, sourceRect, 1.0f, CanvasImageInterpolation.HighQualityCubic);
} }
private int GetPipeConnectionMask(GridPosition position, EPipeMedium medium) private int GetPipeConnectionMask(GridPosition position, EPipeMedium medium)
@@ -422,7 +421,7 @@ public sealed partial class MainWindow
private static void DrawImage(CanvasDrawingSession drawing, CanvasBitmap? image, Rect rect, float opacity = 1) private static void DrawImage(CanvasDrawingSession drawing, CanvasBitmap? image, Rect rect, float opacity = 1)
{ {
if (image is not null) if (image is not null)
drawing.DrawImage(image, rect, image.Bounds, opacity); drawing.DrawImage(image, rect, image.Bounds, opacity, CanvasImageInterpolation.HighQualityCubic);
} }
private static Rect Inset(Rect rect, double fraction) private static Rect Inset(Rect rect, double fraction)
@@ -438,7 +437,7 @@ public sealed partial class MainWindow
var wallMask = c_AllCorners ^ floorMask; var wallMask = c_AllCorners ^ floorMask;
var sourceRect = TilemapSourceRect(wallMask); var sourceRect = TilemapSourceRect(wallMask);
drawing.DrawImage(m_TerrainTilemap, rect, sourceRect); drawing.DrawImage(m_TerrainTilemap, rect, sourceRect, 1.0f, CanvasImageInterpolation.HighQualityCubic);
} }
private static Rect TilemapSourceRect(int wallMask) private static Rect TilemapSourceRect(int wallMask)
@@ -497,7 +496,7 @@ public sealed partial class MainWindow
private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect) private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect)
{ {
if (m_PropSprites.TryGetValue(cell.Prop, out var sprite)) if (m_PropSprites.TryGetValue(cell.Prop, out var sprite))
drawing.DrawImage(sprite, rect, sprite.Bounds); drawing.DrawImage(sprite, rect, sprite.Bounds, 1.0f, CanvasImageInterpolation.HighQualityCubic);
} }
private void DrawGrid(CanvasDrawingSession drawing, CanvasLayout layout) private void DrawGrid(CanvasDrawingSession drawing, CanvasLayout layout)
@@ -530,6 +529,25 @@ public sealed partial class MainWindow
return m_Level.InBounds(position); return m_Level.InBounds(position);
} }
private void SetHoveredCell(Point point)
{
var hoveredCell = TryGetGridPosition(point, out var position) ? position : (GridPosition?)null;
if (m_HoveredCell == hoveredCell)
return;
m_HoveredCell = hoveredCell;
LevelCanvas.Invalidate();
}
private void ClearHoveredCell()
{
if (m_HoveredCell is null)
return;
m_HoveredCell = null;
LevelCanvas.Invalidate();
}
private CanvasLayout GetLayout() private CanvasLayout GetLayout()
{ {
ClampPan(); ClampPan();
@@ -715,6 +733,7 @@ public sealed partial class MainWindow
private const double c_MinZoom = 0.5; private const double c_MinZoom = 0.5;
private const double c_MaxZoom = 4; private const double c_MaxZoom = 4;
private const double c_ZoomStep = 1.15; private const double c_ZoomStep = 1.15;
private const double c_ClickPixelThreshold = 5;
private readonly SimulationEngine m_Simulation = new(); private readonly SimulationEngine m_Simulation = new();
private readonly Dictionary<ECellProp, CanvasBitmap> m_PropSprites = []; private readonly Dictionary<ECellProp, CanvasBitmap> m_PropSprites = [];
@@ -722,9 +741,11 @@ public sealed partial class MainWindow
private StorageFile? m_CurrentFile; private StorageFile? m_CurrentFile;
private LevelState m_Level; private LevelState m_Level;
private IReadOnlyList<EditorToolViewModel> m_EditorTools = []; private IReadOnlyList<EditorToolViewModel> m_EditorTools = [];
private bool m_Painting; private bool m_LeftPointerDown;
private bool m_Panning; private bool m_DragExceededClickThreshold;
private Point m_LeftPointerDownPoint;
private Point m_LastPanPoint; private Point m_LastPanPoint;
private GridPosition? m_HoveredCell;
private GridPosition? m_SelectedCell; private GridPosition? m_SelectedCell;
private EEditorTool m_SelectedTool = EEditorTool.Cursor; private EEditorTool m_SelectedTool = EEditorTool.Cursor;
private double m_Zoom = 1; private double m_Zoom = 1;