Skip to content

Commit

Permalink
Merge pull request #43 from lichess-org/pointer-tool-mode
Browse files Browse the repository at this point in the history
Board editor with pointer tool mode
  • Loading branch information
veloce authored Jul 23, 2024
2 parents b789031 + b3a134a commit 1c7bf48
Show file tree
Hide file tree
Showing 34 changed files with 1,865 additions and 896 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 4.0.0

- Add a `ChessBoardEditor` widget, intended to be used as the basis for a board editor like lichess.org/editor
- Chessground is now dependant on `dartchess`. It is only used to share
common types: `Role`, `Side` and `Piece`. It is not used for any chess logic.
- `SquareId` is now an extension type on String.
- `Board` was renamed to `ChessBoard`.
- `Move` was renamed to `BoardMove`.
- Add the `writeFen` helper function.
- Add the `legalMovesOf` helper function to convert a dartchess `Position` to a
set of valid moves compatible with Chessground.

## 3.2.0

- Add `pieceShiftMethod` to `BoardSetttings`, with possible values: `either` (default), `drag`, or `tapTwoSquares`.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ chess logic so you can use it with different chess variants.
- draw shapes on board while playing using 2 fingers
- move annotations
- opponent's pieces can be displayed upside down
- create positions with a board editor

## Getting started

This package exports a `Board` widget which can be interactable or not. It is
This package exports a `ChessBoard` widget which can be interactable or not. It is
entirely configurable with a `BoardSettings` object.

## Usage
Expand Down Expand Up @@ -53,7 +54,7 @@ class _MyHomePageState extends State<MyHomePage> {
title: const Text('Chessground demo'),
),
body: Center(
child: Board(
child: ChessBoard(
size: screenWidth,
data: BoardData(
interactableSide: InteractableSide.none,
Expand Down
191 changes: 191 additions & 0 deletions example/lib/board_editor_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import 'package:board_example/board_theme.dart';
import 'package:flutter/material.dart';
import 'package:chessground/chessground.dart';
import 'package:dartchess/dartchess.dart' as dc;
import 'package:collection/collection.dart';

class BoardEditorPage extends StatefulWidget {
const BoardEditorPage({super.key});

@override
State<StatefulWidget> createState() => _BoardEditorPageState();
}

class _BoardEditorPageState extends State<BoardEditorPage> {
Pieces pieces = readFen(dc.kInitialFEN);

/// The piece to add when a square is touched. If null, will delete the piece.
dc.Piece? pieceToAddOnTouch;

PointerToolMode pointerMode = PointerToolMode.drag;

@override
Widget build(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;

const PieceSet pieceSet = PieceSet.merida;

final settings = BoardEditorSettings(
pieceAssets: pieceSet.assets,
colorScheme: BoardTheme.blue.colors,
enableCoordinates: true,
);
final boardEditor = ChessBoardEditor(
size: screenWidth,
orientation: dc.Side.white,
pieces: pieces,
settings: settings,
pointerToolMode: pointerMode,
onTouchedSquare: (squareId) => setState(() {
if (pieceToAddOnTouch != null) {
pieces[squareId] = pieceToAddOnTouch!;
} else {
pieces.remove(squareId);
}
}),
onDiscardedPiece: (squareId) => setState(() {
pieces.remove(squareId);
}),
onDroppedPiece: (origin, destination, piece) => setState(() {
pieces[destination] = piece;
if (origin != null && origin != destination) {
pieces.remove(origin);
}
}),
);

makePieceMenu(side) => PieceMenu(
side: side,
pieceSet: pieceSet,
squareSize: boardEditor.squareSize,
settings: settings,
pieceEdition:
pointerMode == PointerToolMode.edit ? pieceToAddOnTouch : null,
pieceTapped: (role) => setState(() {
pieceToAddOnTouch = dc.Piece(role: role, color: side);
pointerMode = PointerToolMode.edit;
}),
pointerMode: pointerMode,
deleteTapped: () => setState(() {
pieceToAddOnTouch = null;
pointerMode = PointerToolMode.edit;
}),
pointerModeTapped: () => setState(() {
pointerMode = PointerToolMode.drag;
}),
);

return Scaffold(
appBar: AppBar(
title: const Text('Board Editor'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
makePieceMenu(dc.Side.black),
boardEditor,
makePieceMenu(dc.Side.white),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child:
SizedBox(height: 50, child: Text('FEN: ${writeFen(pieces)}')),
),
],
),
),
);
}
}

class PieceMenu extends StatelessWidget {
const PieceMenu({
super.key,
required this.side,
required this.pieceSet,
required this.squareSize,
required this.pieceEdition,
required this.pointerMode,
required this.settings,
required this.pieceTapped,
required this.deleteTapped,
required this.pointerModeTapped,
});

final dc.Side side;
final PieceSet pieceSet;
final double squareSize;

/// The piece that is currently being edited.
///
/// If null while [pointerMode] is [PointerToolMode.edit], the user is in delete mode.
final dc.Piece? pieceEdition;

final PointerToolMode pointerMode;
final BoardEditorSettings settings;
final Function(dc.Role role) pieceTapped;
final Function() deleteTapped;
final Function() pointerModeTapped;

@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: pointerMode == PointerToolMode.drag
? Colors.green
: Colors.transparent,
child: GestureDetector(
onTap: () => pointerModeTapped(),
child: Icon(
Icons.pan_tool_alt_outlined,
size: squareSize,
),
),
),
...dc.Role.values.mapIndexed(
(i, role) {
final piece = dc.Piece(role: role, color: side);
final pieceWidget = PieceWidget(
piece: piece,
size: squareSize,
pieceAssets: pieceSet.assets,
);

return Container(
color: pieceEdition == piece ? Colors.blue : Colors.transparent,
child: GestureDetector(
onTap: () => pieceTapped(role),
child: Draggable(
data: piece,
feedback: PieceDragFeedback(
scale: settings.dragFeedbackScale,
squareSize: squareSize,
piece: piece,
pieceAssets: pieceSet.assets,
),
child: pieceWidget),
),
);
},
).toList(),
Container(
color: pointerMode == PointerToolMode.edit && pieceEdition == null
? Colors.red
: Colors.transparent,
child: GestureDetector(
onTap: () => deleteTapped(),
child: Icon(
Icons.delete_outline,
size: squareSize,
),
),
),
],
),
);
}
}
3 changes: 2 additions & 1 deletion example/lib/board_thumbnails.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:dartchess/dartchess.dart' show Side;
import 'package:flutter/material.dart';
import 'package:chessground/chessground.dart';

