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 14, 2024
1 parent b6d04f7 commit 9ba6a9f
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 13 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 `inputMethod` 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
25 changes: 25 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class _HomePageState extends State<HomePage> {
ValidMoves validMoves = IMap(const {});
Side sideToMove = Side.white;
PieceSet pieceSet = PieceSet.merida;
InputMethod inputMethod = InputMethod.either;
BoardTheme boardTheme = BoardTheme.blue;
bool drawMode = true;
bool pieceAnimation = true;
Expand Down Expand Up @@ -129,6 +130,7 @@ class _HomePageState extends State<HomePage> {
});
},
),
inputMethod: inputMethod,
),
data: BoardData(
interactableSide: playMode == Mode.botPlay
Expand Down Expand Up @@ -242,6 +244,29 @@ class _HomePageState extends State<HomePage> {
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
ElevatedButton(
child: Text('Input method: ${inputMethod.label}'),
onPressed: () => _showChoicesPicker<InputMethod>(
context,
choices: InputMethod.values,
selectedItem: inputMethod,
labelBuilder: (t) => Text(t.label),
onSelectedItemChanged: (InputMethod? value) {
setState(() {
if (value != null) {
inputMethod = 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
17 changes: 17 additions & 0 deletions lib/src/board_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import 'models.dart';
import 'piece_set.dart';
import 'draw_shape_options.dart';

enum InputMethod {
tapTwoSquares('Tap two squares'),
drag('Drag a piece'),
either('Either');

final String label;
const InputMethod(this.label);
}

/// 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 +42,7 @@ class BoardSettings {
this.enablePremoveCastling = true,
this.autoQueenPromotion = false,
this.autoQueenPromotionOnPremove = true,
this.inputMethod = InputMethod.either,
});

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

/// Controls how moves are made.
final InputMethod inputMethod;

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

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

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

Expand All @@ -142,6 +157,7 @@ class BoardSettings {
bool? enablePremoveCastling,
bool? autoQueenPromotion,
bool? autoQueenPromotionOnPremove,
InputMethod? inputMethod,
DrawShapeOptions? drawShape,
}) {
return BoardSettings(
Expand All @@ -161,6 +177,7 @@ class BoardSettings {
autoQueenPromotionOnPremove:
autoQueenPromotionOnPremove ?? this.autoQueenPromotionOnPremove,
autoQueenPromotion: autoQueenPromotion ?? this.autoQueenPromotion,
inputMethod: inputMethod ?? this.inputMethod,
drawShape: drawShape ?? this.drawShape,
);
}
Expand Down
7 changes: 6 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.inputMethod == InputMethod.drag) {
_shouldDeselectOnTapUp = true;
}
}

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

if (_currentPointerDownEvent == null ||
_currentPointerDownEvent!.pointer != details.pointer) return;
_currentPointerDownEvent!.pointer != details.pointer ||
widget.settings.inputMethod == InputMethod.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
57 changes: 47 additions & 10 deletions test/widgets/board_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,38 @@ void main() {
});

testWidgets('play e2-e4 move by tap', (WidgetTester tester) async {
await tester.pumpWidget(
await tester.pumpWidget(
buildBoard(initialInteractableSide: InteractableSide.both),
);
await tester.tap(find.byKey(const Key('e2-whitePawn')));
await tester.pump();

expect(find.byKey(const Key('e2-selected')), findsOneWidget);
expect(find.byType(MoveDest), findsNWidgets(2));

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('Cannot move by tap if input method is drag',
(WidgetTester tester) async {
await tester.pumpWidget(
buildBoard(
initialInteractableSide: InteractableSide.both,
inputMethod: InputMethod.drag,
),
);
await tester.tap(find.byKey(const Key('e2-whitePawn')));
await tester.pump();

expect(find.byKey(const Key('e2-selected')), findsOneWidget);
expect(find.byType(MoveDest), findsNWidgets(2));

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('castling by taping king then rook is possible',
Expand Down Expand Up @@ -170,6 +185,26 @@ void main() {
expect(find.byKey(const Key('e4-lastMove')), findsOneWidget);
});

testWidgets('Cannot move by drag if input method is clickTwoSquares', (
WidgetTester tester,
) async {
await tester.pumpWidget(
buildBoard(
initialInteractableSide: InteractableSide.both,
inputMethod: InputMethod.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);
});

testWidgets(
'2 simultaneous pointer down events will cancel current drag/selection',
(
Expand Down Expand Up @@ -859,6 +894,7 @@ Widget buildBoard({
String initialFen = dc.kInitialFEN,
ISet<Shape>? initialShapes,
bool enableDrawingShapes = false,
InputMethod inputMethod = InputMethod.either,

/// play the first available move for the opponent after a delay of 200ms
bool shouldPlayOpponentMove = false,
Expand Down Expand Up @@ -933,6 +969,7 @@ Widget buildBoard({
premove = move;
});
},
inputMethod: inputMethod,
);
},
),
Expand Down

0 comments on commit 9ba6a9f

Please sign in to comment.