From 244e88bc6e5dc61ff815023dcf8acc018752f15e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Fri, 1 Nov 2024 12:21:37 +0100 Subject: [PATCH 1/5] Add a new setting to show a border around the board --- example/lib/main.dart | 24 +- lib/src/board_settings.dart | 54 ++++ lib/src/widgets/background.dart | 66 +---- lib/src/widgets/board.dart | 110 +++++--- lib/src/widgets/coordinate.dart | 151 +++++++++++ test/widgets/board_test.dart | 449 ++++++++++++++++++++------------ 6 files changed, 587 insertions(+), 267 deletions(-) create mode 100644 lib/src/widgets/coordinate.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 7460bef..91b3e33 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -74,6 +74,7 @@ class _HomePageState extends State { Mode playMode = Mode.botPlay; Position? lastPos; ISet shapes = ISet(); + bool showBorder = false; @override Widget build(BuildContext context) { @@ -170,8 +171,8 @@ class _HomePageState extends State { mainAxisSize: MainAxisSize.max, children: [ ElevatedButton( - child: Text( - 'Piece shift method: ${pieceShiftMethodLabel(pieceShiftMethod)}'), + child: + Text('Piece Shift: ${pieceShiftMethodLabel(pieceShiftMethod)}'), onPressed: () => _showChoicesPicker( context, choices: PieceShiftMethod.values, @@ -187,6 +188,14 @@ class _HomePageState extends State { ), ), const SizedBox(width: 8), + ElevatedButton( + child: Text("Show border: ${showBorder ? 'ON' : 'OFF'}"), + onPressed: () { + setState(() { + showBorder = !showBorder; + }); + }, + ), ], ), if (playMode == Mode.freePlay) @@ -291,6 +300,12 @@ class _HomePageState extends State { settings: ChessboardSettings( pieceAssets: pieceSet.assets, colorScheme: boardTheme.colors, + border: showBorder + ? BoardBorder( + width: 16.0, + color: _darken(boardTheme.colors.darkSquare, 0.2), + ) + : null, enableCoordinates: true, animationDuration: pieceAnimation ? const Duration(milliseconds: 200) @@ -505,3 +520,8 @@ class _HomePageState extends State { (move.to.rank == Rank.eighth && position.turn == Side.white)); } } + +Color _darken(Color c, [double amount = .1]) { + assert(amount >= 0 && amount <= 1); + return Color.lerp(c, const Color(0xFF000000), amount) ?? c; +} diff --git a/lib/src/board_settings.dart b/lib/src/board_settings.dart index f7c461e..fae3f32 100644 --- a/lib/src/board_settings.dart +++ b/lib/src/board_settings.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'board_color_scheme.dart'; @@ -29,6 +30,47 @@ enum PieceOrientationBehavior { sideToPlay, } +@immutable + +/// Describes the border of the board. +class BoardBorder { + /// Creates a new border with the provided values. + const BoardBorder({ + required this.color, + required this.width, + }); + + /// Color of the border + final Color color; + + /// Width of the border + final double width; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is BoardBorder && other.color == color && other.width == width; + } + + @override + int get hashCode => Object.hash(color, width); + + BoardBorder copyWith({ + Color? color, + double? width, + }) { + return BoardBorder( + color: color ?? this.color, + width: width ?? this.width, + ); + } +} + /// Board settings that controls visual aspects and behavior of the board. /// /// This is meant for fixed settings that don't change during a game. Sensible @@ -40,6 +82,7 @@ class ChessboardSettings { this.colorScheme = ChessboardColorScheme.brown, this.pieceAssets = PieceSet.cburnettAssets, // visual settings + this.border, this.borderRadius = BorderRadius.zero, this.boxShadow = const [], this.enableCoordinates = true, @@ -67,6 +110,9 @@ class ChessboardSettings { /// Piece set final PieceAssets pieceAssets; + /// Optional border of the board + final BoardBorder? border; + /// Border radius of the board final BorderRadiusGeometry borderRadius; @@ -125,6 +171,7 @@ class ChessboardSettings { return other is ChessboardSettings && other.colorScheme == colorScheme && other.pieceAssets == pieceAssets && + other.border == border && other.borderRadius == borderRadius && other.boxShadow == boxShadow && other.enableCoordinates == enableCoordinates && @@ -146,6 +193,7 @@ class ChessboardSettings { int get hashCode => Object.hash( colorScheme, pieceAssets, + border, borderRadius, boxShadow, enableCoordinates, @@ -219,6 +267,7 @@ class ChessboardEditorSettings { this.colorScheme = ChessboardColorScheme.brown, this.pieceAssets = PieceSet.cburnettAssets, // visual settings + this.border, this.borderRadius = BorderRadius.zero, this.boxShadow = const [], this.enableCoordinates = true, @@ -232,6 +281,9 @@ class ChessboardEditorSettings { /// Piece set. final PieceAssets pieceAssets; + /// Optional border of the board + final BoardBorder? border; + /// Border radius of the board. final BorderRadiusGeometry borderRadius; @@ -258,6 +310,7 @@ class ChessboardEditorSettings { return other is ChessboardEditorSettings && other.colorScheme == colorScheme && other.pieceAssets == pieceAssets && + other.border == border && other.borderRadius == borderRadius && other.boxShadow == boxShadow && other.enableCoordinates == enableCoordinates && @@ -269,6 +322,7 @@ class ChessboardEditorSettings { int get hashCode => Object.hash( colorScheme, pieceAssets, + border, borderRadius, boxShadow, enableCoordinates, diff --git a/lib/src/widgets/background.dart b/lib/src/widgets/background.dart index 1142218..92fc93d 100644 --- a/lib/src/widgets/background.dart +++ b/lib/src/widgets/background.dart @@ -1,3 +1,4 @@ +import 'package:chessground/src/widgets/coordinate.dart'; import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; @@ -156,7 +157,7 @@ class ImageChessboardBackground extends ChessboardBackground { child: SizedBox( width: squareSize, height: squareSize, - child: _Coordinate( + child: InnerBoardCoordinate( rank: rank, file: file, orientation: orientation, @@ -174,66 +175,3 @@ class ImageChessboardBackground extends ChessboardBackground { } } } - -class _Coordinate extends StatelessWidget { - const _Coordinate({ - required this.rank, - required this.file, - required this.color, - required this.orientation, - }); - - final int rank; - final int file; - final Color color; - final Side orientation; - - @override - Widget build(BuildContext context) { - final coordStyle = TextStyle( - inherit: false, - fontWeight: FontWeight.bold, - fontSize: 10.0, - color: color, - fontFamily: 'Roboto', - height: 1.0, - ); - return Stack( - alignment: Alignment.topLeft, - children: [ - if (file == 7) - Positioned( - top: 2.0, - right: 2.0, - child: Align( - alignment: Alignment.topRight, - child: Text( - textDirection: TextDirection.ltr, - orientation == Side.white ? '${8 - rank}' : '${rank + 1}', - style: coordStyle, - textScaler: TextScaler.noScaling, - textAlign: TextAlign.center, - ), - ), - ), - if (rank == 7) - Positioned( - bottom: 2.0, - left: 2.0, - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - textDirection: TextDirection.ltr, - orientation == Side.white - ? String.fromCharCode(97 + file) - : String.fromCharCode(97 + 7 - file), - style: coordStyle, - textScaler: TextScaler.noScaling, - textAlign: TextAlign.center, - ), - ), - ), - ], - ); - } -} diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 586b13b..5a1798c 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'coordinate.dart'; import 'piece.dart'; import 'highlight.dart'; import 'positioned_square.dart'; @@ -36,7 +37,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// The [fen] string should be updated when the position changes. const Chessboard({ super.key, - required this.size, + required double size, this.settings = const ChessboardSettings(), required this.orientation, required this.fen, @@ -45,7 +46,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.game, this.shapes, this.annotations, - }); + }) : _size = size; /// Creates a new chessboard widget that cannot be interacted with. /// @@ -53,19 +54,22 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// Pieces will be animated when the position changes. const Chessboard.fixed({ super.key, - required this.size, + required double size, this.settings = const ChessboardSettings(), required this.orientation, required this.fen, this.lastMove, this.shapes, this.annotations, - }) : game = null, + }) : _size = size, + game = null, opponentsPiecesUpsideDown = false; + final double _size; + /// Size of the board in logical pixels. @override - final double size; + double get size => _size - (settings.border?.width ?? 0) * 2; /// Side by which the board is oriented. @override @@ -174,20 +178,21 @@ class _BoardState extends State { @override Widget build(BuildContext context) { - final colorScheme = widget.settings.colorScheme; - final ISet moveDests = widget.settings.showValidMoves && + final settings = widget.settings; + final colorScheme = settings.colorScheme; + final ISet moveDests = settings.showValidMoves && selected != null && widget.game?.validMoves != null ? widget.game?.validMoves[selected!] ?? _emptyValidMoves : _emptyValidMoves; final Set premoveDests = - widget.settings.showValidMoves ? _premoveDests ?? {} : {}; + settings.showValidMoves ? _premoveDests ?? {} : {}; final shapes = widget.shapes ?? _emptyShapes; final annotations = widget.annotations ?? _emptyAnnotations; final checkSquare = widget.game?.isCheck == true ? _getKingSquare() : null; final premove = widget.game?.premovable?.premove; - final background = widget.settings.enableCoordinates + final background = settings.border == null && settings.enableCoordinates ? widget.orientation == Side.white ? colorScheme.whiteCoordBackground : colorScheme.blackCoordBackground @@ -199,7 +204,7 @@ class _BoardState extends State { dimension: widget.size, child: background, ), - if (widget.settings.showLastMove && widget.lastMove != null) + if (settings.showLastMove && widget.lastMove != null) for (final square in widget.lastMove!.squares) if (premove == null || !premove.hasSquare(square)) PositionedSquare( @@ -271,11 +276,11 @@ class _BoardState extends State { orientation: widget.orientation, square: entry.key, child: AnimatedPieceFadeOut( - duration: widget.settings.animationDuration, + duration: settings.animationDuration, piece: entry.value, size: widget.squareSize, - pieceAssets: widget.settings.pieceAssets, - blindfoldMode: widget.settings.blindfoldMode, + pieceAssets: settings.pieceAssets, + blindfoldMode: settings.blindfoldMode, upsideDown: _isUpsideDown(entry.value.color), onComplete: () { fadingPieces.remove(entry.key); @@ -294,8 +299,8 @@ class _BoardState extends State { child: PieceWidget( piece: entry.value, size: widget.squareSize, - pieceAssets: widget.settings.pieceAssets, - blindfoldMode: widget.settings.blindfoldMode, + pieceAssets: settings.pieceAssets, + blindfoldMode: settings.blindfoldMode, upsideDown: _isUpsideDown(entry.value.color), ), ), @@ -309,15 +314,15 @@ class _BoardState extends State { fromSquare: entry.value.from, toSquare: entry.key, orientation: widget.orientation, - duration: widget.settings.animationDuration, + duration: settings.animationDuration, onComplete: () { translatingPieces.remove(entry.key); }, child: PieceWidget( piece: entry.value.piece, size: widget.squareSize, - pieceAssets: widget.settings.pieceAssets, - blindfoldMode: widget.settings.blindfoldMode, + pieceAssets: settings.pieceAssets, + blindfoldMode: settings.blindfoldMode, upsideDown: _isUpsideDown(entry.value.piece.color), ), ), @@ -346,28 +351,29 @@ class _BoardState extends State { ), ]; - final enableListeners = - widget.interactive || widget.settings.drawShape.enable; + final enableListeners = widget.interactive || settings.drawShape.enable; - return Listener( + final board = Listener( onPointerDown: enableListeners ? _onPointerDown : null, onPointerMove: enableListeners ? _onPointerMove : null, onPointerUp: enableListeners ? _onPointerUp : null, onPointerCancel: enableListeners ? _onPointerCancel : null, child: SizedBox.square( + key: const ValueKey('board-container'), dimension: widget.size, child: Stack( alignment: Alignment.topLeft, clipBehavior: Clip.none, children: [ - if (widget.settings.boxShadow.isNotEmpty || - widget.settings.borderRadius != BorderRadius.zero) + if (settings.border == null && + (settings.boxShadow.isNotEmpty || + settings.borderRadius != BorderRadius.zero)) Container( key: const ValueKey('background-container'), clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - borderRadius: widget.settings.borderRadius, - boxShadow: widget.settings.boxShadow, + borderRadius: settings.borderRadius, + boxShadow: settings.boxShadow, ), child: Stack( alignment: Alignment.topLeft, @@ -379,7 +385,7 @@ class _BoardState extends State { ...objects, if (widget.game?.promotionMove != null) PromotionSelector( - pieceAssets: widget.settings.pieceAssets, + pieceAssets: settings.pieceAssets, move: widget.game!.promotionMove!, size: widget.size, color: widget.game!.sideToMove, @@ -394,6 +400,42 @@ class _BoardState extends State { ), ), ); + + if (settings.border != null) { + return Container( + width: widget.size + settings.border!.width * 2, + height: widget.size + settings.border!.width * 2, + color: settings.border!.color, + child: Stack( + alignment: Alignment.center, + children: [ + board, + if (settings.enableCoordinates) + Positioned( + top: settings.border!.width, + left: 0, + child: BorderRankCoordinates( + orientation: widget.orientation, + width: settings.border!.width, + height: widget.size, + ), + ), + if (settings.enableCoordinates) + Positioned( + bottom: 0, + left: settings.border!.width, + child: BorderFileCoordinates( + orientation: widget.orientation, + width: widget.size, + height: settings.border!.width, + ), + ), + ], + ), + ); + } + + return board; } @override @@ -506,8 +548,12 @@ class _BoardState extends State { final localOffset = widget.squareOffset(square); final tmpOffset = box.localToGlobal(localOffset); return Offset( - tmpOffset.dx - widget.squareSize / 2, - tmpOffset.dy - widget.squareSize / 2, + (widget.settings.border?.width ?? 0) + + tmpOffset.dx - + widget.squareSize / 2, + (widget.settings.border?.width ?? 0) + + tmpOffset.dy - + widget.squareSize / 2, ); } @@ -691,7 +737,9 @@ class _BoardState extends State { } if (_currentPointerDownEvent == null || - _currentPointerDownEvent!.pointer != details.pointer) return; + _currentPointerDownEvent!.pointer != details.pointer) { + return; + } final square = widget.offsetSquare(details.localPosition); @@ -762,7 +810,9 @@ class _BoardState extends State { } if (_currentPointerDownEvent == null || - _currentPointerDownEvent!.pointer != details.pointer) return; + _currentPointerDownEvent!.pointer != details.pointer) { + return; + } _onDragEnd(); setState(() { diff --git a/lib/src/widgets/coordinate.dart b/lib/src/widgets/coordinate.dart new file mode 100644 index 0000000..520075d --- /dev/null +++ b/lib/src/widgets/coordinate.dart @@ -0,0 +1,151 @@ +import 'package:dartchess/dartchess.dart'; +import 'package:flutter/widgets.dart'; + +const _coordStyle = TextStyle( + inherit: false, + fontWeight: FontWeight.bold, + fontSize: 12.0, + fontFamily: 'Roboto', + height: 1.0, + color: Color(0x99FFFFFF), +); + +class BorderRankCoordinates extends StatelessWidget { + const BorderRankCoordinates({ + required this.orientation, + required this.width, + required this.height, + super.key, + }); + + final Side orientation; + final double width; + final double height; + + @override + Widget build(BuildContext context) { + final ranks = orientation == Side.white ? '87654321' : '12345678'; + return SizedBox( + width: width, + height: height, + child: Column( + textDirection: TextDirection.ltr, + children: [ + for (final rank in ranks.split('')) + Expanded( + child: Center( + child: Text( + rank, + style: _coordStyle, + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ), + ), + ), + ], + ), + ); + } +} + +class BorderFileCoordinates extends StatelessWidget { + const BorderFileCoordinates({ + required this.orientation, + required this.width, + required this.height, + super.key, + }); + + final Side orientation; + final double width; + final double height; + + @override + Widget build(BuildContext context) { + final files = orientation == Side.white ? 'abcdefgh' : 'hgfedcba'; + return SizedBox( + height: height, + width: width, + child: Row( + textDirection: TextDirection.ltr, + children: [ + for (final file in files.split('')) + Expanded( + child: Center( + child: Text( + file, + style: _coordStyle, + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ), + ), + ), + ], + ), + ); + } +} + +class InnerBoardCoordinate extends StatelessWidget { + const InnerBoardCoordinate({ + required this.rank, + required this.file, + required this.color, + required this.orientation, + super.key, + }); + + final int rank; + final int file; + final Color color; + final Side orientation; + + @override + Widget build(BuildContext context) { + final coordStyle = TextStyle( + inherit: false, + fontWeight: FontWeight.bold, + fontSize: 10.0, + color: color, + fontFamily: 'Roboto', + height: 1.0, + ); + return Stack( + alignment: Alignment.topLeft, + children: [ + if (file == 7) + Positioned( + top: 2.0, + right: 2.0, + child: Align( + alignment: Alignment.topRight, + child: Text( + textDirection: TextDirection.ltr, + orientation == Side.white ? '${8 - rank}' : '${rank + 1}', + style: coordStyle, + textScaler: TextScaler.noScaling, + textAlign: TextAlign.center, + ), + ), + ), + if (rank == 7) + Positioned( + bottom: 2.0, + left: 2.0, + child: Align( + alignment: Alignment.bottomLeft, + child: Text( + textDirection: TextDirection.ltr, + orientation == Side.white + ? String.fromCharCode(97 + file) + : String.fromCharCode(97 + 7 - file), + style: coordStyle, + textScaler: TextScaler.noScaling, + textAlign: TextAlign.center, + ), + ), + ), + ], + ); + } +} diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index a905fb9..883fbf9 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -47,103 +47,157 @@ void main() { expect(size.width, boardSize); expect(size.height, boardSize); }); + + testWidgets('displays a border', (WidgetTester tester) async { + const board = Chessboard.fixed( + size: boardSize, + orientation: Side.white, + fen: kInitialFEN, + settings: ChessboardSettings( + drawShape: DrawShapeOptions(enable: true), + border: BoardBorder( + width: 16.0, + color: Color(0xFF000000), + ), + ), + ); + + await tester.pumpWidget(board); + + final size = tester.getSize( + find.byType(SolidColorChessboardBackground), + ); + expect(size.width, boardSize - 32.0); + expect(size.height, boardSize - 32.0); + }); }); group('Interactive board', () { testWidgets('selecting and deselecting a square', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp(initialPlayerSide: PlayerSide.both), - ); - await tester.tap(find.byKey(const Key('a2-whitepawn'))); - await tester.pump(); + for (final settings in [ + const ChessboardSettings(), + const ChessboardSettings( + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), + ), + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + settings: settings, + key: ValueKey(settings.hashCode), + ), + ); + await tester.tap(find.byKey(const Key('a2-whitepawn'))); + await tester.pump(); - expect(find.byKey(const Key('a2-selected')), findsOneWidget); - expect(find.byType(ValidMoveHighlight), findsNWidgets(2)); + expect(find.byKey(const Key('a2-selected')), findsOneWidget); + expect(find.byType(ValidMoveHighlight), findsNWidgets(2)); - // selecting same deselects - await tester.tap(find.byKey(const Key('a2-whitepawn'))); - await tester.pump(); - expect(find.byKey(const Key('a2-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + // selecting same deselects + await tester.tap(find.byKey(const Key('a2-whitepawn'))); + await tester.pump(); + expect(find.byKey(const Key('a2-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); - // selecting another square - await tester.tap(find.byKey(const Key('a1-whiterook'))); - await tester.pump(); - expect(find.byKey(const Key('a1-selected')), findsOneWidget); - expect(find.byType(ValidMoveHighlight), findsNothing); + // selecting another square + await tester.tap(find.byKey(const Key('a1-whiterook'))); + await tester.pump(); + expect(find.byKey(const Key('a1-selected')), findsOneWidget); + expect(find.byType(ValidMoveHighlight), findsNothing); - // selecting an opposite piece deselects - await tester.tap(find.byKey(const Key('e7-blackpawn'))); - await tester.pump(); - expect(find.byKey(const Key('a1-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + // selecting an opposite piece deselects + await tester.tap(find.byKey(const Key('e7-blackpawn'))); + await tester.pump(); + expect(find.byKey(const Key('a1-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); - // selecting an empty square deselects - await tester.tap(find.byKey(const Key('a1-whiterook'))); - await tester.pump(); - expect(find.byKey(const Key('a1-selected')), findsOneWidget); - await tester.tapAt(squareOffset(Square.c4)); - await tester.pump(); - expect(find.byKey(const Key('a1-selected')), findsNothing); + // selecting an empty square deselects + await tester.tap(find.byKey(const Key('a1-whiterook'))); + await tester.pump(); + expect(find.byKey(const Key('a1-selected')), findsOneWidget); + await tester.tapAt(squareOffset(tester, Square.c4)); + await tester.pump(); + expect(find.byKey(const Key('a1-selected')), findsNothing); - // cannot select a piece whose side is not the turn to move - await tester.tap(find.byKey(const Key('e7-blackpawn'))); - await tester.pump(); - expect(find.byKey(const Key('e7-selected')), findsNothing); + // cannot select a piece whose side is not the turn to move + await tester.tap(find.byKey(const Key('e7-blackpawn'))); + await tester.pump(); + expect(find.byKey(const Key('e7-selected')), findsNothing); + } }); testWidgets('play e2-e4 move by tap', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp(initialPlayerSide: PlayerSide.both), - ); - await tester.tap(find.byKey(const Key('e2-whitepawn'))); - await tester.pump(); + for (final settings in [ + const ChessboardSettings(), + const ChessboardSettings( + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), + ), + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + settings: settings, + key: ValueKey(settings.hashCode), + ), + ); + await tester.tap(find.byKey(const Key('e2-whitepawn'))); + await tester.pump(); - expect(find.byKey(const Key('e2-selected')), findsOneWidget); - expect(find.byType(ValidMoveHighlight), findsNWidgets(2)); + expect(find.byKey(const Key('e2-selected')), findsOneWidget); + expect(find.byType(ValidMoveHighlight), findsNWidgets(2)); - await tester.tapAt(squareOffset(Square.e4)); - await tester.pump(); + await tester.tapAt(squareOffset(tester, Square.e4)); + await tester.pump(); - expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); - // wait for the animations to finish - await tester.pumpAndSettle(); + // wait for the animations to finish + await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitepawn')), findsNothing); - expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); - expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); + expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + } }); testWidgets('Cannot move by tap if piece shift method is drag', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp( - initialPlayerSide: PlayerSide.both, + for (final settings in [ + const ChessboardSettings(pieceShiftMethod: PieceShiftMethod.drag), + const ChessboardSettings( pieceShiftMethod: PieceShiftMethod.drag, + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), ), - ); - await tester.tap(find.byKey(const Key('e2-whitepawn'))); - await tester.pump(); + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + settings: settings, + key: ValueKey(settings.hashCode), + ), + ); + await tester.tap(find.byKey(const Key('e2-whitepawn'))); + await tester.pump(); - // Tapping a square should have no effect... - expect(find.byKey(const Key('e2-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + // Tapping a square should have no effect... + expect(find.byKey(const Key('e2-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); - // ... but move by drag should work - await tester.dragFrom( - squareOffset(Square.e2), - const Offset(0, -(squareSize * 2)), - ); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitepawn')), findsNothing); - expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); - expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + // ... but move by drag should work + await tester.dragFrom( + squareOffset(tester, Square.e2), + const Offset(0, -(squareSize * 2)), + ); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); + expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + } }); testWidgets('castling by selecting king then rook is possible', @@ -172,46 +226,78 @@ void main() { }); testWidgets('dragging off target', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp(initialPlayerSide: PlayerSide.both), - ); + for (final settings in [ + const ChessboardSettings(), + const ChessboardSettings( + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), + ), + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + settings: settings, + key: ValueKey(settings.hashCode), + ), + ); - final e2 = squareOffset(Square.e2); - await tester.dragFrom(e2, const Offset(0, -(squareSize * 4))); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + final e2 = squareOffset(tester, Square.e2); + await tester.dragFrom(e2, const Offset(0, -(squareSize * 4))); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); + } }); testWidgets('dragging off board', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp(initialPlayerSide: PlayerSide.both), - ); + for (final settings in [ + const ChessboardSettings(), + const ChessboardSettings( + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), + ), + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + key: ValueKey(settings.hashCode), + ), + ); - await tester.dragFrom( - squareOffset(Square.e2), - squareOffset(Square.e2) + const Offset(0, -boardSize + squareSize), - ); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-selected')), findsNothing); - expect(find.byType(ValidMoveHighlight), findsNothing); + await tester.dragFrom( + squareOffset(tester, Square.e2), + squareOffset(tester, Square.e2) + + const Offset(0, -boardSize + squareSize), + ); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('e2-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-selected')), findsNothing); + expect(find.byType(ValidMoveHighlight), findsNothing); + } }); testWidgets('e2-e4 drag move', (WidgetTester tester) async { - await tester.pumpWidget( - const _TestApp(initialPlayerSide: PlayerSide.both), - ); - await tester.dragFrom( - squareOffset(Square.e2), - const Offset(0, -(squareSize * 2)), - ); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); - expect(find.byKey(const Key('e2-whitepawn')), findsNothing); - expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); - expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + for (final settings in [ + const ChessboardSettings(), + const ChessboardSettings( + border: BoardBorder(width: 16.0, color: Color(0xFF000000)), + ), + ]) { + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.both, + key: ValueKey(settings.hashCode), + ), + ); + await tester.dragFrom( + squareOffset(tester, Square.e2), + const Offset(0, -(squareSize * 2)), + ); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('e4-whitepawn')), findsOneWidget); + expect(find.byKey(const Key('e2-whitepawn')), findsNothing); + expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); + expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + } }); testWidgets('Cannot move by drag if piece shift method is tapTwoSquares', ( @@ -227,7 +313,7 @@ void main() { ), ); await tester.dragFrom( - squareOffset(Square.e2), + squareOffset(tester, Square.e2), const Offset(0, -(squareSize * 2)), ); await tester.pumpAndSettle(); @@ -241,7 +327,7 @@ void main() { expect(find.byType(ValidMoveHighlight), findsNWidgets(2)); // ...so we can still tap to move - await tester.tapAt(squareOffset(Square.e4)); + await tester.tapAt(squareOffset(tester, Square.e4)); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); expect(find.byType(ValidMoveHighlight), findsNothing); @@ -260,13 +346,13 @@ void main() { const _TestApp(initialPlayerSide: PlayerSide.both), ); await TestAsyncUtils.guard(() async { - await tester.startGesture(squareOffset(Square.e2)); + await tester.startGesture(squareOffset(tester, Square.e2)); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); - await tester.startGesture(squareOffset(Square.e4)); + await tester.startGesture(squareOffset(tester, Square.e4)); await tester.pump(); @@ -287,7 +373,8 @@ void main() { // drag a piece and tap on another own square while dragging await TestAsyncUtils.guard(() async { - final dragGesture = await tester.startGesture(squareOffset(Square.e2)); + final dragGesture = + await tester.startGesture(squareOffset(tester, Square.e2)); await tester.pump(); // trigger a piece drag by moving the pointer by 4 pixels @@ -301,7 +388,7 @@ void main() { await tester.tap(find.byKey(const Key('d2-whitepawn'))); // finish the move as to release the piece - await dragGesture.moveTo(squareOffset(Square.e4)); + await dragGesture.moveTo(squareOffset(tester, Square.e4)); await dragGesture.up(); }); @@ -315,7 +402,8 @@ void main() { // drag a piece and tap on an empty square while dragging await TestAsyncUtils.guard(() async { - final dragGesture = await tester.startGesture(squareOffset(Square.d2)); + final dragGesture = + await tester.startGesture(squareOffset(tester, Square.d2)); await tester.pump(); // trigger a piece drag by moving the pointer by 4 pixels @@ -327,10 +415,10 @@ void main() { expect(find.byKey(const Key('d2-selected')), findsOneWidget); // tap on an empty square - await tester.tapAt(squareOffset(Square.f5)); + await tester.tapAt(squareOffset(tester, Square.f5)); // finish the move as to release the piece - await dragGesture.moveTo(squareOffset(Square.d4)); + await dragGesture.moveTo(squareOffset(tester, Square.d4)); await dragGesture.up(); }); @@ -349,7 +437,7 @@ void main() { await tester.pumpWidget( const _TestApp(initialPlayerSide: PlayerSide.both), ); - final e2 = squareOffset(Square.e2); + final e2 = squareOffset(tester, Square.e2); await tester.dragFrom(e2, const Offset(0, -(squareSize / 3))); await tester.pumpAndSettle(); @@ -363,7 +451,7 @@ void main() { await tester.pumpWidget( const _TestApp(initialPlayerSide: PlayerSide.both), ); - final e2 = squareOffset(Square.e2); + final e2 = squareOffset(tester, Square.e2); await tester.tapAt(e2); await tester.pump(); final dragFuture = tester.timedDragFrom( @@ -421,7 +509,7 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.e2)); + await tester.tapAt(squareOffset(tester, Square.e2)); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsOneWidget); @@ -450,7 +538,7 @@ void main() { ); await TestAsyncUtils.guard(() async { - await tester.startGesture(squareOffset(Square.f3)); + await tester.startGesture(squareOffset(tester, Square.f3)); await tester.pump(); expect(find.byKey(const Key('f3-selected')), findsOneWidget); }); @@ -462,7 +550,7 @@ void main() { expect(find.byKey(const Key('f3-selected')), findsNothing); // board is not interactive - await tester.tapAt(squareOffset(Square.f3)); + await tester.tapAt(squareOffset(tester, Square.f3)); await tester.pump(); expect(find.byKey(const Key('f3-selected')), findsNothing); @@ -472,7 +560,7 @@ void main() { // the piece selection should work (which would not be the case if the // pointer event was not cancelled) - await tester.tapAt(squareOffset(Square.f3)); + await tester.tapAt(squareOffset(tester, Square.f3)); await tester.pump(); expect(find.byKey(const Key('f3-selected')), findsOneWidget); }); @@ -502,7 +590,7 @@ void main() { await tester.tap(find.byKey(const Key('f7-whitepawn'))); await tester.pump(); - await tester.tapAt(squareOffset(Square.f8)); + await tester.tapAt(squareOffset(tester, Square.f8)); await tester.pump(); // wait for promotion selector to show @@ -513,7 +601,7 @@ void main() { expect(find.byKey(const Key('f7-whitepawn')), findsNothing); // tap on the knight - await tester.tapAt(squareOffset(Square.f7)); + await tester.tapAt(squareOffset(tester, Square.f7)); await tester.pump(); expect(find.byKey(const Key('f8-whiteknight')), findsOneWidget); expect(find.byKey(const Key('f7-whitepawn')), findsNothing); @@ -529,7 +617,7 @@ void main() { await tester.tap(find.byKey(const Key('f7-whitepawn'))); await tester.pump(); - await tester.tapAt(squareOffset(Square.f8)); + await tester.tapAt(squareOffset(tester, Square.f8)); await tester.pump(); // wait for promotion selector to show @@ -537,7 +625,7 @@ void main() { expect(find.byType(PromotionSelector), findsOneWidget); // tap outside the promotion dialog - await tester.tapAt(squareOffset(Square.c4)); + await tester.tapAt(squareOffset(tester, Square.c4)); await tester.pump(); @@ -557,7 +645,7 @@ void main() { await tester.tap(find.byKey(const Key('f7-whitepawn'))); await tester.pump(); - await tester.tapAt(squareOffset(Square.f8)); + await tester.tapAt(squareOffset(tester, Square.f8)); await tester.pump(); expect(find.byKey(const Key('f8-whitequeen')), findsOneWidget); expect(find.byKey(const Key('f7-whitepawn')), findsNothing); @@ -575,12 +663,12 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(7)); - await tester.tapAt(squareOffset(Square.b4)); + await tester.tapAt(squareOffset(tester, Square.b4)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsNothing); expect(find.byType(ValidMoveHighlight), findsNothing); @@ -596,12 +684,12 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(7)); - await tester.tapAt(squareOffset(Square.f8)); + await tester.tapAt(squareOffset(tester, Square.f8)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsNothing); expect(find.byType(ValidMoveHighlight), findsNothing); @@ -617,12 +705,12 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(7)); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsNothing); expect(find.byType(ValidMoveHighlight), findsNothing); @@ -639,7 +727,7 @@ void main() { ), ); - final f1 = squareOffset(Square.f1); + final f1 = squareOffset(tester, Square.f1); await tester.dragFrom(f1, const Offset(0, -(squareSize / 3))); await tester.pumpAndSettle(); @@ -656,14 +744,14 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(7)); await tester.dragFrom( - squareOffset(Square.f1), - squareOffset(Square.f1) + const Offset(0, -squareSize * 3), + squareOffset(tester, Square.f1), + squareOffset(tester, Square.f1) + const Offset(0, -squareSize * 3), ); await tester.pumpAndSettle(); @@ -680,14 +768,15 @@ void main() { ), ); - await tester.tapAt(squareOffset(Square.f1)); + await tester.tapAt(squareOffset(tester, Square.f1)); await tester.pump(); expect(find.byKey(const Key('f1-selected')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(7)); await tester.dragFrom( - squareOffset(Square.f1), - squareOffset(Square.f1) + const Offset(0, -boardSize + squareSize), + squareOffset(tester, Square.f1), + squareOffset(tester, Square.f1) + + const Offset(0, -boardSize + squareSize), ); await tester.pumpAndSettle(); @@ -711,7 +800,7 @@ void main() { expect(find.byKey(const Key('f5-premove')), findsOneWidget); // unset by tapping empty square - await tester.tapAt(squareOffset(Square.c5)); + await tester.tapAt(squareOffset(tester, Square.c5)); await tester.pump(); expect(find.byKey(const Key('e4-premove')), findsNothing); expect(find.byKey(const Key('f5-premove')), findsNothing); @@ -720,7 +809,7 @@ void main() { await makeMove(tester, Square.d1, Square.f3); expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); - await tester.tapAt(squareOffset(Square.g8)); + await tester.tapAt(squareOffset(tester, Square.g8)); await tester.pump(); expect(find.byKey(const Key('d1-premove')), findsNothing); expect(find.byKey(const Key('f3-premove')), findsNothing); @@ -742,8 +831,9 @@ void main() { // unset by dragging off board await tester.dragFrom( - squareOffset(Square.e4), - squareOffset(Square.e4) + const Offset(0, -boardSize + squareSize), + squareOffset(tester, Square.e4), + squareOffset(tester, Square.e4) + + const Offset(0, -boardSize + squareSize), ); await tester.pump(); expect(find.byKey(const Key('e4-premove')), findsNothing); @@ -767,8 +857,8 @@ void main() { // unset by dragging to an empty square await tester.dragFrom( - squareOffset(Square.e4), - squareOffset(Square.e4) + const Offset(0, -squareSize), + squareOffset(tester, Square.e4), + squareOffset(tester, Square.e4) + const Offset(0, -squareSize), ); await tester.pump(); expect(find.byKey(const Key('e4-premove')), findsNothing); @@ -791,7 +881,7 @@ void main() { expect(find.byKey(const Key('f5-premove')), findsOneWidget); // unset by tapping same origin square again - await tester.tapAt(squareOffset(Square.e4)); + await tester.tapAt(squareOffset(tester, Square.e4)); await tester.pump(); expect(find.byKey(const Key('e4-premove')), findsNothing); expect(find.byKey(const Key('f5-premove')), findsNothing); @@ -809,13 +899,13 @@ void main() { await makeMove(tester, Square.d1, Square.f3); expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); - await tester.tapAt(squareOffset(Square.d2)); + await tester.tapAt(squareOffset(tester, Square.d2)); await tester.pump(); // premove is still set expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); expect(find.byType(ValidMoveHighlight), findsNWidgets(4)); - await tester.tapAt(squareOffset(Square.d4)); + await tester.tapAt(squareOffset(tester, Square.d4)); await tester.pump(); // premove is changed expect(find.byKey(const Key('d1-premove')), findsNothing); @@ -837,7 +927,7 @@ void main() { expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('f3-premove')), findsOneWidget); await tester.dragFrom( - squareOffset(Square.d2), + squareOffset(tester, Square.d2), const Offset(0, -squareSize * 2), ); await tester.pump(); @@ -858,7 +948,7 @@ void main() { ); await tester.dragFrom( - squareOffset(Square.e4), + squareOffset(tester, Square.e4), const Offset(0, -squareSize), ); await tester.pumpAndSettle(); @@ -881,7 +971,7 @@ void main() { expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('c2-premove')), findsOneWidget); - await tester.tapAt(squareOffset(Square.e1)); + await tester.tapAt(squareOffset(tester, Square.e1)); await tester.pump(); expect(find.byKey(const Key('d1-premove')), findsOneWidget); expect(find.byKey(const Key('c2-premove')), findsOneWidget); @@ -980,7 +1070,7 @@ void main() { expect(find.byKey(const Key('g7-whitepawn')), findsNothing); // select knight - await tester.tapAt(squareOffset(Square.g7)); + await tester.tapAt(squareOffset(tester, Square.g7)); await tester.pump(); expect(find.byKey(const Key('g8-whiteknight')), findsOneWidget); @@ -1019,7 +1109,7 @@ void main() { expect(find.byKey(const Key('g8-premove')), findsNothing); // cancel promotion dialog - await tester.tapAt(squareOffset(Square.c3)); + await tester.tapAt(squareOffset(tester, Square.c3)); await tester.pump(); // promotion dialog is closed @@ -1101,10 +1191,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset(Square.e4)); + final tapGesture = + await tester.startGesture(squareOffset(tester, Square.e4)); await tapGesture.up(); await pressGesture.up(); @@ -1123,10 +1215,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset(Square.e4)); + final tapGesture = + await tester.startGesture(squareOffset(tester, Square.e4)); await tapGesture.up(); await pressGesture.up(); @@ -1150,10 +1244,11 @@ void main() { ); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); await tester.dragFrom( - squareOffset(Square.e2), + squareOffset(tester, Square.e2), const Offset(0, -(squareSize * 2)), ); @@ -1180,10 +1275,11 @@ void main() { ); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); await tester.dragFrom( - squareOffset(Square.e2), + squareOffset(tester, Square.e2), const Offset(0, -(squareSize * 2)), ); @@ -1210,10 +1306,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset(Square.e4)); + final tapGesture = + await tester.startGesture(squareOffset(tester, Square.e4)); await tapGesture.up(); await pressGesture.up(); @@ -1223,10 +1321,11 @@ void main() { await tester.pump(const Duration(milliseconds: 210)); // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); await tester.dragFrom( - squareOffset(Square.e2), + squareOffset(tester, Square.e2), const Offset(0, -(squareSize * 2)), ); @@ -1237,8 +1336,8 @@ void main() { expect(find.byType(ShapeWidget), findsNWidgets(2)); - await tester.tapAt(squareOffset(Square.a3)); - await tester.tapAt(squareOffset(Square.a3)); + await tester.tapAt(squareOffset(tester, Square.a3)); + await tester.tapAt(squareOffset(tester, Square.a3)); await tester.pump(); expect(find.byType(ShapeWidget), findsNothing); @@ -1255,10 +1354,12 @@ void main() { await TestAsyncUtils.guard(() async { // keep pressing an empty square to enable drawing shapes - final pressGesture = await tester.startGesture(squareOffset(Square.a3)); + final pressGesture = + await tester.startGesture(squareOffset(tester, Square.a3)); // drawing a circle with another tap - final tapGesture = await tester.startGesture(squareOffset(Square.e4)); + final tapGesture = + await tester.startGesture(squareOffset(tester, Square.e4)); await tapGesture.up(); await pressGesture.up(); @@ -1269,7 +1370,7 @@ void main() { expect(find.byType(ShapeWidget), findsOneWidget); - await tester.tapAt(squareOffset(Square.e2)); + await tester.tapAt(squareOffset(tester, Square.e2)); await tester.pump(); expect(find.byType(ShapeWidget), findsNothing); @@ -1397,9 +1498,9 @@ void main() { Future makeMove(WidgetTester tester, Square from, Square to) async { final orientation = tester.widget(find.byType(Chessboard)).orientation; - await tester.tapAt(squareOffset(from, orientation: orientation)); + await tester.tapAt(squareOffset(tester, from, orientation: orientation)); await tester.pump(); - await tester.tapAt(squareOffset(to, orientation: orientation)); + await tester.tapAt(squareOffset(tester, to, orientation: orientation)); await tester.pump(); } @@ -1606,9 +1707,15 @@ class _TestAppState extends State<_TestApp> { } } -Offset squareOffset(Square id, {Side orientation = Side.white}) { +Offset squareOffset( + WidgetTester tester, + Square id, { + Side orientation = Side.white, +}) { + final rect = tester.getRect(find.byKey(const ValueKey('board-container'))); + final sq = rect.width / 8; final x = orientation == Side.black ? 7 - id.file : id.file; final y = orientation == Side.black ? id.rank : 7 - id.rank; - final o = Offset(x * squareSize, y * squareSize); - return Offset(o.dx + squareSize / 2, o.dy + squareSize / 2); + final o = Offset(rect.left + x * sq, rect.top + y * sq); + return Offset(o.dx + sq / 2, o.dy + sq / 2); } From b0ee12d90220cc3fec24696be61cca8f6c24e18e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 4 Nov 2024 12:06:16 +0100 Subject: [PATCH 2/5] Use CustomPaint to draw image background coordinates --- lib/src/widgets/background.dart | 122 ++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/lib/src/widgets/background.dart b/lib/src/widgets/background.dart index 92fc93d..234dc67 100644 --- a/lib/src/widgets/background.dart +++ b/lib/src/widgets/background.dart @@ -33,6 +33,7 @@ class SolidColorChessboardBackground extends ChessboardBackground { @override Widget build(BuildContext context) { return CustomPaint( + size: Size.infinite, painter: _SolidColorChessboardPainter( lightSquare: lightSquare, darkSquare: darkSquare, @@ -140,38 +141,101 @@ class ImageChessboardBackground extends ChessboardBackground { @override Widget build(BuildContext context) { if (coordinates) { - return LayoutBuilder( - builder: (context, constraints) { - final squareSize = constraints.biggest.shortestSide / 8; - return Stack( - alignment: Alignment.topLeft, - clipBehavior: Clip.none, - children: [ - Image(image: image), - for (var rank = 0; rank < 8; rank++) - for (var file = 0; file < 8; file++) - if (file == 7 || rank == 7) - Positioned( - left: file * squareSize, - top: rank * squareSize, - child: SizedBox( - width: squareSize, - height: squareSize, - child: InnerBoardCoordinate( - rank: rank, - file: file, - orientation: orientation, - color: - (rank + file).isEven ? darkSquare : lightSquare, - ), - ), - ), - ], - ); - }, + return Stack( + alignment: Alignment.topLeft, + clipBehavior: Clip.none, + children: [ + Image(image: image), + CustomPaint( + size: Size.infinite, + painter: _ImageBackgroundCoordinatePainter( + lightSquare: lightSquare, + darkSquare: darkSquare, + orientation: orientation, + ), + ), + ], ); } else { return Image(image: image); } } } + +class _ImageBackgroundCoordinatePainter extends CustomPainter { + _ImageBackgroundCoordinatePainter({ + required this.lightSquare, + required this.darkSquare, + required this.orientation, + }); + + final Side orientation; + final Color lightSquare; + final Color darkSquare; + + @override + void paint(Canvas canvas, Size size) { + final squareSize = size.shortestSide / 8; + for (var rank = 0; rank < 8; rank++) { + for (var file = 0; file < 8; file++) { + if (file == 7 || rank == 7) { + final coordStyle = TextStyle( + inherit: false, + fontWeight: FontWeight.bold, + fontSize: 10.0, + color: (rank + file).isEven ? darkSquare : lightSquare, + fontFamily: 'Roboto', + height: 1.0, + ); + final square = Rect.fromLTWH( + file * squareSize, + rank * squareSize, + squareSize, + squareSize, + ); + final paint = Paint()..color = const Color(0x00000000); + canvas.drawRect(square, paint); + if (file == 7) { + final coord = TextPainter( + text: TextSpan( + text: orientation == Side.white ? '${8 - rank}' : '${rank + 1}', + style: coordStyle, + ), + textDirection: TextDirection.ltr, + ); + coord.layout(); + const edgeOffset = 2.0; + final offset = Offset( + file * squareSize + (squareSize - coord.width) - edgeOffset, + rank * squareSize + edgeOffset, + ); + coord.paint(canvas, offset); + } + if (rank == 7) { + final coord = TextPainter( + text: TextSpan( + text: orientation == Side.white + ? String.fromCharCode(97 + file) + : String.fromCharCode(97 + 7 - file), + style: coordStyle, + ), + textDirection: TextDirection.ltr, + ); + coord.layout(); + const edgeOffset = 2.0; + final offset = Offset( + file * squareSize + edgeOffset, + rank * squareSize + (squareSize - coord.height) - edgeOffset, + ); + coord.paint(canvas, offset); + } + } + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} From d067e2796fc30a7851a8240602785cb9ee3e10d5 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 4 Nov 2024 12:08:57 +0100 Subject: [PATCH 3/5] Fix lint --- lib/src/board_settings.dart | 1 - lib/src/widgets/background.dart | 1 - lib/src/widgets/coordinate.dart | 66 +-------------------------------- test/widgets/board_test.dart | 3 -- 4 files changed, 2 insertions(+), 69 deletions(-) diff --git a/lib/src/board_settings.dart b/lib/src/board_settings.dart index fae3f32..a9025b2 100644 --- a/lib/src/board_settings.dart +++ b/lib/src/board_settings.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'board_color_scheme.dart'; diff --git a/lib/src/widgets/background.dart b/lib/src/widgets/background.dart index 234dc67..0099a6c 100644 --- a/lib/src/widgets/background.dart +++ b/lib/src/widgets/background.dart @@ -1,4 +1,3 @@ -import 'package:chessground/src/widgets/coordinate.dart'; import 'package:dartchess/dartchess.dart' show Side; import 'package:flutter/widgets.dart'; diff --git a/lib/src/widgets/coordinate.dart b/lib/src/widgets/coordinate.dart index 520075d..9778a4a 100644 --- a/lib/src/widgets/coordinate.dart +++ b/lib/src/widgets/coordinate.dart @@ -10,6 +10,7 @@ const _coordStyle = TextStyle( color: Color(0x99FFFFFF), ); +/// A widget that displays the rank coordinates of a chess board. class BorderRankCoordinates extends StatelessWidget { const BorderRankCoordinates({ required this.orientation, @@ -48,6 +49,7 @@ class BorderRankCoordinates extends StatelessWidget { } } +/// A widget that displays the file coordinates of a chess board. class BorderFileCoordinates extends StatelessWidget { const BorderFileCoordinates({ required this.orientation, @@ -85,67 +87,3 @@ class BorderFileCoordinates extends StatelessWidget { ); } } - -class InnerBoardCoordinate extends StatelessWidget { - const InnerBoardCoordinate({ - required this.rank, - required this.file, - required this.color, - required this.orientation, - super.key, - }); - - final int rank; - final int file; - final Color color; - final Side orientation; - - @override - Widget build(BuildContext context) { - final coordStyle = TextStyle( - inherit: false, - fontWeight: FontWeight.bold, - fontSize: 10.0, - color: color, - fontFamily: 'Roboto', - height: 1.0, - ); - return Stack( - alignment: Alignment.topLeft, - children: [ - if (file == 7) - Positioned( - top: 2.0, - right: 2.0, - child: Align( - alignment: Alignment.topRight, - child: Text( - textDirection: TextDirection.ltr, - orientation == Side.white ? '${8 - rank}' : '${rank + 1}', - style: coordStyle, - textScaler: TextScaler.noScaling, - textAlign: TextAlign.center, - ), - ), - ), - if (rank == 7) - Positioned( - bottom: 2.0, - left: 2.0, - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - textDirection: TextDirection.ltr, - orientation == Side.white - ? String.fromCharCode(97 + file) - : String.fromCharCode(97 + 7 - file), - style: coordStyle, - textScaler: TextScaler.noScaling, - textAlign: TextAlign.center, - ), - ), - ), - ], - ); - } -} diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 883fbf9..8f04420 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -1521,7 +1521,6 @@ class _TestApp extends StatefulWidget { this.initialPromotionMove, this.initialShapes, this.enableDrawingShapes = false, - this.pieceShiftMethod = PieceShiftMethod.either, this.shouldPlayOpponentMove = false, this.gameEventStream, super.key, @@ -1535,7 +1534,6 @@ class _TestApp extends StatefulWidget { final NormalMove? initialPromotionMove; final ISet? initialShapes; final bool enableDrawingShapes; - final PieceShiftMethod pieceShiftMethod; /// play the first available move for the opponent after a delay of 200ms final bool shouldPlayOpponentMove; @@ -1568,7 +1566,6 @@ class _TestAppState extends State<_TestApp> { }, newShapeColor: const Color(0xFF0000FF), ), - pieceShiftMethod: widget.pieceShiftMethod, ); @override From 7bf17213bf91b55fcfd0c9fa3fd7469f2e923f83 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 4 Nov 2024 12:25:11 +0100 Subject: [PATCH 4/5] Fix doc --- lib/src/images.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/images.dart b/lib/src/images.dart index 0c12762..a2edbe7 100644 --- a/lib/src/images.dart +++ b/lib/src/images.dart @@ -34,7 +34,7 @@ import 'widgets/piece.dart'; /// the piece image using the [RawImage] widget. /// /// This is the responsibility of the user to dispose of the cache when it is no -/// longer needed, or when changing the piece set, using the [clearCache] method. +/// longer needed, or when changing the piece set, using the [clear] method. class ChessgroundImages { ChessgroundImages._(); @@ -65,7 +65,7 @@ class ChessgroundImages { /// Removes all cached images. /// /// This calls [ui.Image.dispose] for all images in the cache, so make sure that - /// you don't use any of the previously cached images once [clearCache] has + /// you don't use any of the previously cached images once [clear] has /// been called. void clear() { _assets.forEach((_, asset) => asset.dispose()); From 05e84c5473d45c6a0b041bd5efc0054ed9a1c96b Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Mon, 4 Nov 2024 12:25:36 +0100 Subject: [PATCH 5/5] Bump version --- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 2bbb870..86b0207 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -31,7 +31,7 @@ packages: path: ".." relative: true source: path - version: "5.3.0" + version: "5.4.0" clock: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6655e30..92f5ce6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: chessground description: Chess board UI developed for lichess.org. It has no chess logic inside so it can be used for chess variants. -version: 5.3.0 +version: 5.4.0 repository: https://github.com/lichess-org/flutter-chessground funding: - https://lichess.org/patron