import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:jeweled/config/default_global_settings.dart'; import 'package:jeweled/models/game.dart'; import 'package:jeweled/models/cell_location.dart'; import 'package:jeweled/utils/color_extensions.dart'; import 'package:jeweled/utils/color_theme.dart'; class GameBoardPainter extends CustomPainter { const GameBoardPainter({ required this.game, required this.animations, }); final Game game; final List<List<Animation<double>?>> animations; @override void paint(Canvas canvas, Size size) { int graphicTheme = game.globalSettings.graphicTheme; final double canvasSize = min(size.width, size.height); const double cellBorderWidth = 2; const double boardBorderWidth = 3; switch (graphicTheme) { case DefaultGlobalSettings.graphicThemeSolidBackground: drawBoard( canvas: canvas, canvasSize: canvasSize, game: game, ); break; case DefaultGlobalSettings.graphicThemeGradientAndBorder: drawBoard( canvas: canvas, canvasSize: canvasSize, game: game, borderWidth: cellBorderWidth, gradientFrom: -10, gradientTo: 10, ); break; case DefaultGlobalSettings.graphicThemeEmojis: drawBoard( canvas: canvas, canvasSize: canvasSize, game: game, contentStrings: DefaultGlobalSettings.graphicThemeContentEmojiStrings, ); break; case DefaultGlobalSettings.graphicThemePatterns: drawBoard( canvas: canvas, canvasSize: canvasSize, game: game, contentStrings: DefaultGlobalSettings.graphicThemeContentPatternStrings, ); break; default: } // board borders final boardPaintBorder = Paint(); boardPaintBorder.color = ColorTheme.getBorderColor(); boardPaintBorder.strokeWidth = boardBorderWidth; boardPaintBorder.strokeCap = StrokeCap.round; const Offset boardTopLeft = Offset(0, 0); final Offset boardTopRight = Offset(canvasSize, 0); final Offset boardBottomLeft = Offset(0, canvasSize); final Offset boardBottomRight = Offset(canvasSize, canvasSize); canvas.drawLine(boardTopLeft, boardTopRight, boardPaintBorder); canvas.drawLine(boardTopRight, boardBottomRight, boardPaintBorder); canvas.drawLine(boardBottomRight, boardBottomLeft, boardPaintBorder); canvas.drawLine(boardBottomLeft, boardTopLeft, boardPaintBorder); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } void drawBoard({ required Canvas canvas, required double canvasSize, required Game game, double overlapping = 1, int gradientFrom = 0, int gradientTo = 0, double borderWidth = 0, List<String>? contentStrings, }) { final int cellsCountHorizontal = game.gameSettings.boardSize; final int cellsCountVertical = game.gameSettings.boardSize; final int colorsTheme = game.globalSettings.colorsTheme; final double size = canvasSize / max(cellsCountHorizontal, cellsCountVertical); for (int row = 0; row < cellsCountVertical; row++) { final double yOrigin = size * row; for (int col = 0; col < cellsCountHorizontal; col++) { final double x = size * col; final CellLocation cellLocation = CellLocation.go(row, col); final int? cellValue = game.getCellValueShuffled(cellLocation); if (cellValue != null) { final Animation<double>? translation = animations[row][col]; final double y = yOrigin + (translation?.value ?? 0) * size; drawCell( canvas: canvas, x: x, y: y, cellSize: size, cellValue: cellValue, colorsTheme: colorsTheme, overlapping: overlapping, gradientFrom: gradientFrom, gradientTo: gradientTo, borderWidth: borderWidth, contentStrings: contentStrings, ); } } } } void drawCell({ required Canvas canvas, required double x, required double y, required double cellSize, required int cellValue, required int colorsTheme, required double overlapping, required int gradientFrom, required int gradientTo, required double borderWidth, required List<String>? contentStrings, }) { final Color baseColor = ColorTheme.getColor(cellValue, colorsTheme); final paint = Paint(); // draw background final Rect rect = Rect.fromLTWH(x, y, cellSize + overlapping, cellSize + overlapping); // solid or gradient if (gradientFrom == 0 && gradientTo == 0) { paint.color = baseColor; } else { paint.shader = ui.Gradient.linear( rect.topLeft, rect.bottomCenter, [ (gradientFrom < 0) ? baseColor.lighten(-gradientFrom) : baseColor.darken(gradientFrom), (gradientTo < 0) ? baseColor.lighten(-gradientTo) : baseColor.darken(gradientTo), ], ); } paint.style = PaintingStyle.fill; canvas.drawRect(rect, paint); // draw border if (borderWidth != 0) { final Rect border = Rect.fromLTWH(x + borderWidth, y + borderWidth, cellSize + overlapping - 2 * borderWidth, cellSize + overlapping - 2 * borderWidth); final paintBorder = Paint(); paintBorder.color = baseColor.darken(20); paintBorder.style = PaintingStyle.stroke; paintBorder.strokeWidth = borderWidth; paintBorder.strokeJoin = StrokeJoin.round; canvas.drawRect(border, paintBorder); } // draw content if (contentStrings != null) { final int emojiIndex = cellValue - 1 + game.shuffledColors[0] + 2 * game.shuffledColors[1]; final String text = contentStrings[emojiIndex % contentStrings.length]; final textSpan = TextSpan( text: text, style: TextStyle( color: Colors.black, fontSize: 4 + cellSize / 2, fontWeight: FontWeight.bold, ), ); final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint( canvas, Offset( x + (cellSize - textPainter.width) * 0.5, y + (cellSize - textPainter.height) * 0.5, ), ); } } }