tablet-app/lib/Screens/Puzzle/puzzle_piece.dart
2024-04-12 17:10:25 +02:00

287 lines
7.8 KiB
Dart

import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/app_context.dart';
class PuzzlePiece extends StatefulWidget {
final CachedNetworkImage image;
final Size imageSize;
final int row;
final int col;
final int maxRow;
final int maxCol;
final Function bringToTop;
final Function sendToBack;
static PuzzlePiece fromMap(Map<String, dynamic> map) {
return PuzzlePiece(
image: map['image'],
imageSize: map['imageSize'],
row: map['row'],
col: map['col'],
maxRow: map['maxRow'],
maxCol: map['maxCol'],
bringToTop: map['bringToTop'],
sendToBack: map['SendToBack'],
);
}
PuzzlePiece(
{Key? key,
required this.image,
required this.imageSize,
required this.row,
required this.col,
required this.maxRow,
required this.maxCol,
required this.bringToTop,
required this.sendToBack})
: super(key: key);
@override
_PuzzlePieceState createState() => _PuzzlePieceState();
}
class _PuzzlePieceState extends State<PuzzlePiece> {
// the piece initial top offset
double? top;
// the piece initial left offset
double? left;
// can we move the piece ?
bool isMovable = true;
GlobalKey _widgetPieceKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
RenderBox renderBox = _widgetPieceKey.currentContext?.findRenderObject() as RenderBox;
Size size = renderBox.size;
final appContext = Provider.of<AppContext>(context, listen: false);
TabletAppContext tabletAppContext = appContext.getContext();
tabletAppContext.puzzleSize = size;
appContext.setContext(tabletAppContext);
if(widget.row == 0 && widget.col == 0) {
widget.sendToBack(widget);
}
});
});
}
@override
Widget build(BuildContext context) {
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
var imageHeight = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.width;
var imageWidth = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.height;
final pieceWidth = imageWidth / widget.maxCol;
final pieceHeight = imageHeight / widget.maxRow;
if (top == null) {
top = Random().nextInt((imageHeight - pieceHeight).ceil()).toDouble();
var test = top!;
test -= widget.row * pieceHeight;
top = test /7; // TODO change ?
}
if (left == null) {
left = Random().nextInt((imageWidth - pieceWidth).ceil()).toDouble();
var test = left!;
test -= widget.col * pieceWidth;
left = test /7; // TODO change ?
}
if(widget.row == 0 && widget.col == 0) {
top = 0;
left = 0;
isMovable = false;
}
return Positioned(
top: top,
left: left,
width: imageWidth,
child: Container(
key: _widgetPieceKey,
decoration: widget.col == 0 && widget.row == 0 ? BoxDecoration(
border: Border.all(
color: Colors.black,
width: 0.5,
),
) : null,
child: GestureDetector(
onTap: () {
if (isMovable) {
widget.bringToTop(widget);
}
},
onPanStart: (_) {
if (isMovable) {
widget.bringToTop(widget);
}
},
onPanUpdate: (dragUpdateDetails) {
if (isMovable) {
setState(() {
var testTop = top!;
var testLeft = left!;
testTop = top!;
testLeft = left!;
testTop += dragUpdateDetails.delta.dy;
testLeft += dragUpdateDetails.delta.dx;
top = testTop;
left = testLeft;
if (-10 < top! && top! < 10 && -10 < left! && left! < 10) {
top = 0;
left = 0;
isMovable = false;
widget.sendToBack(widget);
}
});
}
},
child: ClipPath(
child: CustomPaint(
foregroundPainter: PuzzlePiecePainter(
widget.row, widget.col, widget.maxRow, widget.maxCol),
child: widget.image),
clipper: PuzzlePieceClipper(
widget.row, widget.col, widget.maxRow, widget.maxCol),
),
),
));
}
}
// this class is used to clip the image to the puzzle piece path
class PuzzlePieceClipper extends CustomClipper<Path> {
final int row;
final int col;
final int maxRow;
final int maxCol;
PuzzlePieceClipper(this.row, this.col, this.maxRow, this.maxCol);
@override
Path getClip(Size size) {
return getPiecePath(size, row, col, maxRow, maxCol);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
// this class is used to draw a border around the clipped image
class PuzzlePiecePainter extends CustomPainter {
final int row;
final int col;
final int maxRow;
final int maxCol;
PuzzlePiecePainter(this.row, this.col, this.maxRow, this.maxCol);
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = Colors.black//Color(0x80FFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 2.5;
canvas.drawPath(getPiecePath(size, row, col, maxRow, maxCol), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
// this is the path used to clip the image and, then, to draw a border around it; here we actually draw the puzzle piece
Path getPiecePath(Size size, int row, int col, int maxRow, int maxCol) {
final width = size.width / maxCol;
final height = size.height / maxRow;
final offsetX = col * width;
final offsetY = row * height;
final bumpSize = height / 4;
var path = Path();
path.moveTo(offsetX, offsetY);
if (row == 0) {
// top side piece
path.lineTo(offsetX + width, offsetY);
} else {
// top bump
path.lineTo(offsetX + width / 3, offsetY);
path.cubicTo(
offsetX + width / 6,
offsetY - bumpSize,
offsetX + width / 6 * 5,
offsetY - bumpSize,
offsetX + width / 3 * 2,
offsetY);
path.lineTo(offsetX + width, offsetY);
}
if (col == maxCol - 1) {
// right side piece
path.lineTo(offsetX + width, offsetY + height);
} else {
// right bump
path.lineTo(offsetX + width, offsetY + height / 3);
path.cubicTo(
offsetX + width - bumpSize,
offsetY + height / 6,
offsetX + width - bumpSize,
offsetY + height / 6 * 5,
offsetX + width,
offsetY + height / 3 * 2);
path.lineTo(offsetX + width, offsetY + height);
}
if (row == maxRow - 1) {
// bottom side piece
path.lineTo(offsetX, offsetY + height);
} else {
// bottom bump
path.lineTo(offsetX + width / 3 * 2, offsetY + height);
path.cubicTo(
offsetX + width / 6 * 5,
offsetY + height - bumpSize,
offsetX + width / 6,
offsetY + height - bumpSize,
offsetX + width / 3,
offsetY + height);
path.lineTo(offsetX, offsetY + height);
}
if (col == 0) {
// left side piece
path.close();
} else {
// left bump
path.lineTo(offsetX, offsetY + height / 3 * 2);
path.cubicTo(
offsetX - bumpSize,
offsetY + height / 6 * 5,
offsetX - bumpSize,
offsetY + height / 6,
offsetX,
offsetY + height / 3);
path.close();
}
return path;
}