Skip to content

Commit

Permalink
feat: support input method (click, drag, either)
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Jul 16, 2024
1 parent b6d04f7 commit ac43d2b
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.2.0

- Add `pieceShiftMethod` to `BoardSetttings`, with possible values: `either` (default), `drag`, or `tapTwoSquares`.

## 3.1.2

- Any simultaneous touch on the board will now cancel the current piece
Expand Down
37 changes: 37 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ class MyApp extends StatelessWidget {
}
}

String pieceShiftMethodLabel(PieceShiftMethod method) {
switch (method) {
case PieceShiftMethod.drag:
return 'Drag';
case PieceShiftMethod.tapTwoSquares:
return 'Tap two squares';
case PieceShiftMethod.either:
return 'Either';
}
}

enum Mode {
botPlay,
freePlay,
Expand All @@ -51,6 +62,7 @@ class _HomePageState extends State<HomePage> {
ValidMoves validMoves = IMap(const {});
Side sideToMove = Side.white;
PieceSet pieceSet = PieceSet.merida;
PieceShiftMethod pieceShiftMethod = PieceShiftMethod.either;
BoardTheme boardTheme = BoardTheme.blue;
bool drawMode = true;
bool pieceAnimation = true;
Expand Down Expand Up @@ -129,6 +141,7 @@ class _HomePageState extends State<HomePage> {
});
},
),
pieceShiftMethod: pieceShiftMethod,
),
data: BoardData(
interactableSide: playMode == Mode.botPlay
Expand Down Expand Up @@ -242,6 +255,30 @@ class _HomePageState extends State<HomePage> {
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
ElevatedButton(
child: Text(
'Piece shift method: ${pieceShiftMethodLabel(pieceShiftMethod)}'),
onPressed: () => _showChoicesPicker<PieceShiftMethod>(
context,
choices: PieceShiftMethod.values,
selectedItem: pieceShiftMethod,
labelBuilder: (t) => Text(pieceShiftMethodLabel(t)),
onSelectedItemChanged: (PieceShiftMethod? value) {
setState(() {
if (value != null) {
pieceShiftMethod = value;
}
});
},
),
),
const SizedBox(width: 8),
],
),
if (playMode == Mode.freePlay)
Center(
child: IconButton(
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ packages:
path: ".."
relative: true
source: path
version: "3.1.2"
version: "3.2.0"
clock:
dependency: transitive
description:
Expand Down
20 changes: 20 additions & 0 deletions lib/src/board_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import 'models.dart';
import 'piece_set.dart';
import 'draw_shape_options.dart';

/// Describes how moves are made on an interactive board.
enum PieceShiftMethod {
/// First tap the piece to be moved, then tap the target square.
tapTwoSquares,

/// Drag-and-drop the piece to the target square.
drag,

/// Both tap and drag are enabled.
either;
}

/// Board settings that control the theme, behavior and purpose of the board.
///
/// This is meant for fixed settings that don't change during a game. Sensible
Expand Down Expand Up @@ -33,6 +45,7 @@ class BoardSettings {
this.enablePremoveCastling = true,
this.autoQueenPromotion = false,
this.autoQueenPromotionOnPremove = true,
this.pieceShiftMethod = PieceShiftMethod.either,
});

/// Theme of the board
Expand Down Expand Up @@ -79,6 +92,9 @@ class BoardSettings {
/// automatically to queen only if the premove is confirmed
final bool autoQueenPromotionOnPremove;

/// Controls how moves are made.
final PieceShiftMethod pieceShiftMethod;

/// Shape drawing options object containing data about how new shapes can be drawn.
final DrawShapeOptions drawShape;

Expand All @@ -105,6 +121,7 @@ class BoardSettings {
other.enablePremoveCastling == enablePremoveCastling &&
other.autoQueenPromotion == autoQueenPromotion &&
other.autoQueenPromotionOnPremove == autoQueenPromotionOnPremove &&
other.pieceShiftMethod == pieceShiftMethod &&
other.drawShape == drawShape;
}

Expand All @@ -124,6 +141,7 @@ class BoardSettings {
enablePremoveCastling,
autoQueenPromotion,
autoQueenPromotionOnPremove,
pieceShiftMethod,
drawShape,
);

Expand All @@ -142,6 +160,7 @@ class BoardSettings {
bool? enablePremoveCastling,
bool? autoQueenPromotion,
bool? autoQueenPromotionOnPremove,
PieceShiftMethod? pieceShiftMethod,
DrawShapeOptions? drawShape,
}) {
return BoardSettings(
Expand All @@ -161,6 +180,7 @@ class BoardSettings {
autoQueenPromotionOnPremove:
autoQueenPromotionOnPremove ?? this.autoQueenPromotionOnPremove,
autoQueenPromotion: autoQueenPromotion ?? this.autoQueenPromotion,
pieceShiftMethod: pieceShiftMethod ?? this.pieceShiftMethod,
drawShape: drawShape ?? this.drawShape,
);
}
Expand Down
8 changes: 7 additions & 1 deletion lib/src/widgets/board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ class _BoardState extends State<Board> {
_premoveDests = null;
});
}

