From 4b581d60b5bb3e0c7d5faf9af3b03548cfc8616b Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Sat, 9 May 2026 03:01:26 +0200 Subject: [PATCH] Improve Win2D editor hover feedback --- CODESTYLE.md | 1 + src/ReactorMaintenance.Win2D/MainWindow.xaml | 24 ++--- .../MainWindow.xaml.cs | 95 +++++++++++-------- 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index eeb4e9d..3bc7baf 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -25,6 +25,7 @@ This repository follows the local `.editorconfig` and the style visible in the c ## Braces And Blocks - 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. - 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. diff --git a/src/ReactorMaintenance.Win2D/MainWindow.xaml b/src/ReactorMaintenance.Win2D/MainWindow.xaml index bb45e54..4868552 100644 --- a/src/ReactorMaintenance.Win2D/MainWindow.xaml +++ b/src/ReactorMaintenance.Win2D/MainWindow.xaml @@ -21,7 +21,7 @@ - + @@ -30,19 +30,18 @@ + + + + + - - - - - - - - - - + + + @@ -61,6 +60,7 @@ PointerPressed="LevelCanvas_PointerPressed" PointerMoved="LevelCanvas_PointerMoved" PointerReleased="LevelCanvas_PointerReleased" + PointerExited="LevelCanvas_PointerExited" PointerWheelChanged="LevelCanvas_PointerWheelChanged" /> diff --git a/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs b/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs index f961752..9d86e0c 100644 --- a/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs +++ b/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs @@ -2,7 +2,6 @@ using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.UI; -using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; @@ -12,9 +11,7 @@ using System.Globalization; using Windows.Foundation; using Windows.Storage; using Windows.Storage.Pickers; -using Windows.System; using Windows.UI; -using Windows.UI.Core; using Windows.UI.Popups; using WinRT.Interop; @@ -86,7 +83,7 @@ public sealed partial class MainWindow 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) { @@ -176,15 +173,6 @@ public sealed partial class MainWindow private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e) { 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) { RemovePropAt(point.Position); @@ -195,8 +183,10 @@ public sealed partial class MainWindow if (point.Properties.IsLeftButtonPressed) { _ = LevelCanvas.CapturePointer(e.Pointer); - SelectOrPaintAt(point.Position); - m_Painting = m_SelectedTool != EEditorTool.Cursor; + m_LeftPointerDown = true; + m_LeftPointerDownPoint = point.Position; + m_LastPanPoint = point.Position; + m_DragExceededClickThreshold = false; e.Handled = true; } } @@ -204,11 +194,17 @@ public sealed partial class MainWindow private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e) { var point = e.GetCurrentPoint(LevelCanvas); - if (m_Panning) + if (m_LeftPointerDown) { var deltaX = point.Position.X - m_LastPanPoint.X; var deltaY = point.Position.Y - m_LastPanPoint.Y; 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_PanY += deltaY; ClampPan(); @@ -217,21 +213,26 @@ public sealed partial class MainWindow return; } - if (m_Painting) - { - PaintAt(point.Position); - e.Handled = true; - } + SetHoveredCell(point.Position); } private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e) { - m_Painting = false; - m_Panning = false; + var point = e.GetCurrentPoint(LevelCanvas); + if (m_LeftPointerDown && !m_DragExceededClickThreshold) + SelectOrPaintAt(point.Position); + + m_LeftPointerDown = false; + m_DragExceededClickThreshold = false; LevelCanvas.ReleasePointerCapture(e.Pointer); e.Handled = true; } + private void LevelCanvas_PointerExited(object sender, PointerRoutedEventArgs e) + { + ClearHoveredCell(); + } + private void LevelCanvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e) { var point = e.GetCurrentPoint(LevelCanvas); @@ -243,11 +244,6 @@ public sealed partial class MainWindow e.Handled = true; } - private static bool IsShiftDown() - { - return InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down); - } - private void ZoomAt(Point point, double zoomFactor) { var oldLayout = GetLayout(); @@ -314,7 +310,7 @@ public sealed partial class MainWindow drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21)); DrawTerrain(drawing, layout); DrawCellOverlays(drawing, layout); - DrawGrid(drawing, layout); + //DrawGrid(drawing, layout); DrawRobot(drawing, layout); } @@ -350,10 +346,13 @@ public sealed partial class MainWindow if (cell.Hazards.Fire) 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) drawing.DrawRectangle(rect, Colors.White, 3); - - DrawCellProp(drawing, cell, rect); } } } @@ -364,7 +363,7 @@ public sealed partial class MainWindow return; 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) @@ -422,7 +421,7 @@ public sealed partial class MainWindow private static void DrawImage(CanvasDrawingSession drawing, CanvasBitmap? image, Rect rect, float opacity = 1) { 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) @@ -438,7 +437,7 @@ public sealed partial class MainWindow var wallMask = c_AllCorners ^ floorMask; 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) @@ -497,7 +496,7 @@ public sealed partial class MainWindow private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect) { 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) @@ -530,6 +529,25 @@ public sealed partial class MainWindow 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() { ClampPan(); @@ -715,6 +733,7 @@ public sealed partial class MainWindow private const double c_MinZoom = 0.5; private const double c_MaxZoom = 4; private const double c_ZoomStep = 1.15; + private const double c_ClickPixelThreshold = 5; private readonly SimulationEngine m_Simulation = new(); private readonly Dictionary m_PropSprites = []; @@ -722,9 +741,11 @@ public sealed partial class MainWindow private StorageFile? m_CurrentFile; private LevelState m_Level; private IReadOnlyList m_EditorTools = []; - private bool m_Painting; - private bool m_Panning; + private bool m_LeftPointerDown; + private bool m_DragExceededClickThreshold; + private Point m_LeftPointerDownPoint; private Point m_LastPanPoint; + private GridPosition? m_HoveredCell; private GridPosition? m_SelectedCell; private EEditorTool m_SelectedTool = EEditorTool.Cursor; private double m_Zoom = 1;