Skip to content

Commit

Permalink
appearance: Implement Piece Image Settings
Browse files Browse the repository at this point in the history
  • Loading branch information
calcitem committed Aug 3, 2024
1 parent 2683083 commit a0e04d9
Show file tree
Hide file tree
Showing 21 changed files with 371 additions and 41 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class DisplaySettings {
this.backgroundImagePath = '',
this.isNumbersOnPiecesShown = false,
this.isAnalysisToolbarShown = false,
this.whitePieceImagePath = '',
this.blackPieceImagePath = '',
this.markedPieceImagePath = '',
});

/// Encodes a Json style map into a [DisplaySettings] object
Expand Down Expand Up @@ -153,6 +156,15 @@ class DisplaySettings {
@HiveField(22, defaultValue: false)
final bool isAnalysisToolbarShown;

@HiveField(23, defaultValue: '')
final String whitePieceImagePath;

@HiveField(24, defaultValue: '')
final String blackPieceImagePath;

@HiveField(25, defaultValue: '')
final String markedPieceImagePath;

/// Decodes a Json from a [DisplaySettings] object
Map<String, dynamic> toJson() => _$DisplaySettingsToJson(this);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:hive_flutter/hive_flutter.dart' show Box;
Expand All @@ -36,6 +37,7 @@ import '../models/display_settings.dart';

part 'package:sanmill/appearance_settings/widgets/modals/point_painting_style_modal.dart';
part 'package:sanmill/appearance_settings/widgets/pickers/background_image_picker.dart';
part 'package:sanmill/appearance_settings/widgets/pickers/piece_image_picker.dart';
part 'package:sanmill/appearance_settings/widgets/pickers/language_picker.dart';
part 'package:sanmill/appearance_settings/widgets/sliders/ai_response_delay_time_slider.dart';
part 'package:sanmill/appearance_settings/widgets/sliders/animation_duration_slider.dart';
Expand Down Expand Up @@ -114,6 +116,11 @@ class AppearanceSettingsPage extends StatelessWidget {
builder: (_) => const _BackgroundImagePicker(),
);

void setPieceImage(BuildContext context) => showModalBottomSheet(
context: context,
builder: (_) => const _PieceImagePicker(),
);

void langCallback(
BuildContext context,
DisplaySettings displaySettings, [
Expand Down Expand Up @@ -180,6 +187,10 @@ class AppearanceSettingsPage extends StatelessWidget {
titleString: S.of(context).backgroundImage,
onTap: () => setBackgroundImage(context),
),
SettingsListTile(
titleString: S.of(context).pieceImage,
onTap: () => setPieceImage(context),
),
SettingsListTile(
titleString: S.of(context).theme,
onTap: () => _setTheme(context, colorSettings),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// This file is part of Sanmill.
// Copyright (C) 2019-2024 The Sanmill developers (see AUTHORS file)
//
// Sanmill is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Sanmill is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

part of 'package:sanmill/appearance_settings/widgets/appearance_settings_page.dart';

final List<String> _pieceBgPaths = <String>[
'', // Pure color
Assets.images.whitePieceImage1.path,
Assets.images.blackPieceImage1.path,
Assets.images.whitePieceImage2.path,
Assets.images.blackPieceImage2.path,
Assets.images.whitePieceImage3.path,
Assets.images.blackPieceImage3.path,
Assets.images.whitePieceImage4.path,
Assets.images.blackPieceImage4.path,
Assets.images.whitePieceImage5.path,
Assets.images.blackPieceImage5.path,
Assets.images.whitePieceImage6.path,
Assets.images.blackPieceImage6.path,
];

class _PieceImagePicker extends StatelessWidget {
const _PieceImagePicker();

@override
Widget build(BuildContext context) {
return Container(
color: DB().colorSettings.boardBackgroundColor,
child: Semantics(
label: S.of(context).pieceImage,
child: ValueListenableBuilder<Box<DisplaySettings>>(
valueListenable: DB().listenDisplaySettings,
builder: (BuildContext context, Box<DisplaySettings> box, _) {
final DisplaySettings displaySettings = box.get(
DB.displaySettingsKey,
defaultValue: const DisplaySettings(),
)!;

return Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Row for Player 1
_buildPlayerRow(
context,
S.of(context).player1,
displaySettings.whitePieceImagePath,
(String asset) {
DB().displaySettings = displaySettings.copyWith(
whitePieceImagePath: asset);
},
displaySettings.blackPieceImagePath,
isPlayerOne: true,
),
const SizedBox(height: 20),
// Row for Player 2
_buildPlayerRow(
context,
S.of(context).player2,
displaySettings.blackPieceImagePath,
(String asset) {
DB().displaySettings = displaySettings.copyWith(
blackPieceImagePath: asset);
},
displaySettings.whitePieceImagePath,
isPlayerOne: false,
),
],
),
),
);
},
),
),
);
}

Widget _buildPlayerRow(
BuildContext context,
String playerLabel,
String selectedImagePath,
void Function(String) onImageSelected,
String otherPlayerSelectedImagePath,
{required bool isPlayerOne}) {
final ScrollController scrollController = ScrollController();

return GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails details) {
scrollController.jumpTo(
scrollController.offset - details.delta.dx,
);
},
child: Listener(
onPointerSignal: (PointerSignalEvent event) {
if (event is PointerScrollEvent) {
final double delta = event.scrollDelta.dy;
scrollController.jumpTo(scrollController.offset + delta);
}
},
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 16, right: 12),
child: Text(
playerLabel,
style: TextStyle(color: DB().colorSettings.boardLineColor),
),
),
Expanded(
child: SizedBox(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _pieceBgPaths.length,
controller: scrollController,
itemBuilder: (BuildContext context, int index) {
final String asset = _pieceBgPaths[index];
final bool isSelectable =
index == 0 || asset != otherPlayerSelectedImagePath;
if (index == 0) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: GestureDetector(
onTap: isSelectable
? () => onImageSelected(asset)
: null,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: isPlayerOne
? DB().colorSettings.whitePieceColor
: DB().colorSettings.blackPieceColor,
shape: BoxShape.circle,
border: selectedImagePath == asset
? Border.all(color: Colors.red, width: 2)
: null,
),
child: selectedImagePath == asset
? const Align(
child: Icon(
Icons.check_circle,
color: Colors.green,
),
)
: null,
),
),
);
} else {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: GestureDetector(
onTap: isSelectable
? () => onImageSelected(asset)
: null,
child: _PieceImageItem(
asset: asset,
isSelect: selectedImagePath == asset,
),
),
);
}
},
),
),
),
Padding(
padding: const EdgeInsets.only(right: 16),
child: Container(),
),
],
),
),
);
}
}