if (widget.settings.pieceShiftMethod == PieceShiftMethod.drag) {
_shouldDeselectOnTapUp = true;
}
}

void _onPointerMove(PointerMoveEvent details) {
Expand All @@ -577,7 +581,9 @@ class _BoardState extends State<Board> {
}

if (_currentPointerDownEvent == null ||
_currentPointerDownEvent!.pointer != details.pointer) return;
_currentPointerDownEvent!.pointer != details.pointer ||
widget.settings.pieceShiftMethod == PieceShiftMethod.tapTwoSquares)
return;

final distance =
(details.position - _currentPointerDownEvent!.position).distance;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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: 3.1.2
version: 3.2.0
repository: https://github.com/lichess-org/flutter-chessground
funding:
- https://lichess.org/patron
Expand Down
63 changes: 63 additions & 0 deletions test/widgets/board_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,33 @@ void main() {
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(
buildBoard(
initialInteractableSide: InteractableSide.both,
pieceShiftMethod: PieceShiftMethod.drag,
),
);
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(MoveDest), findsNothing);

// ... but move by drag should work
await tester.dragFrom(
squareOffset('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 taping king then rook is possible',
(WidgetTester tester) async {
await tester.pumpWidget(
Expand Down Expand Up @@ -170,6 +197,40 @@ void main() {
expect(find.byKey(const Key('e4-lastMove')), findsOneWidget);
});

testWidgets('Cannot move by drag if piece shift method is tapTwoSquares', (
WidgetTester tester,
) async {
await tester.pumpWidget(
buildBoard(
initialInteractableSide: InteractableSide.both,
pieceShiftMethod: PieceShiftMethod.tapTwoSquares,
),
);
await tester.dragFrom(
squareOffset('e2'),
const Offset(0, -(squareSize * 2)),
);
await tester.pumpAndSettle();
expect(find.byKey(const Key('e4-whitePawn')), findsNothing);
expect(find.byKey(const Key('e2-whitePawn')), findsOneWidget);
expect(find.byKey(const Key('e2-lastMove')), findsNothing);
expect(find.byKey(const Key('e4-lastMove')), findsNothing);

// Original square is still selected after drag attempt
expect(find.byKey(const Key('e2-selected')), findsOneWidget);
expect(find.byType(MoveDest), findsNWidgets(2));

// ...so we can still tap to move
await tester.tapAt(squareOffset('e4'));
await tester.pump();
expect(find.byKey(const Key('e2-selected')), findsNothing);
expect(find.byType(MoveDest), findsNothing);
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(
'2 simultaneous pointer down events will cancel current drag/selection',
(
Expand Down Expand Up @@ -859,6 +920,7 @@ Widget buildBoard({
String initialFen = dc.kInitialFEN,
ISet<Shape>? initialShapes,
bool enableDrawingShapes = false,
PieceShiftMethod pieceShiftMethod = PieceShiftMethod.either,

/// play the first available move for the opponent after a delay of 200ms
bool shouldPlayOpponentMove = false,
Expand All @@ -884,6 +946,7 @@ Widget buildBoard({
},
newShapeColor: const Color(0xFF0000FF),
),
pieceShiftMethod: pieceShiftMethod,
);

return Board(
Expand Down

0 comments on commit ac43d2b

Please sign in to comment.