mirror of
https://bitbucket.org/FransoletThomas/tablet-app.git
synced 2025-12-06 08:31:19 +00:00
287 lines
7.8 KiB
Dart
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;
|
|
} |