import 'package:flutter/material.dart'; import '../../game/models/cell.dart'; import '../../game/models/grid.dart'; import '../../game/models/piece.dart'; import '../theme/palette.dart'; import 'board_geometry.dart'; import 'piece_painter.dart'; import 'tile_painter.dart'; /// Drag ghost preview: a piece hovering at a snapped anchor. class GhostSpec { const GhostSpec({ required this.piece, required this.anchorX, required this.anchorY, required this.legal, }); final Piece piece; final int anchorX; final int anchorY; final bool legal; } class BoardPainter extends CustomPainter { const BoardPainter({ required this.grid, required this.ghost, required this.flashProgress, required this.flashRows, required this.flashCols, }); final GridState grid; final GhostSpec? ghost; /// 1.0 -> 0.0 while the clear flash fades. final double flashProgress; final List flashRows; final List flashCols; @override void paint(Canvas canvas, Size size) { final geo = BoardGeometry(boardSize: size.width); final radius = Radius.circular(geo.cellSize * 0.18); final inset = geo.cellSize * 0.05; final bg = Paint()..color = GamePalette.boardBackground; canvas.drawRRect( RRect.fromRectAndRadius(Offset.zero & size, const Radius.circular(12)), bg, ); for (var y = 0; y < GridState.size; y++) { for (var x = 0; x < GridState.size; x++) { final rect = geo.cellRect(x, y).deflate(inset); final cell = grid.cellAt(x, y); switch (cell.type) { case CellType.empty: canvas.drawRRect( RRect.fromRectAndRadius(rect, radius), Paint()..color = GamePalette.emptyCell, ); case CellType.filled: paintGlossyTile(canvas, rect, GamePalette.tile(cell.colorId)); case CellType.gem: canvas.drawRRect( RRect.fromRectAndRadius(rect, radius), Paint()..color = GamePalette.emptyCell, ); _paintGem(canvas, rect); } } } final g = ghost; if (g != null) { paintPiece( canvas, g.piece, cellSize: geo.cellSize, origin: Offset(g.anchorX * geo.cellSize, g.anchorY * geo.cellSize), overrideColor: g.legal ? GamePalette.ghostLegal : GamePalette.ghostIllegal, ); } if (flashProgress > 0) { final flash = Paint() ..color = Colors.white.withValues(alpha: 0.75 * flashProgress); for (final y in flashRows) { canvas.drawRect( Rect.fromLTWH(0, y * geo.cellSize, size.width, geo.cellSize), flash, ); } for (final x in flashCols) { canvas.drawRect( Rect.fromLTWH(x * geo.cellSize, 0, geo.cellSize, size.height), flash, ); } } } void _paintGem(Canvas canvas, Rect rect) { final glowPaint = Paint() ..color = GamePalette.gem.withValues(alpha: 0.45) ..maskFilter = MaskFilter.blur(BlurStyle.normal, rect.width * 0.25); canvas.drawCircle(rect.center, rect.width * 0.32, glowPaint); final center = rect.center; final r = rect.width * 0.32; final path = Path() ..moveTo(center.dx, center.dy - r) ..lineTo(center.dx + r, center.dy) ..lineTo(center.dx, center.dy + r) ..lineTo(center.dx - r, center.dy) ..close(); canvas.drawPath(path, Paint()..color = GamePalette.gem); canvas.drawPath( path, Paint() ..color = Colors.white.withValues(alpha: 0.6) ..style = PaintingStyle.stroke ..strokeWidth = rect.width * 0.05, ); } @override bool shouldRepaint(BoardPainter old) => old.grid != grid || old.ghost != ghost || old.flashProgress != flashProgress; }