class _PieceImageItem extends StatelessWidget {
const _PieceImageItem({
required this.asset,
this.isSelect = false,
});

final String asset;
final bool isSelect;

@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(asset),
fit: BoxFit.contain,
),
border: isSelect ? Border.all(color: Colors.green, width: 2) : null,
borderRadius: BorderRadius.circular(8),
),
),
if (isSelect)
const Align(
child: Icon(
Icons.check_circle,
color: Colors.green,
),
),
],
);
}
}
68 changes: 55 additions & 13 deletions src/ui/flutter_app/lib/game_page/widgets/game_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ class GameBoard extends StatefulWidget {
class _GameBoardState extends State<GameBoard>
with SingleTickerProviderStateMixin {
static const String _logTag = "[board]";
late Future<Map<PieceColor, ui.Image?>> pieceImagesFuture;

@override
void initState() {
super.initState();

pieceImagesFuture = _loadImages();
GameController().gameResultNotifier.addListener(_showResult);

if (visitedRuleSettingsPage == true) {
Expand Down Expand Up @@ -86,6 +87,10 @@ class _GameBoardState extends State<GameBoard>
});
}

Future<Map<PieceColor, ui.Image?>> _loadImages() async {
return loadPieceImages();
}

void processInitialSharingMoveList() {
if (!mounted) {
return;
Expand Down Expand Up @@ -125,6 +130,32 @@ class _GameBoardState extends State<GameBoard>
GameController().initialSharingMoveList = null;
}

Future<ui.Image> loadImage(String assetPath) async {
final ByteData data = await rootBundle.load(assetPath);
final ui.Codec codec =
await ui.instantiateImageCodec(data.buffer.asUint8List());
final ui.FrameInfo frame = await codec.getNextFrame();
return frame.image;
}

// Loading images and creating PiecePainter
// TODO: Load from settings
Future<Map<PieceColor, ui.Image?>> loadPieceImages() async {
final DisplaySettings displaySettings = DB().displaySettings;
final Map<PieceColor, ui.Image?> images = <PieceColor, ui.Image?>{};

final String whitePieceImagePath = displaySettings.whitePieceImagePath;
final String blackPieceImagePath = displaySettings.blackPieceImagePath;

images[PieceColor.white] = await loadImage(whitePieceImagePath);
images[PieceColor.black] = await loadImage(blackPieceImagePath);

images[PieceColor.marked] =
await loadImage('assets/images/marked_piece_image.png');

return images;
}

@override
Widget build(BuildContext context) {
final TapHandler tapHandler = TapHandler(
Expand All @@ -134,20 +165,31 @@ class _GameBoardState extends State<GameBoard>
final AnimatedBuilder customPaint = AnimatedBuilder(
animation: GameController().animation,
builder: (_, Widget? child) {
return CustomPaint(
painter: BoardPainter(context),
foregroundPainter: PiecePainter(
animationValue: GameController().animation.value,
),
child: child,
return FutureBuilder<Map<PieceColor, ui.Image?>>(
future: pieceImagesFuture,
builder: (BuildContext context,
AsyncSnapshot<Map<PieceColor, ui.Image?>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else {
final Map<PieceColor, ui.Image?>? pieceImages = snapshot.data;
return CustomPaint(
painter: BoardPainter(context),
foregroundPainter: PiecePainter(
animationValue: GameController().animation.value,
pieceImages: pieceImages,
),
child: DB().generalSettings.screenReaderSupport
? const _BoardSemantics()
: Semantics(
label: S.of(context).youCanEnableScreenReaderSupport,
container: true,
),
);
}
},
);
},
child: DB().generalSettings.screenReaderSupport
? const _BoardSemantics()
: Semantics(
label: S.of(context).youCanEnableScreenReaderSupport,
container: true,
),
);

GameController().animationController.forward();
Expand Down
Loading

0 comments on commit a0e04d9

Please sign in to comment.