Files
m3te/board_layout.sx
swipelab 6f7d2f4db2 lang migration: rename signed integer types sN -> iN
Mechanical sweep of all .sx sources, plan docs, and tests/expected
snapshots for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64).
Verified: tools/run_tests.sh 23/23.

Note: the ios-sim build has 2 pre-existing 'restart' dot-call errors
from the sx opt-in UFCS change (sx a47ea14) — independent of this
rename (present pre-sweep); migrated in the follow-up commit.
2026-06-12 09:36:51 +03:00

96 lines
3.9 KiB
Plaintext
Raw Permalink 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: i64, row: i64) -> 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 : i64 = xx fx;
row : i64 = xx fy;
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
Cell.{ col = col, row = row }
}
// Frame of the whole 8×8 grid (origin + cols/rows × cell_size). The banner
// and its dimming overlay are sized off this so they cover exactly the board.
grid_frame :: (self: *BoardLayout) -> Frame {
Frame.make(
self.origin.x, self.origin.y,
self.cell_size * cast(f32) BOARD_COLS,
self.cell_size * cast(f32) BOARD_ROWS
)
}
// Win/lose banner geometry (P7.2): an overlay panel centered over the board
// grid, with the title band and the restart button inside it. Derived purely
// from the SAME grid layout the gems use, so the restart hit-test in
// BoardView.handle_event lands on exactly the button BoardView draws. The
// headless banner_layout test locks the button-rect ↔ hit-test round-trip.
banner :: (self: *BoardLayout) -> BannerLayout {
grid := self.grid_frame();
cx := grid.mid_x();
cy := grid.mid_y();
panel_w := grid.size.width * 0.84;
panel_h := grid.size.height * 0.44;
panel := Frame.make(cx - panel_w * 0.5, cy - panel_h * 0.5, panel_w, panel_h);
title := Frame.make(panel.origin.x, panel.origin.y + panel_h * 0.18, panel_w, panel_h * 0.30);
btn_w := panel_w * 0.60;
btn_h := panel_h * 0.24;
btn_y := panel.origin.y + panel_h - btn_h - panel_h * 0.16;
button := Frame.make(cx - btn_w * 0.5, btn_y, btn_w, btn_h);
BannerLayout.{ panel = panel, title = title, button = button }
}
}
// Resolved rectangles of the win/lose banner: the centered `panel`, the `title`
// band where the win/lose headline is centered, and the restart `button` rect
// (also the hit-test target). All in the same view-local space as BoardLayout.
BannerLayout :: struct {
panel: Frame;
title: Frame;
button: Frame;
}