Files
BlockSeasons/lib/ui/widgets/board_geometry.dart
T
airkjw 3138fc4b08 Add playable core UI: board painter, drag-and-drop, HUD, result overlay
CustomPainter board with gems/ghost/clear-flash, finger-lifted drag
with snap preview, combo text effect, HUD chips, phase overlays with
rescue stubs, demo stage. E2E widget test drives a real drag gesture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:19:34 +09:00

42 lines
1.3 KiB
Dart

import 'dart:math' as math;
import 'dart:ui';
import '../../game/models/grid.dart';
import '../../game/models/piece.dart';
/// Pure mapping between board-local pixels and grid cells.
class BoardGeometry {
const BoardGeometry({required this.boardSize});
final double boardSize;
double get cellSize => boardSize / GridState.size;
Rect cellRect(int x, int y) =>
Rect.fromLTWH(x * cellSize, y * cellSize, cellSize, cellSize);
/// Cell under a local point, or null outside the board.
(int, int)? cellAt(Offset local) {
if (local.dx < 0 || local.dy < 0) return null;
if (local.dx >= boardSize || local.dy >= boardSize) return null;
return (local.dx ~/ cellSize, local.dy ~/ cellSize);
}
/// Nearest anchor for a piece whose visual top-left sits at [pieceTopLeft],
/// clamped so the piece's bounding box stays on the board.
(int, int) snapAnchor(Piece piece, Offset pieceTopLeft) {
var maxDx = 0;
var maxDy = 0;
for (final (dx, dy) in piece.offsets) {
maxDx = math.max(maxDx, dx);
maxDy = math.max(maxDy, dy);
}
final x = (pieceTopLeft.dx / cellSize).round();
final y = (pieceTopLeft.dy / cellSize).round();
return (
x.clamp(0, GridState.size - 1 - maxDx),
y.clamp(0, GridState.size - 1 - maxDy),
);
}
}