Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active September 30, 2022 03:05
Show Gist options
  • Save roipeker/7495ca1eadf7bf6be393d9ed12566027 to your computer and use it in GitHub Desktop.
Save roipeker/7495ca1eadf7bf6be393d9ed12566027 to your computer and use it in GitHub Desktop.
Graphx puzzle grid demo. Image mapping, useful for jigsaw puzzles.
import 'package:flutter/material.dart';
import 'package:graphx/graphx.dart';
/// Live demo:
/// https://graphx-puzzle-ref.surge.sh/
/// Remember, add graphx to your pubspec!
/// You can get the image used in the demo here
/// https://graphx-puzzle-ref.surge.sh/assets/assets/ath.jpg
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'GraphX Puzzle Reference',
home: MyHomePage(title: 'GraphX Puzzle Reference'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SceneBuilderWidget(
builder: () => SceneController(
back: MyScene(),
),
autoSize: true,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
/// -- GraphX code here --
class MyScene extends GSprite {
@override
void addedToStage() {
// Show stage bounds (critical to debug so we know we will get
// the right size for the stage AND TOUCH/MOUSE events!).
// Use `SceneBuilderWidget::autoSize=true` to ensure an expanded stage size.
stage!.showBoundsRect = true;
loadImage();
}
Future<void> loadImage() async {
// load image from assets
var texture = await ResourceLoader.loadTexture('assets/ath.jpg', 1);
// add a "ghost" image for reference.
var bmp = GBitmap(texture);
bmp.alpha = .28;
addChild(bmp);
var container = GSprite();
addChild(container);
/// scaling down the "root", so we can see the bounds. for debugging.
scale = 0.5;
var w = bmp.texture!.width!;
var h = bmp.texture!.height!;
var cols = 6;
var rows = 4;
var tileW = w / cols;
var tileH = h / rows;
var totalPieces = cols * rows;
for (var i = 0; i < totalPieces; ++i) {
var piece = GSprite();
// Using ::userData as an ugly way to store the mouse state interaction.
piece.userData = 0;
piece.onMouseOver.add((event) {
var p = event.target! as GSprite;
p.userData = 1; // "over" state (irrelevant)
/// move the piece at the top of other siblings.
p.parent!.addChild(p);
/// "hack" required to not crop the Dropshadow.
p.$useSaveLayerBounds = false;
p.filters = [GDropShadowFilter(8, 8, 0, 2)];
p.tween(
duration: 0.3,
colorize: Colors.white.withOpacity(.25),
ease: GEase.easeOut,
scale: 1.1
);
p.onMouseOut.addOnce((event) {
p.filters = null;
// piece was pressed and "released", so is bouncing.
// So, don't override the tween.
if (p.userData == 3) {
return;
}
p.userData = 0;
p.tween(
duration: 0.8,
ease: GEase.easeOutBack,
colorize: Colors.transparent,
scale: 1,
);
});
});
piece.onMouseDown.add((event) {
var p = event.target!;
p.startDrag(true);
p.userData = 2; // "press" state (irrelevant).
p.tween(duration: 0.3, ease: GEase.easeOutBack, scale: 1.5);
stage!.onMouseUp.addOnce((event) {
p.stopDrag();
p.userData = 3; // "release" state (RELEVANT for mouseOut).
p.tween(
duration: 0.8,
ease: GEase.bounceOut,
colorize: Colors.transparent,
scale: 1,
);
});
});
container.addChild(piece);
// "grid" layout.
piece.x = (i % cols) * tileW;
piece.y = (i ~/ cols) * tileH;
// Matrix magic for the texture.
final matrix = GMatrix();
matrix.translate(-piece.x, -piece.y);
piece.graphics.beginBitmapFill(texture, matrix);
piece.graphics.drawRect(0, 0, tileW, tileH);
piece.graphics.endFill();
// center the pivot point for each piece!
// so we can animate the scale from the center (looks nicer).
piece.alignPivot();
piece.x += piece.pivotX;
piece.y += piece.pivotY;
// add some border to the piece..
piece.graphics.lineStyle(3, Colors.red, true);
piece.graphics.drawRect(0, 0, tileW, tileH);
piece.graphics.endFill();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment