// 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 } } }