Last active
September 30, 2022 03:05
-
-
Save roipeker/7495ca1eadf7bf6be393d9ed12566027 to your computer and use it in GitHub Desktop.
Graphx puzzle grid demo. Image mapping, useful for jigsaw puzzles.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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