Files
BlockSeasons/lib/ui/widgets/board_painter.dart
T
airkjw 8739fc0e26 feat: glossy tile rendering and per-season theme colors
Introduces candy-gloss tile rendering (diagonal gradient + glass highlight
+ optional glow) via a shared paintGlossyTile() in tile_painter.dart,
applied to board filled-cells and tray/drag-overlay pieces. Adds
ThemeColors to palette.dart for UI-layer season color resolution, and
activeThemeProvider for one-call access to the active season's theme.
Regenerates the game_screen golden to reflect the new look.
2026-06-11 21:06:26 +09:00

136 lines
3.8 KiB
Dart

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<int> flashRows;
final List<int> 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;
}