diff --git a/lib/src/view/analysis/analysis_screen.dart b/lib/src/view/analysis/analysis_screen.dart index ea3095e3ce..9c20e224aa 100644 --- a/lib/src/view/analysis/analysis_screen.dart +++ b/lib/src/view/analysis/analysis_screen.dart @@ -357,7 +357,7 @@ class _Body extends ConsumerWidget { } } -class _Board extends ConsumerWidget { +class _Board extends ConsumerStatefulWidget { const _Board( this.pgn, this.options, @@ -371,8 +371,15 @@ class _Board extends ConsumerWidget { final bool isTablet; @override - Widget build(BuildContext context, WidgetRef ref) { - final ctrlProvider = analysisControllerProvider(pgn, options); + ConsumerState<_Board> createState() => _BoardState(); +} + +class _BoardState extends ConsumerState<_Board> { + ISet userShapes = ISet(); + + @override + Widget build(BuildContext context) { + final ctrlProvider = analysisControllerProvider(widget.pgn, widget.options); final analysisState = ref.watch(ctrlProvider); final boardPrefs = ref.watch(boardPreferencesProvider); final showBestMoveArrow = ref.watch( @@ -392,8 +399,46 @@ class _Board extends ConsumerWidget { final sanMove = currentNode.sanMove; + final ISet bestMoveShapes = showBestMoveArrow && + analysisState.isEngineAvailable && + bestMoves != null + ? ISet( + bestMoves.where((move) => move != null).mapIndexed( + (i, move) { + switch (move!) { + case NormalMove(from: _, to: _, promotion: final promRole): + return [ + cg.Arrow( + color: + const Color(0x40003088).withOpacity(0.4 - 0.15 * i), + orig: move.cg.from, + dest: move.cg.to, + ), + if (promRole != null) + cg.PieceShape( + color: const Color(0x40003088) + .withOpacity(0.4 - 0.15 * i), + orig: move.cg.to, + role: promRole.cg, + ), + ]; + case DropMove(role: final role, to: _): + return [ + cg.PieceShape( + color: + const Color(0x40003088).withOpacity(0.4 - 0.15 * i), + orig: move.cg.to, + role: role.cg, + ), + ]; + } + }, + ).expand((e) => e), + ) + : ISet(); + return cg.Board( - size: boardSize, + size: widget.boardSize, onMove: (move, {isDrop, isPremove}) => ref.read(ctrlProvider.notifier).onUserMove(Move.fromUci(move.uci)!), data: cg.BoardData( @@ -408,47 +453,7 @@ class _Board extends ConsumerWidget { lastMove: analysisState.lastMove?.cg, sideToMove: analysisState.position.turn.cg, validMoves: analysisState.validMoves, - shapes: showBestMoveArrow && - analysisState.isEngineAvailable && - bestMoves != null - ? ISet( - bestMoves.where((move) => move != null).mapIndexed( - (i, move) { - switch (move!) { - case NormalMove( - from: _, - to: _, - promotion: final promRole - ): - return [ - cg.Arrow( - color: const Color(0x40003088) - .withOpacity(0.4 - 0.15 * i), - orig: move.cg.from, - dest: move.cg.to, - ), - if (promRole != null) - cg.PieceShape( - color: const Color(0x40003088) - .withOpacity(0.4 - 0.15 * i), - orig: move.cg.to, - role: promRole.cg, - ), - ]; - case DropMove(role: final role, to: _): - return [ - cg.PieceShape( - color: const Color(0x40003088) - .withOpacity(0.4 - 0.15 * i), - orig: move.cg.to, - role: role.cg, - ), - ]; - } - }, - ).expand((e) => e), - ) - : null, + shapes: userShapes.union(bestMoveShapes), annotations: sanMove != null && annotation != null ? altCastles.containsKey(sanMove.move.uci) ? IMap({ @@ -465,13 +470,37 @@ class _Board extends ConsumerWidget { showLastMove: boardPrefs.boardHighlights, enableCoordinates: boardPrefs.coordinates, animationDuration: boardPrefs.pieceAnimationDuration, - borderRadius: isTablet + borderRadius: widget.isTablet ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, - boxShadow: isTablet ? boardShadows : const [], + boxShadow: widget.isTablet ? boardShadows : const [], + drawShape: cg.DrawShapeOptions( + enable: true, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + ), ), ); } + + void _onCompleteShape(cg.Shape shape) { + if (userShapes.any((element) => element == shape)) { + setState(() { + userShapes = userShapes.remove(shape); + }); + return; + } else { + setState(() { + userShapes = userShapes.add(shape); + }); + } + } + + void _onClearShapes() { + setState(() { + userShapes = ISet(); + }); + } } class _EngineGaugeVertical extends ConsumerWidget { diff --git a/lib/src/widgets/board_table.dart b/lib/src/widgets/board_table.dart index 6d287e1090..e84762d04f 100644 --- a/lib/src/widgets/board_table.dart +++ b/lib/src/widgets/board_table.dart @@ -1,5 +1,6 @@ import 'package:chessground/chessground.dart' hide BoardTheme; import 'package:collection/collection.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -25,7 +26,7 @@ const _moveListOpacity = 0.6; /// An optional move list can be displayed above the top table space. /// /// An optional overlay or error message can be displayed on top of the board. -class BoardTable extends ConsumerWidget { +class BoardTable extends ConsumerStatefulWidget { const BoardTable({ this.onMove, this.onPremove, @@ -91,7 +92,14 @@ class BoardTable extends ConsumerWidget { final bool showEngineGaugePlaceholder; @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _BoardTableState(); +} + +class _BoardTableState extends ConsumerState { + ISet userShapes = ISet(); + + @override + Widget build(BuildContext context) { final boardPrefs = ref.watch(boardPreferencesProvider); return LayoutBuilder( @@ -108,7 +116,7 @@ class BoardTable extends ConsumerWidget { final verticalSpaceLeftBoardOnPortrait = constraints.biggest.height - boardSize; - final error = errorMessage != null + final error = widget.errorMessage != null ? SizedBox.square( dimension: boardSize, child: Center( @@ -125,7 +133,7 @@ class BoardTable extends ConsumerWidget { ), child: Padding( padding: const EdgeInsets.all(10.0), - child: Text(errorMessage!), + child: Text(widget.errorMessage!), ), ), ), @@ -144,24 +152,29 @@ class BoardTable extends ConsumerWidget { ? const BorderRadius.all(Radius.circular(4.0)) : BorderRadius.zero, boxShadow: isTablet ? boardShadows : const [], + drawShape: DrawShapeOptions( + enable: true, + onCompleteShape: _onCompleteShape, + onClearShapes: _onClearShapes, + ), ); - final settings = boardSettingsOverrides != null - ? boardSettingsOverrides!.merge(defaultSettings) + final settings = widget.boardSettingsOverrides != null + ? widget.boardSettingsOverrides!.merge(defaultSettings) : defaultSettings; final board = Board( - key: boardKey, + key: widget.boardKey, size: boardSize, - data: boardData, + data: widget.boardData, settings: settings, - onMove: onMove, - onPremove: onPremove, + onMove: widget.onMove, + onPremove: widget.onPremove, ); Widget boardWidget = board; - if (boardOverlay != null) { + if (widget.boardOverlay != null) { boardWidget = SizedBox.square( dimension: boardSize, child: Stack( @@ -173,7 +186,7 @@ class BoardTable extends ConsumerWidget { child: SizedBox( width: (boardSize / 8) * 6.6, height: (boardSize / 8) * 4.6, - child: boardOverlay, + child: widget.boardOverlay, ), ), ), @@ -192,7 +205,7 @@ class BoardTable extends ConsumerWidget { ); } - final slicedMoves = moves?.asMap().entries.slices(2); + final slicedMoves = widget.moves?.asMap().entries.slices(2); return aspectRatio > 1 ? Row( @@ -207,12 +220,12 @@ class BoardTable extends ConsumerWidget { child: Row( children: [ boardWidget, - if (engineGauge != null) + if (widget.engineGauge != null) EngineGauge( - params: engineGauge!, + params: widget.engineGauge!, displayMode: EngineGaugeDisplayMode.vertical, ) - else if (showEngineGaugePlaceholder) + else if (widget.showEngineGaugePlaceholder) const SizedBox(width: kEvalGaugeSize), ], ), @@ -226,7 +239,7 @@ class BoardTable extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Flexible(child: topTable), + Flexible(child: widget.topTable), if (slicedMoves != null) Expanded( child: Padding( @@ -234,8 +247,9 @@ class BoardTable extends ConsumerWidget { child: MoveList( type: MoveListType.stacked, slicedMoves: slicedMoves, - currentMoveIndex: currentMoveIndex ?? 0, - onSelectMove: onSelectMove, + currentMoveIndex: + widget.currentMoveIndex ?? 0, + onSelectMove: widget.onSelectMove, ), ), ) @@ -247,7 +261,7 @@ class BoardTable extends ConsumerWidget { child: SizedBox(height: 40), ), ), - Flexible(child: bottomTable), + Flexible(child: widget.bottomTable), ], ), ), @@ -263,10 +277,10 @@ class BoardTable extends ConsumerWidget { MoveList( type: MoveListType.inline, slicedMoves: slicedMoves, - currentMoveIndex: currentMoveIndex ?? 0, - onSelectMove: onSelectMove, + currentMoveIndex: widget.currentMoveIndex ?? 0, + onSelectMove: widget.onSelectMove, ) - else if (showMoveListPlaceholder && + else if (widget.showMoveListPlaceholder && verticalSpaceLeftBoardOnPortrait >= 130) const SizedBox(height: 40), Expanded( @@ -275,10 +289,10 @@ class BoardTable extends ConsumerWidget { horizontal: isTablet ? kTabletBoardTableSidePadding : 12.0, ), - child: topTable, + child: widget.topTable, ), ), - if (engineGauge != null) + if (widget.engineGauge != null) Padding( padding: isTablet ? const EdgeInsets.symmetric( @@ -286,11 +300,11 @@ class BoardTable extends ConsumerWidget { ) : EdgeInsets.zero, child: EngineGauge( - params: engineGauge!, + params: widget.engineGauge!, displayMode: EngineGaugeDisplayMode.horizontal, ), ) - else if (showEngineGaugePlaceholder) + else if (widget.showEngineGaugePlaceholder) const SizedBox(height: kEvalGaugeSize), boardWidget, Expanded( @@ -299,7 +313,7 @@ class BoardTable extends ConsumerWidget { horizontal: isTablet ? kTabletBoardTableSidePadding : 12.0, ), - child: bottomTable, + child: widget.bottomTable, ), ), ], @@ -307,6 +321,25 @@ class BoardTable extends ConsumerWidget { }, ); } + + void _onCompleteShape(Shape shape) { + if (userShapes.any((element) => element == shape)) { + setState(() { + userShapes = userShapes.remove(shape); + }); + return; + } else { + setState(() { + userShapes = userShapes.add(shape); + }); + } + } + + void _onClearShapes() { + setState(() { + userShapes = ISet(); + }); + } } class BoardSettingsOverrides { diff --git a/pubspec.lock b/pubspec.lock index 9b51c5bcad..ff40d10857 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -186,10 +186,10 @@ packages: dependency: "direct main" description: name: chessground - sha256: f538c1b4fa3676e91b8d8b052b5835119d74e717cd7d66e76dc6969682713063 + sha256: a09963817456e27ba6b474ba6f0855b632245324459964ceca208423c0747567 url: "https://pub.dev" source: hosted - version: "2.6.4" + version: "3.0.0" ci: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 55fd3b75f3..f4f4d928a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: async: ^2.10.0 cached_network_image: ^3.2.2 - chessground: ^2.6.1 + chessground: ^3.0.0 collection: ^1.17.0 connectivity_plus: ^6.0.2 cronet_http: ^1.3.1