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).
53 lines
2.2 KiB
Plaintext
53 lines
2.2 KiB
Plaintext
// 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 }
|
||
}
|
||
}
|