Files
LlamaCasty/TASKS/task-52-zoom-box.md
gramps fe80e8c994 Docs: record the zoom box as TASK 52 and fix the task-numbering drift (Unit: zoom docs)
The zoom feature shipped without a task record while its index rows carried the
"TASK 51" label — which belongs to the webcam background effects (Remove + Blur).
For an honest catalogue and handoff:

- TASKS/task-52-zoom-box.md: feature provenance (creator-requested), the three
  2026-10-08 corrections (loupe -> anchored capture over the full composite drawn
  last -> never show the dim shading), shapes, implementation map, tests, DONE.
- TASKS.md: row 52 added (shipped); row 51 reworded — unblocked now that the
  zoom build shipped.
- TASK 51 -> TASK 52 in Models/index.md, ViewModels/index.md, and the two code
  comments that pointed at the zoom as "TASK 51".
- HANDOFF.md: pre-push readiness notes (5 commits ahead, push-safe).
2026-10-08 08:52:36 -07:00

2.7 KiB
Raw Permalink Blame History

TASK 52 — Zoom box (anchored 2× magnifier)

Status: ✅ Done — shipped 2026-10-08 (three creator corrections during the build).

What it is

Right-clicking the live preview parks a movable 2× magnifier box that shows a live, always-on magnified view of a FIXED capture region — the box/2 window centred on the click. The captured area is whatever was under the click; it stays the same no matter where the box is dragged.

Creator rulings (order received, 2026-10-08)

  1. Not a loupe. The click fixes the capture; moving the box never changes its content. First cut shipped 4b3d82f as a box-following loupe — reworked in 6bff21b to anchored CaptureX/Y (drag = UpdateZoomMove moves box only).
  2. Content = every layer. The box magnifies the fully-composited frame (backdrop + every element incl. webcam), drawn LAST, on top of everything (layer n+1) — SceneCompositor skips the active ZoomSceneElement in every layer loop and BlitZooms it at the end via Services/Compositor/ZoomMagnifier.
  3. Never show shading. The dim rectangles around the box must never appear inside it — the DimRects overlay is a sibling of CanvasGrid, never a descendant, so the zoom box's VisualBrush can't pick it up (948dd08).
  4. Rectangle = 4:3 ("more of a 640×480 aspect ratio"); Circle square, Phone 9:16.

Shapes (master 1920×1080; max box 0.7·frame)

Shape Box Notes
Circle square, min(maxW,maxH) round clip
VerticalRectangle (Phone) 9:16 h = 0.7·1080
HorizontalRectangle (Tablet, default) 4:3 w = h·4/3

Box and capture are each centred on the click and clamped independently.

Implementation

  • ViewModels/MainViewModel.Zoom.cs — activate/move/dismiss + dims; transient ZoomSceneElement healed/mirrored (HealZoomLayers/EnsureZoomLayer, z-index 1, never persisted).
  • Services/Compositor/ZoomMagnifier.cs — pure 2× bilinear over an already-composited opaque buffer (temp snapshot via ArrayPool, optional round clip).
  • Models/ZoomSceneElement.cs / Models/ZoomState.cs — box rect + CaptureX/Y.
  • Controls/PreviewPane.xaml(.cs) — ZoomBox (sibling above CanvasGrid, brushes the whole composed preview at the fixed capture, 2×) + DimRectsLayer (sibling above the content, below the box) + UpdateZoomBox.
  • Output crop always equals the tier — the box is never the crop source.

Tests

ZoomMagnifierTests (money proof: box over blue shows green capture at 2× incl. an extra red "layer"; round-clip) + ZoomBoxInteractionTests (aspects, capture anchor centred on the click, move-moves-only-the-box, corner clamp, dims-layer-structure invariant). Full suite 406/406 on a clean run at 948dd08.