Expand All @@ -18,7 +19,7 @@ class BoardThumbnailsPage extends StatelessWidget {
children: [
for (final fen in positions)
LayoutBuilder(builder: (context, constraints) {
return Board(
return ChessBoard(
size: constraints.biggest.width,
settings: BoardSettings(
enableCoordinates: false,
Expand Down
25 changes: 13 additions & 12 deletions example/lib/maestro.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import 'package:dartchess/dartchess.dart';
import 'package:flutter/widgets.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:chessground/chessground.dart' as cg;

const cg.PieceAssets maestroPieceSet = IMapConst({
cg.PieceKind.blackRook: AssetImage('lib/piece_set/maestro/bR.png'),
cg.PieceKind.blackPawn: AssetImage('lib/piece_set/maestro/bP.png'),
cg.PieceKind.blackKnight: AssetImage('lib/piece_set/maestro/bN.png'),
cg.PieceKind.blackBishop: AssetImage('lib/piece_set/maestro/bB.png'),
cg.PieceKind.blackQueen: AssetImage('lib/piece_set/maestro/bQ.png'),
cg.PieceKind.blackKing: AssetImage('lib/piece_set/maestro/bK.png'),
cg.PieceKind.whiteRook: AssetImage('lib/piece_set/maestro/wR.png'),
cg.PieceKind.whiteKnight: AssetImage('lib/piece_set/maestro/wN.png'),
cg.PieceKind.whiteBishop: AssetImage('lib/piece_set/maestro/wB.png'),
cg.PieceKind.whiteQueen: AssetImage('lib/piece_set/maestro/wQ.png'),
cg.PieceKind.whiteKing: AssetImage('lib/piece_set/maestro/wK.png'),
cg.PieceKind.whitePawn: AssetImage('lib/piece_set/maestro/wP.png'),
kBlackRookKind: AssetImage('lib/piece_set/maestro/bR.png'),
kBlackPawnKind: AssetImage('lib/piece_set/maestro/bP.png'),
kBlackKnightKind: AssetImage('lib/piece_set/maestro/bN.png'),
kBlackBishopKind: AssetImage('lib/piece_set/maestro/bB.png'),
kBlackQueenKind: AssetImage('lib/piece_set/maestro/bQ.png'),
kBlackKingKind: AssetImage('lib/piece_set/maestro/bK.png'),
kWhiteRookKind: AssetImage('lib/piece_set/maestro/wR.png'),
kWhiteKnightKind: AssetImage('lib/piece_set/maestro/wN.png'),
kWhiteBishopKind: AssetImage('lib/piece_set/maestro/wB.png'),
kWhiteQueenKind: AssetImage('lib/piece_set/maestro/wQ.png'),
kWhiteKingKind: AssetImage('lib/piece_set/maestro/wK.png'),
kWhitePawnKind: AssetImage('lib/piece_set/maestro/wP.png'),
});
Loading

0 comments on commit 1c7bf48

Please sign in to comment.