From a986469e07e3d1fcd13a5c25a471974b4f828cbc Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 25 Aug 2022 11:13:56 +0200 Subject: [PATCH] [flutter] Refactoring, example selector --- spine-flutter/example/lib/main.dart | 189 +++++++-------------------- spine-flutter/lib/spine_flutter.dart | 3 +- spine-flutter/lib/spine_widget.dart | 135 +++++++++++++++++++ 3 files changed, 181 insertions(+), 146 deletions(-) create mode 100644 spine-flutter/lib/spine_widget.dart diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index f30053824..466ad2a09 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -1,154 +1,53 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:spine_flutter/spine_flutter.dart' as spine_flutter; -import 'package:flutter/services.dart' show rootBundle; import 'package:spine_flutter/spine_flutter.dart'; -void main() { - runApp(const MyApp()); -} - -class SpineWidget extends StatefulWidget { - final String skeletonFile; - final String atlasFile; - - const SpineWidget(this.skeletonFile, this.atlasFile, {super.key}); - - @override - State createState() => _SpineWidgetState(); -} - -class _SpineWidgetState extends State { - SpineSkeletonDrawable? skeletonDrawable; - - @override - void initState() { - super.initState(); - loadSkeleton(widget.skeletonFile, widget.atlasFile); - } - - void loadSkeleton(String skeletonFile, String atlasFile) async { - final atlas = await spine_flutter.SpineAtlas.fromAsset(rootBundle, atlasFile); - final skeletonData = skeletonFile.endsWith(".json") ? - spine_flutter.SpineSkeletonData.fromJson(atlas, await rootBundle.loadString(skeletonFile)) - : spine_flutter.SpineSkeletonData.fromBinary(atlas, await rootBundle.load(skeletonFile)); - skeletonDrawable = spine_flutter.SpineSkeletonDrawable(atlas, skeletonData); - skeletonDrawable?.update(0.016); - setState(() {}); - } - +class ExampleSelector extends StatelessWidget { @override Widget build(BuildContext context) { - if (skeletonDrawable != null) { - print("Skeleton loaded, rebuilding painter"); - return _SpineRenderObjectWidget(skeletonDrawable!); - } else { - print("Skeleton not loaded yet"); - return SizedBox(); - } - } -} + const spacer = SizedBox(height: 10); -class _SpineRenderObjectWidget extends LeafRenderObjectWidget { - final SpineSkeletonDrawable skeletonDrawable; - _SpineRenderObjectWidget(this.skeletonDrawable); - - @override - RenderObject createRenderObject(BuildContext context) { - return _SpineRenderObject(skeletonDrawable); - } - - @override - void updateRenderObject(BuildContext context, covariant _SpineRenderObject renderObject) { - renderObject.skeletonDrawable = skeletonDrawable; - } -} - -class _SpineRenderObject extends RenderBox { - SpineSkeletonDrawable _skeletonDrawable; - double _deltaTime = 0; - final Stopwatch _stopwatch = Stopwatch(); - - _SpineRenderObject(this._skeletonDrawable); - - set skeletonDrawable(SpineSkeletonDrawable skeletonDrawable) { - if (_skeletonDrawable == skeletonDrawable) return; - - // FIXME dispose old drawable here? - _skeletonDrawable = skeletonDrawable; - markNeedsPaint(); - } - - @override - bool get sizedByParent => true; - - @override - bool get isRepaintBoundary => true; - - @override - bool hitTestSelf(Offset position) => true; - - @override - void performResize() { - size = constraints.biggest; - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _stopwatch.start(); - } - - @override - void detach() { - _stopwatch.stop(); - super.detach(); - } - - void _beginFrame(Duration duration) { - _deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency; - _stopwatch.reset(); - _stopwatch.start(); - markNeedsPaint(); - } - - @override - void paint(PaintingContext context, Offset offset) { - print("painting"); - final Canvas canvas = context.canvas - ..save() - ..clipRect(offset & size); - - final commands = _skeletonDrawable.render(); - canvas.save(); - canvas.translate(offset.dx, offset.dy); - for (final cmd in commands) { - canvas.drawVertices(cmd.vertices, BlendMode.modulate, _skeletonDrawable.atlas.atlasPagePaints[cmd.atlasPageIndex]); - } - - canvas.restore(); - SchedulerBinding.instance.scheduleFrameCallback(_beginFrame); - } -} - -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - - @override - _MyAppState createState() => _MyAppState(); -} - -class _MyAppState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: SpineWidget("assets/spineboy-pro.json", "assets/spineboy.atlas") + return Scaffold( + appBar: AppBar(title: const Text('Spine Examples')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + child: const Text('Spineboy'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const Spineboy(), + ), + ); + }, + ), + spacer + ] + ) + ) ); } } + +class Spineboy extends StatelessWidget { + const Spineboy({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Spineboy')), + body: const Center( + child: SpineWidget("assets/spineboy-pro.skel", "assets/spineboy.atlas") + ), + ); + } +} + +void main() { + runApp(MaterialApp( + title: "Spine Examples", + home: Spineboy() + )); +} diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index ae8a0078c..36d6e339d 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'spine_flutter_bindings_generated.dart'; +export 'spine_widget.dart'; import 'package:path/path.dart' as Path; int majorVersion() => _bindings.spine_major_version(); @@ -150,4 +151,4 @@ final DynamicLibrary _dylib = () { throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); }(); -final SpineFlutterBindings _bindings = SpineFlutterBindings(_dylib); \ No newline at end of file +final SpineFlutterBindings _bindings = SpineFlutterBindings(_dylib); diff --git a/spine-flutter/lib/spine_widget.dart b/spine-flutter/lib/spine_widget.dart new file mode 100644 index 000000000..06a0443b4 --- /dev/null +++ b/spine-flutter/lib/spine_widget.dart @@ -0,0 +1,135 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'spine_flutter.dart'; + +class SpineWidget extends StatefulWidget { + final String skeletonFile; + final String atlasFile; + + const SpineWidget(this.skeletonFile, this.atlasFile, {super.key}); + + @override + State createState() => _SpineWidgetState(); +} + +class _SpineWidgetState extends State { + SpineSkeletonDrawable? skeletonDrawable; + + @override + void initState() { + super.initState(); + loadSkeleton(widget.skeletonFile, widget.atlasFile); + } + + void loadSkeleton(String skeletonFile, String atlasFile) async { + final atlas = + await SpineAtlas.fromAsset(rootBundle, atlasFile); + final skeletonData = skeletonFile.endsWith(".json") + ? SpineSkeletonData.fromJson( + atlas, await rootBundle.loadString(skeletonFile)) + : SpineSkeletonData.fromBinary( + atlas, await rootBundle.load(skeletonFile)); + skeletonDrawable = SpineSkeletonDrawable(atlas, skeletonData); + skeletonDrawable?.update(0.016); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + if (skeletonDrawable != null) { + print("Skeleton loaded, rebuilding painter"); + return _SpineRenderObjectWidget(skeletonDrawable!); + } else { + print("Skeleton not loaded yet"); + return SizedBox(); + } + } +} + +class _SpineRenderObjectWidget extends LeafRenderObjectWidget { + final SpineSkeletonDrawable skeletonDrawable; + + _SpineRenderObjectWidget(this.skeletonDrawable); + + @override + RenderObject createRenderObject(BuildContext context) { + return _SpineRenderObject(skeletonDrawable); + } + + @override + void updateRenderObject(BuildContext context, + covariant _SpineRenderObject renderObject) { + renderObject.skeletonDrawable = skeletonDrawable; + } +} + +class _SpineRenderObject extends RenderBox { + SpineSkeletonDrawable _skeletonDrawable; + double _deltaTime = 0; + final Stopwatch _stopwatch = Stopwatch(); + + _SpineRenderObject(this._skeletonDrawable); + + set skeletonDrawable(SpineSkeletonDrawable skeletonDrawable) { + if (_skeletonDrawable == skeletonDrawable) return; + + // FIXME dispose old drawable here? + _skeletonDrawable = skeletonDrawable; + markNeedsPaint(); + } + + @override + bool get sizedByParent => true; + + @override + bool get isRepaintBoundary => true; + + @override + bool hitTestSelf(Offset position) => true; + + @override + void performResize() { + size = constraints.biggest; + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _stopwatch.start(); + } + + @override + void detach() { + _stopwatch.stop(); + super.detach(); + } + + void _beginFrame(Duration duration) { + _deltaTime = _stopwatch.elapsedTicks / _stopwatch.frequency; + _stopwatch.reset(); + _stopwatch.start(); + _skeletonDrawable.update(_deltaTime); + markNeedsPaint(); + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas + ..save() + ..clipRect(offset & size); + + final commands = _skeletonDrawable.render(); + canvas.save(); + canvas.translate(offset.dx + size.width / 2, offset.dy + size.height); + for (final cmd in commands) { + canvas.drawVertices(cmd.vertices, BlendMode.modulate, + _skeletonDrawable.atlas.atlasPagePaints[cmd.atlasPageIndex]); + } + + canvas.restore(); + SchedulerBinding.instance.scheduleFrameCallback(_beginFrame); + } +}