Skip to content

Resizable, movable minimap with quality scaling#106

Merged
sjvrensburg merged 4 commits intomainfrom
feature/resizable-minimap
Apr 14, 2026
Merged

Resizable, movable minimap with quality scaling#106
sjvrensburg merged 4 commits intomainfrom
feature/resizable-minimap

Conversation

@sjvrensburg
Copy link
Copy Markdown
Owner

Summary

The minimap was a fixed 180×240 thumbnail in the bottom-right corner. This PR makes it a freely draggable, resizable, quality-scaling context view:

  • Drag the top-edge grip to move the minimap anywhere on the window
  • Drag the inner-corner handle (the corner pointing into the screen, auto-selected based on docking) to resize. Aspect-locked to the page so the thumbnail never distorts
  • Hover to reveal the grip and resize affordances; they stay out of the way otherwise
  • Click/drag inside still navigates the primary view (unchanged)
  • Position and size persist in AppConfig across sessions

Quality (tier 1)

When the displayed thumbnail exceeds the cached thumbnail's resolution by more than 10%, the minimap transparently switches its source to the primary view's high-DPI CachedImage. The source is uploaded to the GPU as a mipmapped texture, cached per control, so enlarged minimaps stay crisp without re-sampling a multi-megapixel bitmap on every frame.

Smoothness fixes landed during implementation

  • Drag/resize deltas switched from control-relative to window-relative coordinates — the previous approach fed the moving origin back into each frame's delta calculation, causing resistance/jitter.
  • Aspect-locked resize now uses diagonal projection instead of axis-dominance switching — mouse wobble no longer flips the dominant axis, so size changes smoothly in any drag direction.
  • Rail-mode scroll stutter with a large minimap eliminated via the mipmapped GPU texture cache.
  • s_hashLine dedicated ThreadStatic paint instead of mutating a shared paint's Style mid-render.

Bumps to 3.10.0.0 (minor — user-visible enhancement, no API changes).

Test plan

  • dotnet build RailReader2.slnx -c Release clean
  • 249/249 Core tests pass
  • Manual: drag grip to all four corners, shape-locked resize in all directions, both crisp rendering at large sizes and smooth rail-scroll
  • Default and custom-sized minimap persist across restart
  • Ctrl+M toggle still works, click-to-navigate unchanged

🤖 Generated with Claude Code

sjvrensburg and others added 4 commits April 14, 2026 14:44
The minimap was a fixed 180x240 docked bottom-right. Now:

- Drag the top-edge grip to move it anywhere on the window
- Drag the inner-corner handle (opposite the screen edge it's docked
  against) to resize. Aspect-locked to the page so the thumbnail
  never distorts.
- Position and size persist via AppConfig (MinimapWidth, Height,
  MarginRight, MarginBottom).
- Click/drag inside as before to navigate the primary view.

Tier-1 quality: when the displayed thumbnail size exceeds the cached
thumbnail bitmap by >10%, render from the primary's high-DPI SKImage
(`CachedImage`) instead of the small thumbnail. No extra rendering
work — reuses what the primary already has. Mitchell sampling at
rest, Linear during drag.

Implementation:
- TabViewModel.MinimapImage wraps the thumbnail SKBitmap as SKImage
  (cached, disposed with the tab), so the canvas can use sampling-
  aware DrawImage uniformly across both source paths.
- Drag-distance threshold (4px) preserves "click to navigate" UX —
  small movements still navigate; only intentional drags move/resize.
- Resize handle position auto-flips to whichever corner points into
  the screen (handles non-default minimap docking).
- Hover/drag chrome (move stripe, resize hash) only renders when
  needed; otherwise the minimap looks the same as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues:

1. Drag/resize deltas were computed from e.GetPosition(this), relative
   to the moving control. As the control's origin moved during drag,
   subsequent pointer events saw a shifted relative position, feeding
   back into the next delta calculation — visible as resistance and
   jitter. Switched to window-coords for delta math; local coords still
   used for hit-zone detection and navigation.

2. Aspect-locked resize switched between width-driven and height-driven
   each frame based on which axis had the larger proportional drag.
   Mouse wobble flipped the dominant axis, jumping the size. Replaced
   with diagonal projection: project the drag vector onto the corner's
   aspect-ratio direction, derive both width and height from a single
   scalar. Continuous and aspect-preserving by construction.

3. Large minimap stuttered during rail scrolling because each redraw
   resampled the multi-megapixel CachedImage from scratch on the UI
   thread. Upload the source as a mipmapped GPU texture once (cached
   on the control, disposed on source change or detach) so subsequent
   draws are constant-cost regardless of downscale ratio.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Separate ThreadStatic SKPaint for resize-handle hash lines instead of
  mutating the shared grip-dot paint's Style back and forth (fragile
  with shared state).
- Name PrimarySourceThreshold (1.1) and MaxViewportFraction (0.8).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sjvrensburg sjvrensburg merged commit b0c70c0 into main Apr 14, 2026
2 checks passed
@sjvrensburg sjvrensburg deleted the feature/resizable-minimap branch April 14, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant