Files
m3te/board_layout.sx
swipelab 9ed98c73d2 P4.4: selection highlight + score/moves HUD (sx, iOS sim)
Tap a gem to select it: BoardView hit-tests the touch to a grid cell and
draws a bright rim + translucent fill over it; tapping the same cell clears
the selection, tapping another moves it, tapping off-board clears it.
Selection only — no swap (that's P5). The HUD renders the live score and
remaining moves (out of the move limit) in the Lato font on a translucent
card above the grid.

The touch→cell geometry is factored into a pure BoardLayout (no GL/stb
imports) that BoardView composes and P5 will reuse for swap endpoints.
tests/hit_test.sx locks point_to_cell as the exact inverse of cell_frame
(every cell center round-trips; off-board taps reject) — headless because
BoardLayout pulls no C imports. goldens/p4_hud.png captures the scene after
a real idb tap at (201,437)pt: the HUD plus a yellow selection rim on the
red gem at cell (col 4, row 3).
2026-06-05 00:00:48 +03:00

53 lines
2.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Pure geometry of the on-screen board: where the centered 8×8 grid sits inside
// a frame, and the two-way mapping between cells and screen points. Owns no
// rendering and pulls in NO GL/stb imports, so the touch→cell mapping is
// unit-testable headless. BoardView composes this for layout + hit-testing, and
// P5's swap input reuses `point_to_cell` to resolve a tap to a swap endpoint.
#import "modules/std.sx";
#import "modules/math";
#import "modules/ui/types.sx";
#import "board.sx";
BoardLayout :: struct {
cell_size: f32;
origin: Point;
// Center a square 8×8 grid inside the safe-area-inset region of `frame`.
compute :: (self: *BoardLayout, frame: Frame, safe: EdgeInsets) {
avail := frame.inset(safe);
cols : f32 = xx BOARD_COLS;
board_dim := min(avail.size.width, avail.size.height);
self.cell_size = board_dim / cols;
total := self.cell_size * cols;
self.origin = Point.{
x = avail.origin.x + (avail.size.width - total) * 0.5,
y = avail.origin.y + (avail.size.height - total) * 0.5
};
}
cell_frame :: (self: *BoardLayout, col: s64, row: s64) -> Frame {
Frame.make(
self.origin.x + xx col * self.cell_size,
self.origin.y + xx row * self.cell_size,
self.cell_size,
self.cell_size
)
}
// Inverse of `cell_frame`: map a view-local point to the grid cell under it,
// or null when the point falls outside the 8×8 grid. The `< 0.0` guards run
// BEFORE the truncating cast, since casting a small negative float rounds
// toward zero into a valid index. Uses the SAME origin / cell_size `compute`
// produced, so a tap resolves to exactly the cell drawn under the finger.
point_to_cell :: (self: *BoardLayout, p: Point) -> ?Cell {
if self.cell_size <= 0.0 { return null; }
fx := (p.x - self.origin.x) / self.cell_size;
fy := (p.y - self.origin.y) / self.cell_size;
if fx < 0.0 or fy < 0.0 { return null; }
col : s64 = xx fx;
row : s64 = xx fy;
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
Cell.{ col = col, row = row }
}
}