Play Grid Magnifier

Status: Draft | Implemented | 2026

Author: Toluwaleke Ogundipe

Reviewer: Jonathan Blandford

Goals

An interactive magnifier on PlayGrid that provides a zoomed view of the grid.

Prior Art

https://codeberg.org/haydn/typesetter/src/branch/main/screenshots/light-magnifier.png

Overall Approach

The magnifier is an overlay on the PlayGrid widget.

Behaviour

The magnifier is a non-intrusive overlay that responds to standard mouse inputs while ensuring core puzzle interactions remain uninterrupted.

  • Activation & Deactivation: The magnifier is toggled on and off using a secondary (right) click anywhere on the grid. Upon activation, the lens immediately appears, centered on the cursor’s current position.

  • Cursor Tracking: While the magnifier is enabled, any mouse movement within the grid boundaries causes the lens to move with its center always at the current cursor position. Also, the exact point on the grid underneath the cursor is always at the center of the magnified projection.

  • Auto-Hiding: If the cursor leaves the allocated area of the widget, the magnifier is temporarily hidden. The moment the cursor re-enters the widget bounds, the magnifier instantly reappears and resumes tracking.

  • Interoperability with Gameplay: The magnifier overlay does not intercept standard gameplay input. A primary (left) click while the magnifier is active passes straight “through the lens”, selecting the underlying cell exactly as it normally would without the magnifier. Keyboard input alike.

State Management

PlayGrid’s internal state is expanded to track the magnifier’s state:

  • Lifecycle: A boolean toggle to indicate/determine whether the magnifier is enabled or disabled.

  • Visibility: A separate boolean to prevent the magnifier from rendering, for instance, when it’s enabled but the cursor has left the widget’s bounds.

  • Position: The sub-pixel coordinates of the cursor relative to the widget’s origin are stored and continuously updated to position the magnifier.

Event Handling

Input handling relies on dedicated GTK event controllers:

  • Toggle: A click gesture for the secondary (right) click acts exclusively as the magnifier’s enable/disable toggle.

  • Dynamic Motion Tracking: Tracking mouse coordinates is computationally expensive and unnecessary while the magnifier is disabled. Therefore, a motion controller is created and attached to the widget only when the magnifier is toggled on, and immediately removed and destroyed when toggled off.

  • Redraw Triggers: Any change in state (be it a toggle, cursor movement, or the cursor entering/leaving the widget bounds) queues a full widget redraw.

Geometry

The radius of the target region (and in turn, the viewport) is dynamically calculated based on the current layout config to ensure it consistently covers an area of 3 cells across and down, regardless of the grid’s zoom level.

When the center of the target region coincides with the center of a cell, the geometry is thus:

Magnifier Target Region Geometry

where:

  • a represents the distance from the center point to the far outer edge of the adjacent cells, accounting for the center half-cell, a full cell, and necessary borders, i.e:

    a = cell_size / 2.0 + border_size + cell_size + border_size
    
  • b represents the orthogonal distance from the center point to the immediate edge of the center cell (plus its border) i.e:

    b = cell_size / 2.0 + border_size
    
  • r represents the radius of the target region determined by finding the hypotenuse of the right-angled triangle formed by a and b, ensuring the circle passes through the absolute corners of all four immediately adjacent cells. This is achieved using the Pythagorean theorem:

    r = sqrt (a * a + b * b)
    

The radius of the viewport is twice that of the target region, given the magnification factor of 2.0.

Rendering

After rendering grid overlays (enumerations, etc), the magnifier is rendered if enabled and not hidden.

The viewport is a circle with radius as described earlier and centered at the cursor’s coordinates. It is filled with a solid background color (to prevent the underlying grid from bleeding through magnified NULL cells) and outlined (i.e stroked on the outside) with a solid color and thickness of border_size to visually separate the magnified grid from the main grid.

Then, the magnified grid is rendered at a scale of 2.0 and positioned such that the same point at which the cursor is on the grid falls right at the center of the circle, and is clipped by the same circle.

Open Questions

Areas For Improvement

  • [ ] Snap to the grid (not sure this will work well with the magnifier directly underneath the cursor).

  • [ ] React to keyboard navigation by snapping to the focused cell. Once the cursor moves again, it should snap back to the cursor and continue to follow it.

  • [ ] Animation when snapping between cells / the cursor.

  • [ ] Variable zoom:

    - when the magnifier is enabled, the normal grid zoom controls adjust the
      magnifier's magnification factor instead
    - `magnification_factor` is the scale factor of the magnified view to the grid
    - `MIN_MAGNIFICATION_FACTOR` > 1.0
    - `BASE_MAGNIFICATION_FACTOR` = 2.0
    - `base_radius` is as computated earlier
    - `lens_radius = base_radius * BASE_MAGNIFICATION_FACTOR`
    - `target_radius = base_radius * BASE_MAGNIFICATION_FACTOR / magnification_factor`