From 2e78d64a23289bf4fe6089101150f2a23e34a8a2 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 26 Aug 2022 14:15:05 +0200 Subject: [PATCH] [flutter] Assets can now be loaded from bundles, local files, or via http. --- spine-flutter/example/lib/main.dart | 6 +- .../macos/Runner/DebugProfile.entitlements | 2 - spine-flutter/example/pubspec.lock | 21 +++++++ spine-flutter/lib/spine_flutter.dart | 29 +++++++-- spine-flutter/lib/spine_widget.dart | 59 +++++++++++++++---- spine-flutter/pubspec.yaml | 1 + 6 files changed, 97 insertions(+), 21 deletions(-) diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index 7fdd8b288..6f6b96228 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -37,9 +37,9 @@ class Spineboy extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Spineboy')), - body: const Center( - child: SpineWidget("assets/spineboy-pro.skel", "assets/spineboy.atlas") - ), + body: const SpineWidget.asset("assets/spineboy-pro.skel", "assets/spineboy.atlas"), + // body: const SpineWidget.file("/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy-pro.skel", "/Users/badlogic/workspaces/spine-runtimes/examples/spineboy/export/spineboy.atlas"), + // body: const SpineWidget.http("https://marioslab.io/dump/spineboy/spineboy-pro.json", "https://marioslab.io/dump/spineboy/spineboy.atlas"), ); } } diff --git a/spine-flutter/example/macos/Runner/DebugProfile.entitlements b/spine-flutter/example/macos/Runner/DebugProfile.entitlements index dddb8a30c..4eac53fe0 100644 --- a/spine-flutter/example/macos/Runner/DebugProfile.entitlements +++ b/spine-flutter/example/macos/Runner/DebugProfile.entitlements @@ -2,8 +2,6 @@ - com.apple.security.app-sandbox - com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/spine-flutter/example/pubspec.lock b/spine-flutter/example/pubspec.lock index 7d084421d..84047a5e5 100644 --- a/spine-flutter/example/pubspec.lock +++ b/spine-flutter/example/pubspec.lock @@ -81,6 +81,20 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" lints: dependency: transitive description: @@ -177,6 +191,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 7499ddd57..f0abf8336 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -1,9 +1,11 @@ +import 'dart:convert' as convert; import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui'; import 'package:ffi/ffi.dart'; import 'package:flutter/rendering.dart'; +import 'package:http/http.dart' as http; import 'package:flutter/services.dart'; import 'spine_flutter_bindings_generated.dart'; @@ -22,8 +24,9 @@ class SpineAtlas { SpineAtlas(this._atlas, this.atlasPages, this.atlasPagePaints): _disposed = false; - static Future fromAsset(AssetBundle assetBundle, String atlasFileName) async { - final atlasData = await assetBundle.loadString(atlasFileName); + static Future _load(String atlasFileName, Future Function(String name) loadFile) async { + final atlasBytes = await loadFile(atlasFileName); + final atlasData = convert.utf8.decode(atlasBytes); final atlasDataNative = atlasData.toNativeUtf8(); final atlas = _bindings.spine_atlas_load(atlasDataNative.cast()); calloc.free(atlasDataNative); @@ -40,8 +43,8 @@ class SpineAtlas { for (int i = 0; i < atlas.ref.numImagePaths; i++) { final Pointer atlasPageFile = atlas.ref.imagePaths[i].cast(); final imagePath = Path.join(atlasDir, atlasPageFile.toDartString()); - var imageData = await assetBundle.load(imagePath); - final Codec codec = await instantiateImageCodec(imageData.buffer.asUint8List()); + var imageData = await loadFile(imagePath); + final Codec codec = await instantiateImageCodec(imageData); final FrameInfo frameInfo = await codec.getNextFrame(); final Image image = frameInfo.image; atlasPages.add(image); @@ -54,6 +57,20 @@ class SpineAtlas { return SpineAtlas(atlas, atlasPages, atlasPagePaints); } + static Future fromAsset(AssetBundle assetBundle, String atlasFileName) async { + return _load(atlasFileName, (file) async => (await assetBundle.load(file)).buffer.asUint8List()); + } + + static Future fromFile(String atlasFileName) async { + return _load(atlasFileName, (file) => File(file).readAsBytes()); + } + + static Future fromUrl(String atlasFileName) async { + return _load(atlasFileName, (file) async { + return (await http.get(Uri.parse(file))).bodyBytes; + }); + } + void dispose() { if (_disposed) return; _disposed = true; @@ -80,9 +97,9 @@ class SpineSkeletonData { return SpineSkeletonData(skeletonData); } - static SpineSkeletonData fromBinary(SpineAtlas atlas, ByteData binary) { + static SpineSkeletonData fromBinary(SpineAtlas atlas, Uint8List binary) { final Pointer binaryNative = malloc.allocate(binary.lengthInBytes); - binaryNative.asTypedList(binary.lengthInBytes).setAll(0, binary.buffer.asUint8List()); + binaryNative.asTypedList(binary.lengthInBytes).setAll(0, binary); final skeletonData = _bindings.spine_skeleton_data_load_binary(atlas._atlas, binaryNative.cast(), binary.lengthInBytes); malloc.free(binaryNative); if (skeletonData.ref.error.address != nullptr.address) { diff --git a/spine-flutter/lib/spine_widget.dart b/spine-flutter/lib/spine_widget.dart index 7d6c4b004..a9c964fb8 100644 --- a/spine-flutter/lib/spine_widget.dart +++ b/spine-flutter/lib/spine_widget.dart @@ -1,15 +1,31 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:http/http.dart' as http; import 'spine_flutter.dart'; +abstract class SpineWidgetController { +} + +enum AssetType { + Asset, + File, + Http +} + class SpineWidget extends StatefulWidget { final String skeletonFile; final String atlasFile; + final AssetType _assetType; - const SpineWidget(this.skeletonFile, this.atlasFile, {super.key}); + const SpineWidget.asset(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.Asset; + const SpineWidget.file(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.File; + const SpineWidget.http(this.skeletonFile, this.atlasFile, {super.key}): _assetType = AssetType.Http; @override State createState() => _SpineWidgetState(); @@ -21,17 +37,40 @@ class _SpineWidgetState extends State { @override void initState() { super.initState(); - loadSkeleton(widget.skeletonFile, widget.atlasFile); + loadSkeleton(widget.skeletonFile, widget.atlasFile, widget._assetType); } - 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)); + void loadSkeleton(String skeletonFile, String atlasFile, AssetType assetType) async { + late SpineAtlas atlas; + late SpineSkeletonData skeletonData; + + switch (assetType) { + case AssetType.Asset: + atlas = await SpineAtlas.fromAsset(rootBundle, atlasFile); + skeletonData = skeletonFile.endsWith(".json") + ? SpineSkeletonData.fromJson( + atlas, await rootBundle.loadString(skeletonFile)) + : SpineSkeletonData.fromBinary( + atlas, (await rootBundle.load(skeletonFile)).buffer.asUint8List()); + break; + case AssetType.File: + atlas = await SpineAtlas.fromFile(atlasFile); + skeletonData = skeletonFile.endsWith(".json") + ? SpineSkeletonData.fromJson( + atlas, utf8.decode(await File(skeletonFile).readAsBytes())) + : SpineSkeletonData.fromBinary( + atlas, await File(skeletonFile).readAsBytes()); + break; + case AssetType.Http: + atlas = await SpineAtlas.fromUrl(atlasFile); + skeletonData = skeletonFile.endsWith(".json") + ? SpineSkeletonData.fromJson( + atlas, utf8.decode((await http.get(Uri.parse(skeletonFile))).bodyBytes)) + : SpineSkeletonData.fromBinary( + atlas, (await http.get(Uri.parse(skeletonFile))).bodyBytes); + break; + } + skeletonDrawable = SpineSkeletonDrawable(atlas, skeletonData); skeletonDrawable?.update(0.016); setState(() {}); diff --git a/spine-flutter/pubspec.yaml b/spine-flutter/pubspec.yaml index 47cbe7ec2..08706c946 100644 --- a/spine-flutter/pubspec.yaml +++ b/spine-flutter/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: sdk: flutter plugin_platform_interface: ^2.0.2 ffi: ^1.1.2 + http: ^0.13.5 dev_dependencies: ffigen: ^4.1.2