mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
[flutter] macOS/iOS builds, fixed examples wrt new API
This commit is contained in:
parent
83f5bd0e0a
commit
4225214caf
@ -24,7 +24,7 @@ class AnimationStateEvents extends StatelessWidget {
|
||||
controller.animationState.setListener((type, trackEntry, event) {
|
||||
if (type == EventType.event) {
|
||||
print(
|
||||
"User event: { name: ${event?.getData().getName()}, intValue: ${event?.getIntValue()}, floatValue: ${event?.getFloatValue()}, stringValue: ${event?.getStringValue()} }",
|
||||
"User event: { name: ${event?.data.name}, intValue: ${event?.intValue}, floatValue: ${event?.floatValue}, stringValue: ${event?.stringValue} }",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -40,7 +40,7 @@ class DebugRendering extends StatelessWidget {
|
||||
const debugRenderer = DebugRenderer();
|
||||
final controller = SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
controller.animationState.setAnimationByName(0, "walk", true);
|
||||
controller.animationState.setAnimation(0, "walk", true);
|
||||
},
|
||||
onBeforePaint: (controller, canvas) {
|
||||
// Save the current transform and other canvas state
|
||||
|
||||
@ -41,7 +41,7 @@ class DressUp extends StatefulWidget {
|
||||
|
||||
class DressUpState extends State<DressUp> {
|
||||
static const double thumbnailSize = 200;
|
||||
late SkeletonDrawable _drawable;
|
||||
late SkeletonDrawableFlutter _drawable;
|
||||
Skin? _customSkin;
|
||||
final Map<String, RawImageData> _skinImages = {};
|
||||
final Map<String, bool> _selectedSkins = {};
|
||||
@ -50,17 +50,19 @@ class DressUpState extends State<DressUp> {
|
||||
void initState() {
|
||||
reportLeaks();
|
||||
super.initState();
|
||||
SkeletonDrawable.fromAsset("assets/mix-and-match.atlas", "assets/mix-and-match-pro.skel").then((drawable) async {
|
||||
SkeletonDrawableFlutter.fromAsset("assets/mix-and-match.atlas", "assets/mix-and-match-pro.skel")
|
||||
.then((drawable) async {
|
||||
_drawable = drawable;
|
||||
for (var skin in drawable.skeletonData.getSkins()) {
|
||||
if (skin.getName() == "default") continue;
|
||||
for (var skin in drawable.skeletonData.skins) {
|
||||
if (skin == null) continue;
|
||||
if (skin.name == "default") continue;
|
||||
var skeleton = drawable.skeleton;
|
||||
skeleton.setSkin(skin);
|
||||
skeleton.setToSetupPose();
|
||||
skeleton.setSkin2(skin);
|
||||
skeleton.setupPose();
|
||||
skeleton.update(0);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
_skinImages[skin.getName()] = await drawable.renderToRawImageData(thumbnailSize, thumbnailSize, 0xffffffff);
|
||||
_selectedSkins[skin.getName()] = false;
|
||||
_skinImages[skin.name] = await drawable.renderToRawImageData(thumbnailSize, thumbnailSize, 0xffffffff);
|
||||
_selectedSkins[skin.name] = false;
|
||||
}
|
||||
_toggleSkin("full-skins/girl");
|
||||
setState(() {});
|
||||
@ -69,7 +71,7 @@ class DressUpState extends State<DressUp> {
|
||||
|
||||
void _toggleSkin(String skinName) {
|
||||
_selectedSkins[skinName] = !_selectedSkins[skinName]!;
|
||||
_drawable.skeleton.setSkinByName("default");
|
||||
_drawable.skeleton.setSkin("default");
|
||||
if (_customSkin != null) _customSkin?.dispose();
|
||||
_customSkin = Skin("custom-skin");
|
||||
for (var skinName in _selectedSkins.keys) {
|
||||
@ -78,15 +80,15 @@ class DressUpState extends State<DressUp> {
|
||||
if (skin != null) _customSkin?.addSkin(skin);
|
||||
}
|
||||
}
|
||||
_drawable.skeleton.setSkin(_customSkin!);
|
||||
_drawable.skeleton.setSlotsToSetupPose();
|
||||
_drawable.skeleton.setSkin2(_customSkin!);
|
||||
_drawable.skeleton.setupPoseSlots();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
controller.animationState.setAnimationByName(0, "dance", true);
|
||||
controller.animationState.setAnimation(0, "dance", true);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class SpineComponent extends PositionComponent {
|
||||
final BoundsProvider _boundsProvider;
|
||||
final SkeletonDrawable _drawable;
|
||||
final SkeletonDrawableFlutter _drawable;
|
||||
late final Bounds _bounds;
|
||||
final bool _ownsDrawable;
|
||||
|
||||
@ -70,7 +70,7 @@ class SpineComponent extends PositionComponent {
|
||||
int? priority,
|
||||
}) async {
|
||||
return SpineComponent(
|
||||
await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle),
|
||||
await SkeletonDrawableFlutter.fromAsset(atlasFile, skeletonFile, bundle: bundle),
|
||||
ownsDrawable: true,
|
||||
boundsProvider: boundsProvider,
|
||||
position: position,
|
||||
@ -137,15 +137,15 @@ class SimpleFlameExample extends FlameGame {
|
||||
}
|
||||
|
||||
class DragonExample extends FlameGame {
|
||||
late final Atlas cachedAtlas;
|
||||
late final AtlasFlutter cachedAtlas;
|
||||
late final SkeletonData cachedSkeletonData;
|
||||
late final SpineComponent dragon;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
cachedAtlas = await Atlas.fromAsset("assets/dragon.atlas");
|
||||
cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/dragon-ess.skel");
|
||||
final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
|
||||
cachedAtlas = await AtlasFlutter.fromAsset("assets/dragon.atlas");
|
||||
cachedSkeletonData = await SkeletonDataFlutter.fromAsset(cachedAtlas, "assets/dragon-ess.skel");
|
||||
final drawable = SkeletonDrawableFlutter(cachedAtlas, cachedSkeletonData, false);
|
||||
dragon = SpineComponent(
|
||||
drawable,
|
||||
scale: Vector2(0.4, 0.4),
|
||||
@ -168,21 +168,21 @@ class DragonExample extends FlameGame {
|
||||
|
||||
class PreloadAndShareSpineDataExample extends FlameGame {
|
||||
late final SkeletonData cachedSkeletonData;
|
||||
late final Atlas cachedAtlas;
|
||||
late final AtlasFlutter cachedAtlas;
|
||||
late final List<SpineComponent> spineboys = [];
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
// Pre-load the atlas and skeleton data once.
|
||||
cachedAtlas = await Atlas.fromAsset("assets/spineboy.atlas");
|
||||
cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/spineboy-pro.skel");
|
||||
cachedAtlas = await AtlasFlutter.fromAsset("assets/spineboy.atlas");
|
||||
cachedSkeletonData = await SkeletonDataFlutter.fromAsset(cachedAtlas, "assets/spineboy-pro.skel");
|
||||
|
||||
// Instantiate many spineboys from the pre-loaded data. Each SpineComponent
|
||||
// gets their own SkeletonDrawable copy derived from the cached data. The
|
||||
// SkeletonDrawable copies do not own the underlying skeleton data and atlas.
|
||||
final rng = Random();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
|
||||
final drawable = SkeletonDrawableFlutter(cachedAtlas, cachedSkeletonData, false);
|
||||
final scale = 0.1 + rng.nextDouble() * 0.2;
|
||||
final position = Vector2(rng.nextDouble() * size.x, rng.nextDouble() * size.y);
|
||||
final spineboy = SpineComponent(drawable, scale: Vector2(scale, scale), position: position);
|
||||
|
||||
@ -48,17 +48,19 @@ class IkFollowingState extends State<IkFollowing> {
|
||||
controller = SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
// Set the walk animation on track 0, let it loop
|
||||
controller.animationState.setAnimationByName(0, "walk", true);
|
||||
controller.animationState.setAnimationByName(1, "aim", true);
|
||||
controller.animationState.setAnimation(0, "walk", true);
|
||||
controller.animationState.setAnimation(1, "aim", true);
|
||||
},
|
||||
onAfterUpdateWorldTransforms: (controller) {
|
||||
final worldPosition = crossHairPosition;
|
||||
if (worldPosition == null) return;
|
||||
final bone = controller.skeleton.findBone("crosshair")!;
|
||||
final parent = bone.getParent()!;
|
||||
final position = parent.worldToLocal(worldPosition.dx, worldPosition.dy);
|
||||
bone.setX(position.x);
|
||||
bone.setY(position.y);
|
||||
final parent = bone.parent;
|
||||
if (parent != null) {
|
||||
final position = parent.appliedPose.worldToLocal(worldPosition.dx, worldPosition.dy);
|
||||
bone.appliedPose.x = position.x;
|
||||
bone.appliedPose.y = position.y;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ import 'package:spine_flutter/spine_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlayPauseAnimation extends StatefulWidget {
|
||||
const PlayPauseAnimation({Key? key}) : super(key: key);
|
||||
const PlayPauseAnimation({super.key});
|
||||
|
||||
@override
|
||||
PlayPauseAnimationState createState() => PlayPauseAnimationState();
|
||||
@ -45,7 +45,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
|
||||
super.initState();
|
||||
controller = SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
controller.animationState.setAnimationByName(0, "flying", true);
|
||||
controller.animationState.setAnimation(0, "flying", true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -48,8 +48,8 @@ class PhysicsState extends State<PhysicsTest> {
|
||||
|
||||
controller = SpineWidgetController(
|
||||
onInitialized: (controller) {
|
||||
controller.animationState.setAnimationByName(0, "eyeblink-long", true);
|
||||
controller.animationState.setAnimationByName(1, "wings-and-feet", true);
|
||||
controller.animationState.setAnimation(0, "eyeblink-long", true);
|
||||
controller.animationState.setAnimation(1, "wings-and-feet", true);
|
||||
},
|
||||
onAfterUpdateWorldTransforms: (controller) {
|
||||
if (lastMousePosition == null) {
|
||||
|
||||
@ -5,5 +5,6 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
spine_flutter: 75f9d54a630ac150d238210f9c211529c37c11ba
|
||||
spine_flutter: 8469a2cfb87c5a7101a7c87dc7c14ee49699ea3b
|
||||
|
||||
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
||||
@ -21,6 +21,16 @@ if [ ! -d "codegen/node_modules" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generating spine-c bindings
|
||||
log_action "Generating spine-c bindings"
|
||||
if LOG=$(cd ../spine-c && ./build.sh codegen 2>&1); then
|
||||
log_ok
|
||||
else
|
||||
log_fail
|
||||
log_error_output "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy spine-c and spine-cpp sources
|
||||
log_action "Setting up source files"
|
||||
if ./setup.sh > /dev/null 2>&1; then
|
||||
@ -32,19 +42,21 @@ fi
|
||||
|
||||
# Run the codegen
|
||||
log_action "Generating Dart bindings"
|
||||
if npx tsx codegen/src/index.ts > /dev/null 2>&1; then
|
||||
if LOG=$(npx tsx codegen/src/index.ts 2>&1); then
|
||||
log_ok
|
||||
else
|
||||
log_fail
|
||||
log_error_output "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build test spine_flutter shared library
|
||||
log_action "Building test library"
|
||||
if (cd test && ./build.sh > /dev/null 2>&1); then
|
||||
if LOG=$(cd test && ./build.sh 2>&1); then
|
||||
log_ok
|
||||
else
|
||||
log_fail
|
||||
log_error_output "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
#include "../../src/spine-cpp-lite/spine-cpp-lite.cpp"
|
||||
@ -12,14 +12,9 @@ A new Flutter FFI plugin project.
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
# This will ensure the source files in Classes/ are included in the native
|
||||
# builds of apps using this FFI plugin. Podspec does not support relative
|
||||
# paths, so Classes contains a forwarder C file that relatively imports
|
||||
# `../src/*` so that the C sources can be shared among all target platforms.
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*.{cpp}'
|
||||
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/Classes/spine-cpp/include"' }
|
||||
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/Classes/spine-cpp/include" "$(PODS_TARGET_SRCROOT)/Classes/spine-c/include"' }
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '9.0'
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -80,10 +80,10 @@ class Animation {
|
||||
SpineBindings.bindings.spine_animation_set_duration(_ptr, value);
|
||||
}
|
||||
|
||||
void apply(Skeleton skeleton, double lastTime, double time, bool loop, ArrayEvent? pEvents, double alpha,
|
||||
void apply(Skeleton skeleton, double lastTime, double time, bool loop, ArrayEvent? events, double alpha,
|
||||
MixBlend blend, MixDirection direction, bool appliedPose) {
|
||||
SpineBindings.bindings.spine_animation_apply(_ptr, skeleton.nativePtr.cast(), lastTime, time, loop,
|
||||
pEvents?.nativePtr.cast() ?? Pointer.fromAddress(0), alpha, blend.value, direction.value, appliedPose);
|
||||
events?.nativePtr.cast() ?? Pointer.fromAddress(0), alpha, blend.value, direction.value, appliedPose);
|
||||
}
|
||||
|
||||
String get name {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -52,10 +52,10 @@ abstract class Timeline {
|
||||
return Rtti.fromPointer(result);
|
||||
}
|
||||
|
||||
void apply(Skeleton skeleton, double lastTime, double time, ArrayEvent? pEvents, double alpha, MixBlend blend,
|
||||
void apply(Skeleton skeleton, double lastTime, double time, ArrayEvent? events, double alpha, MixBlend blend,
|
||||
MixDirection direction, bool appliedPose) {
|
||||
SpineBindings.bindings.spine_timeline_apply(_ptr, skeleton.nativePtr.cast(), lastTime, time,
|
||||
pEvents?.nativePtr.cast() ?? Pointer.fromAddress(0), alpha, blend.value, direction.value, appliedPose);
|
||||
events?.nativePtr.cast() ?? Pointer.fromAddress(0), alpha, blend.value, direction.value, appliedPose);
|
||||
}
|
||||
|
||||
int get frameEntries {
|
||||
|
||||
@ -50,6 +50,7 @@ import 'generated/event.dart';
|
||||
import 'generated/event_type.dart';
|
||||
import 'generated/render_command.dart';
|
||||
import 'generated/physics.dart';
|
||||
import 'generated/bone_pose.dart';
|
||||
|
||||
// Export generated classes
|
||||
export 'generated/api.dart';
|
||||
@ -321,20 +322,24 @@ extension TrackEntryExtensions on TrackEntry {
|
||||
|
||||
/// Represents a bounding box with position and dimensions
|
||||
class Bounds {
|
||||
final double x;
|
||||
final double y;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const Bounds({
|
||||
double x;
|
||||
double y;
|
||||
double width;
|
||||
double height;
|
||||
|
||||
Bounds({
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => 'Bounds(x: $x, y: $y, width: $width, height: $height)';
|
||||
}
|
||||
|
||||
class Vector {
|
||||
double x;
|
||||
double y;
|
||||
|
||||
Vector({required this.x, required this.y});
|
||||
}
|
||||
|
||||
/// Extension to add bounds property to Skeleton
|
||||
@ -349,6 +354,33 @@ extension SkeletonExtensions on Skeleton {
|
||||
height: spineBounds.height,
|
||||
);
|
||||
}
|
||||
|
||||
Vector getPosition() {
|
||||
final position = SpineBindings.bindings.spine_skeleton_get_position_v(nativePtr.cast());
|
||||
return Vector(x: position.x, y: position.y);
|
||||
}
|
||||
}
|
||||
|
||||
extension BonePoseExtensions on BonePose {
|
||||
Vector worldToLocal(double worldX, double worldY) {
|
||||
final result = SpineBindings.bindings.spine_bone_pose_world_to_local_v(nativePtr.cast(), worldX, worldY);
|
||||
return Vector(x: result.x, y: result.y);
|
||||
}
|
||||
|
||||
Vector localToWorld(double localX, double localY) {
|
||||
final result = SpineBindings.bindings.spine_bone_pose_local_to_world_v(nativePtr.cast(), localX, localY);
|
||||
return Vector(x: result.x, y: result.y);
|
||||
}
|
||||
|
||||
Vector worldToParent(double worldX, double worldY) {
|
||||
final result = SpineBindings.bindings.spine_bone_pose_world_to_parent_v(nativePtr.cast(), worldX, worldY);
|
||||
return Vector(x: result.x, y: result.y);
|
||||
}
|
||||
|
||||
Vector parentToWorld(double parentX, double parentY) {
|
||||
final result = SpineBindings.bindings.spine_bone_pose_parent_to_world_v(nativePtr.cast(), parentX, parentY);
|
||||
return Vector(x: result.x, y: result.y);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenient drawable that combines skeleton, animation state, and rendering
|
||||
@ -419,7 +451,7 @@ class SkeletonDrawable {
|
||||
|
||||
// Apply animation state to skeleton
|
||||
animationState.apply(skeleton);
|
||||
|
||||
|
||||
// Update skeleton physics and world transforms
|
||||
skeleton.update(delta);
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
|
||||
@ -24,14 +24,13 @@ Future<void> initSpineFlutter({bool useStaticLinkage = false, bool enableMemoryD
|
||||
}
|
||||
|
||||
/// Flutter wrapper for Atlas that manages texture loading and Paint creation
|
||||
class AtlasFlutter {
|
||||
class AtlasFlutter extends Atlas {
|
||||
static FilterQuality filterQuality = FilterQuality.low;
|
||||
final Atlas atlas;
|
||||
final List<Image> atlasPages;
|
||||
final List<Map<BlendMode, Paint>> atlasPagePaints;
|
||||
bool _disposed = false;
|
||||
|
||||
AtlasFlutter._(this.atlas, this.atlasPages, this.atlasPagePaints);
|
||||
AtlasFlutter._(super.ptr, this.atlasPages, this.atlasPagePaints) : super.fromPointer();
|
||||
|
||||
/// Internal method to load atlas and images
|
||||
static Future<AtlasFlutter> _load(String atlasFileName, Future<Uint8List> Function(String name) loadFile) async {
|
||||
@ -77,7 +76,7 @@ class AtlasFlutter {
|
||||
paints.add(pagePaints);
|
||||
}
|
||||
|
||||
return AtlasFlutter._(atlas, pages, paints);
|
||||
return AtlasFlutter._(atlas.nativePtr.cast(), pages, paints);
|
||||
}
|
||||
|
||||
/// Loads an [AtlasFlutter] from the file [atlasFileName] in the root bundle or the optionally provided [bundle].
|
||||
@ -103,10 +102,11 @@ class AtlasFlutter {
|
||||
}
|
||||
|
||||
/// Disposes all resources including the native atlas and images
|
||||
@override
|
||||
void dispose() {
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
atlas.dispose();
|
||||
super.dispose();
|
||||
for (final image in atlasPages) {
|
||||
image.dispose();
|
||||
}
|
||||
@ -114,6 +114,64 @@ class AtlasFlutter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Flutter wrapper for SkeletonData that provides convenient loading methods
|
||||
class SkeletonDataFlutter extends SkeletonData {
|
||||
SkeletonDataFlutter._(super.ptr) : super.fromPointer();
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the file [skeletonFile] in the root bundle or the optionally provided [bundle].
|
||||
/// Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromAsset(AtlasFlutter atlas, String skeletonFile, {AssetBundle? bundle}) async {
|
||||
bundle ??= rootBundle;
|
||||
if (skeletonFile.endsWith(".json")) {
|
||||
final jsonData = await bundle.loadString(skeletonFile);
|
||||
final skeletonData = loadSkeletonDataJson(atlas, jsonData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final binaryData = (await bundle.load(skeletonFile)).buffer.asUint8List();
|
||||
final skeletonData = loadSkeletonDataBinary(atlas, binaryData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the file [skeletonFile]. Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromFile(AtlasFlutter atlasFlutter, String skeletonFile) async {
|
||||
if (skeletonFile.endsWith(".json")) {
|
||||
final jsonData = await File(skeletonFile).readAsString();
|
||||
final skeletonData = loadSkeletonDataJson(atlasFlutter, jsonData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final binaryData = await File(skeletonFile).readAsBytes();
|
||||
final skeletonData = loadSkeletonDataBinary(atlasFlutter, binaryData, path: skeletonFile);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a [SkeletonDataFlutter] from the URL [skeletonURL]. Uses the provided [atlasFlutter] to resolve attachment images.
|
||||
///
|
||||
/// Throws an [Exception] in case the skeleton data could not be loaded.
|
||||
static Future<SkeletonDataFlutter> fromHttp(AtlasFlutter atlasFlutter, String skeletonURL) async {
|
||||
if (skeletonURL.endsWith(".json")) {
|
||||
final response = await http.get(Uri.parse(skeletonURL));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonURL: ${response.statusCode}');
|
||||
}
|
||||
final skeletonData = loadSkeletonDataJson(atlasFlutter, response.body, path: skeletonURL);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
} else {
|
||||
final response = await http.get(Uri.parse(skeletonURL));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonURL: ${response.statusCode}');
|
||||
}
|
||||
final skeletonData = loadSkeletonDataBinary(atlasFlutter, response.bodyBytes, path: skeletonURL);
|
||||
return SkeletonDataFlutter._(skeletonData.nativePtr.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension to convert Spine BlendMode to Flutter BlendMode
|
||||
extension BlendModeExtensions on BlendMode {
|
||||
rendering.BlendMode toFlutterBlendMode() {
|
||||
@ -184,7 +242,6 @@ class RenderCommandFlutter {
|
||||
// See https://github.com/flutter/flutter/issues/127486
|
||||
//
|
||||
// We thus batch all meshes not only by atlas page and blend mode, but also vertex color.
|
||||
// See spine_flutter.cpp, batch_commands().
|
||||
//
|
||||
// If the vertex color equals (1, 1, 1, 1), we do not store
|
||||
// colors, which will trigger the fast path in Impeller. Otherwise we have to go the slow path, which
|
||||
@ -218,13 +275,13 @@ class RenderCommandFlutter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A SkeletonDrawable bundles loading, updating, and rendering an [Atlas], [Skeleton], and [AnimationState]
|
||||
/// A SkeletonDrawable bundles loading, updating, and rendering an [AtlasFlutter], [Skeleton], and [AnimationState]
|
||||
/// into a single easy to use class.
|
||||
///
|
||||
/// Use the [fromAsset], [fromFile], or [fromHttp] methods to construct a SkeletonDrawable. To have
|
||||
/// multiple skeleton drawable instances share the same [Atlas] and [SkeletonData], use the constructor.
|
||||
/// multiple skeleton drawable instances share the same [AtlasFlutter] and [SkeletonDataFlutter], use the constructor.
|
||||
///
|
||||
/// You can then directly access the [atlas], [skeletonData], [skeleton], [animationStateData], and [animationState]
|
||||
/// You can then directly access the [atlasFlutter], [skeletonDataFlutter], [skeleton], [animationStateData], and [animationState]
|
||||
/// to query and animate the skeleton. Use the [AnimationState] to queue animations on one or more tracks
|
||||
/// via [AnimationState.setAnimation] or [AnimationState.addAnimation].
|
||||
///
|
||||
@ -235,27 +292,18 @@ class RenderCommandFlutter {
|
||||
/// [renderToPng], or [renderToRawImageData], depending on your needs.
|
||||
///
|
||||
/// When the skeleton drawable is no longer needed, call the [dispose] method to release its resources. If
|
||||
/// the skeleton drawable was constructed from a shared [Atlas] and [SkeletonData], make sure to dispose the
|
||||
/// the skeleton drawable was constructed from a shared [AtlasFlutter] and [SkeletonDataFlutter], make sure to dispose the
|
||||
/// atlas and skeleton data as well, if no skeleton drawable references them anymore.
|
||||
class SkeletonDrawableFlutter {
|
||||
class SkeletonDrawableFlutter extends SkeletonDrawable {
|
||||
final AtlasFlutter atlasFlutter;
|
||||
final SkeletonData skeletonData;
|
||||
late final SkeletonDrawable _drawable;
|
||||
late final Skeleton skeleton;
|
||||
late final AnimationStateData animationStateData;
|
||||
late final AnimationState animationState;
|
||||
final bool _ownsAtlasAndSkeletonData;
|
||||
bool _disposed = false;
|
||||
|
||||
/// Constructs a new skeleton drawable from the given (possibly shared) [Atlas] and [SkeletonData]. If
|
||||
/// Constructs a new skeleton drawable from the given (possibly shared) [AtlasFlutter] and [SkeletonDataFlutter]. If
|
||||
/// the atlas and skeleton data are not shared, the drawable can take ownership by passing true for [_ownsAtlasAndSkeletonData].
|
||||
/// In that case a call to [dispose] will also dispose the atlas and skeleton data.
|
||||
SkeletonDrawableFlutter(this.atlasFlutter, this.skeletonData, this._ownsAtlasAndSkeletonData) {
|
||||
_drawable = SkeletonDrawable(skeletonData);
|
||||
skeleton = _drawable.skeleton;
|
||||
animationStateData = _drawable.animationStateData;
|
||||
animationState = _drawable.animationState;
|
||||
}
|
||||
SkeletonDrawableFlutter(this.atlasFlutter, this.skeletonData, this._ownsAtlasAndSkeletonData) : super(skeletonData);
|
||||
|
||||
/// Constructs a new skeleton drawable from the [atlasFile] and [skeletonFile] from the root asset bundle
|
||||
/// or the optionally provided [bundle].
|
||||
@ -264,13 +312,8 @@ class SkeletonDrawableFlutter {
|
||||
static Future<SkeletonDrawableFlutter> fromAsset(String atlasFile, String skeletonFile, {AssetBundle? bundle}) async {
|
||||
bundle ??= rootBundle;
|
||||
final atlasFlutter = await AtlasFlutter.fromAsset(atlasFile, bundle: bundle);
|
||||
|
||||
final skeletonData = await bundle.loadString(skeletonFile);
|
||||
final skeleton = skeletonFile.endsWith('.json')
|
||||
? loadSkeletonDataJson(atlasFlutter.atlas, skeletonData)
|
||||
: throw Exception('Binary skeleton data loading from assets not yet implemented');
|
||||
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeleton, true);
|
||||
final skeletonDataFlutter = await SkeletonDataFlutter.fromAsset(atlasFlutter, skeletonFile, bundle: bundle);
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeletonDataFlutter, true);
|
||||
}
|
||||
|
||||
/// Constructs a new skeleton drawable from the [atlasFile] and [skeletonFile].
|
||||
@ -278,17 +321,8 @@ class SkeletonDrawableFlutter {
|
||||
/// Throws an exception in case the data could not be loaded.
|
||||
static Future<SkeletonDrawableFlutter> fromFile(String atlasFile, String skeletonFile) async {
|
||||
final atlasFlutter = await AtlasFlutter.fromFile(atlasFile);
|
||||
|
||||
final SkeletonData skeleton;
|
||||
if (skeletonFile.endsWith('.json')) {
|
||||
final skeletonData = await File(skeletonFile).readAsString();
|
||||
skeleton = loadSkeletonDataJson(atlasFlutter.atlas, skeletonData);
|
||||
} else {
|
||||
final skeletonData = await File(skeletonFile).readAsBytes();
|
||||
skeleton = loadSkeletonDataBinary(atlasFlutter.atlas, skeletonData);
|
||||
}
|
||||
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeleton, true);
|
||||
final skeletonDataFlutter = await SkeletonDataFlutter.fromFile(atlasFlutter, skeletonFile);
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeletonDataFlutter, true);
|
||||
}
|
||||
|
||||
/// Constructs a new skeleton drawable from the [atlasUrl] and [skeletonUrl].
|
||||
@ -296,45 +330,22 @@ class SkeletonDrawableFlutter {
|
||||
/// Throws an exception in case the data could not be loaded.
|
||||
static Future<SkeletonDrawableFlutter> fromHttp(String atlasUrl, String skeletonUrl) async {
|
||||
final atlasFlutter = await AtlasFlutter.fromHttp(atlasUrl);
|
||||
|
||||
final SkeletonData skeleton;
|
||||
if (skeletonUrl.endsWith('.json')) {
|
||||
final skeletonResponse = await http.get(Uri.parse(skeletonUrl));
|
||||
if (skeletonResponse.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonUrl: ${skeletonResponse.statusCode}');
|
||||
}
|
||||
skeleton = loadSkeletonDataJson(atlasFlutter.atlas, skeletonResponse.body);
|
||||
} else {
|
||||
final skeletonResponse = await http.get(Uri.parse(skeletonUrl));
|
||||
if (skeletonResponse.statusCode != 200) {
|
||||
throw Exception('Failed to load skeleton from $skeletonUrl: ${skeletonResponse.statusCode}');
|
||||
}
|
||||
skeleton = loadSkeletonDataBinary(atlasFlutter.atlas, skeletonResponse.bodyBytes);
|
||||
}
|
||||
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeleton, true);
|
||||
}
|
||||
|
||||
/// Updates the [AnimationState] using the [delta] time given in seconds, applies the
|
||||
/// animation state to the [Skeleton] and updates the world transforms of the skeleton
|
||||
/// to calculate its current pose.
|
||||
void update(double delta) {
|
||||
if (_disposed) return;
|
||||
_drawable.update(delta);
|
||||
final skeletonDataFlutter = await SkeletonDataFlutter.fromHttp(atlasFlutter, skeletonUrl);
|
||||
return SkeletonDrawableFlutter(atlasFlutter, skeletonDataFlutter, true);
|
||||
}
|
||||
|
||||
/// Renders to current skeleton pose to a list of [RenderCommandFlutter] instances. The render commands
|
||||
/// can be rendered via [Canvas.drawVertices].
|
||||
List<RenderCommandFlutter> render() {
|
||||
List<RenderCommandFlutter> renderFlutter() {
|
||||
if (_disposed) return [];
|
||||
|
||||
var commands = <RenderCommandFlutter>[];
|
||||
var nativeCmd = _drawable.render();
|
||||
var nativeCmd = render();
|
||||
|
||||
while (nativeCmd != null) {
|
||||
// Get page dimensions from atlas
|
||||
final pageIndex = nativeCmd.texture?.address ?? 0;
|
||||
final pages = atlasFlutter.atlas.pages;
|
||||
final pages = atlasFlutter.pages;
|
||||
final page = pages[pageIndex];
|
||||
if (page != null) {
|
||||
commands.add(RenderCommandFlutter._(nativeCmd, page.width.toDouble(), page.height.toDouble()));
|
||||
@ -350,7 +361,7 @@ class SkeletonDrawableFlutter {
|
||||
/// Renders the skeleton drawable's current pose to the given [canvas]. Does not perform any
|
||||
/// scaling or fitting.
|
||||
List<RenderCommandFlutter> renderToCanvas(Canvas canvas) {
|
||||
var commands = render();
|
||||
var commands = renderFlutter();
|
||||
|
||||
for (final cmd in commands) {
|
||||
// Get the paint for this atlas page and blend mode
|
||||
@ -425,10 +436,11 @@ class SkeletonDrawableFlutter {
|
||||
/// Disposes the skeleton drawable's resources. If the skeleton drawable owns the atlas
|
||||
/// and skeleton data, they are disposed as well. Must be called when the skeleton drawable
|
||||
/// is no longer in use.
|
||||
@override
|
||||
void dispose() {
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_drawable.dispose();
|
||||
super.dispose();
|
||||
|
||||
if (_ownsAtlasAndSkeletonData) {
|
||||
atlasFlutter.dispose();
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
/// THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
///
|
||||
library;
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
@ -34,7 +35,7 @@ import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'spine_dart.dart';
|
||||
import 'spine_flutter.dart';
|
||||
|
||||
/// Controls how the skeleton of a [SpineWidget] is animated and rendered.
|
||||
///
|
||||
@ -54,7 +55,7 @@ import 'spine_dart.dart';
|
||||
/// [SpineWidget] then renderes the skeleton's current pose, and finally calls the optional [onAfterPaint], which
|
||||
/// can render additional objects on top of the skeleton.
|
||||
///
|
||||
/// The underlying [Atlas], [SkeletonData], [Skeleton], [AnimationStateData], [AnimationState], and [SkeletonDrawable]
|
||||
/// The underlying [AtlasFlutter], [SkeletonData], [Skeleton], [AnimationStateData], [AnimationState], and [SkeletonDrawableFlutter]
|
||||
/// can be accessed through their respective getters to inspect and/or modify the skeleton and its associated data. Accessing
|
||||
/// this data is only allowed if the [SpineWidget] and its data have been initialized and have not been disposed yet.
|
||||
///
|
||||
@ -62,7 +63,7 @@ import 'spine_dart.dart';
|
||||
/// and rendering the skeleton. The [resume] method resumes updating and rendering the skeleton. The [isPlaying] getter
|
||||
/// reports the current state.
|
||||
class SpineWidgetController {
|
||||
SkeletonDrawable? _drawable;
|
||||
SkeletonDrawableFlutter? _drawable;
|
||||
double _offsetX = 0, _offsetY = 0, _scaleX = 1, _scaleY = 1;
|
||||
bool _isPlaying = true;
|
||||
_SpineRenderObject? _renderObject;
|
||||
@ -70,7 +71,8 @@ class SpineWidgetController {
|
||||
final void Function(SpineWidgetController controller)? onBeforeUpdateWorldTransforms;
|
||||
final void Function(SpineWidgetController controller)? onAfterUpdateWorldTransforms;
|
||||
final void Function(SpineWidgetController controller, Canvas canvas)? onBeforePaint;
|
||||
final void Function(SpineWidgetController controller, Canvas canvas, List<RenderCommand> commands)? onAfterPaint;
|
||||
final void Function(SpineWidgetController controller, Canvas canvas, List<RenderCommandFlutter> commands)?
|
||||
onAfterPaint;
|
||||
|
||||
/// Constructs a new [SpineWidget] controller. See the class documentation of [SpineWidgetController] for information on
|
||||
/// the optional arguments.
|
||||
@ -82,16 +84,16 @@ class SpineWidgetController {
|
||||
this.onAfterPaint,
|
||||
});
|
||||
|
||||
void _initialize(SkeletonDrawable drawable) {
|
||||
void _initialize(SkeletonDrawableFlutter drawable) {
|
||||
var wasInitialized = _drawable != null;
|
||||
_drawable = drawable;
|
||||
if (!wasInitialized) onInitialized?.call(this);
|
||||
}
|
||||
|
||||
/// The [Atlas] from which images to render the skeleton are sourced.
|
||||
Atlas get atlas {
|
||||
/// The [AtlasFlutter] from which images to render the skeleton are sourced.
|
||||
AtlasFlutter get atlasFlutter {
|
||||
if (_drawable == null) throw Exception("Controller is not initialized yet.");
|
||||
return _drawable!.atlas;
|
||||
return _drawable!.atlasFlutter;
|
||||
}
|
||||
|
||||
/// The setup-pose data used by the skeleton.
|
||||
@ -119,8 +121,8 @@ class SpineWidgetController {
|
||||
return _drawable!.skeleton;
|
||||
}
|
||||
|
||||
/// The [SkeletonDrawable]
|
||||
SkeletonDrawable get drawable {
|
||||
/// The [SkeletonDrawableFlutter]
|
||||
SkeletonDrawableFlutter get drawable {
|
||||
if (_drawable == null) throw Exception("Controller is not initialized yet.");
|
||||
return _drawable!;
|
||||
}
|
||||
@ -170,7 +172,7 @@ enum _AssetType { asset, file, http, drawable }
|
||||
abstract class BoundsProvider {
|
||||
const BoundsProvider();
|
||||
|
||||
Bounds computeBounds(SkeletonDrawable drawable);
|
||||
Bounds computeBounds(SkeletonDrawableFlutter drawable);
|
||||
}
|
||||
|
||||
/// A [BoundsProvider] that calculates the bounding box of the skeleton based on the visible
|
||||
@ -179,8 +181,10 @@ class SetupPoseBounds extends BoundsProvider {
|
||||
const SetupPoseBounds();
|
||||
|
||||
@override
|
||||
Bounds computeBounds(SkeletonDrawable drawable) {
|
||||
return drawable.skeleton.getBounds();
|
||||
Bounds computeBounds(SkeletonDrawableFlutter drawable) {
|
||||
drawable.skeleton.setupPose();
|
||||
drawable.skeleton.updateWorldTransform(Physics.none);
|
||||
return drawable.skeleton.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +195,8 @@ class RawBounds extends BoundsProvider {
|
||||
RawBounds(this.x, this.y, this.width, this.height);
|
||||
|
||||
@override
|
||||
Bounds computeBounds(SkeletonDrawable drawable) {
|
||||
return Bounds(x, y, width, height);
|
||||
Bounds computeBounds(SkeletonDrawableFlutter drawable) {
|
||||
return Bounds(x: x, y: y, width: width, height: height);
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,17 +215,17 @@ class SkinAndAnimationBounds extends BoundsProvider {
|
||||
: skins = skins == null || skins.isEmpty ? ["default"] : skins;
|
||||
|
||||
@override
|
||||
Bounds computeBounds(SkeletonDrawable drawable) {
|
||||
Bounds computeBounds(SkeletonDrawableFlutter drawable) {
|
||||
final data = drawable.skeletonData;
|
||||
final oldSkin = drawable.skeleton.getSkin();
|
||||
final oldSkin = drawable.skeleton.skin;
|
||||
final customSkin = Skin("custom-skin");
|
||||
for (final skinName in skins) {
|
||||
final skin = data.findSkin(skinName);
|
||||
if (skin == null) continue;
|
||||
customSkin.addSkin(skin);
|
||||
}
|
||||
drawable.skeleton.setSkin(customSkin);
|
||||
drawable.skeleton.setToSetupPose();
|
||||
drawable.skeleton.setSkin2(customSkin);
|
||||
drawable.skeleton.setupPose();
|
||||
|
||||
final animation = this.animation != null ? data.findAnimation(this.animation!) : null;
|
||||
double minX = double.infinity;
|
||||
@ -229,35 +233,37 @@ class SkinAndAnimationBounds extends BoundsProvider {
|
||||
double maxX = double.negativeInfinity;
|
||||
double maxY = double.negativeInfinity;
|
||||
if (animation == null) {
|
||||
final bounds = drawable.skeleton.getBounds();
|
||||
drawable.skeleton.updateWorldTransform(Physics.none);
|
||||
final bounds = drawable.skeleton.bounds;
|
||||
minX = bounds.x;
|
||||
minY = bounds.y;
|
||||
maxX = minX + bounds.width;
|
||||
maxY = minY + bounds.height;
|
||||
} else {
|
||||
drawable.animationState.setAnimation(0, animation, false);
|
||||
final steps = max(animation.getDuration() / stepTime, 1.0).toInt();
|
||||
drawable.animationState.setAnimation(0, animation.name, false);
|
||||
final steps = max(animation.duration / stepTime, 1.0).toInt();
|
||||
for (int i = 0; i < steps; i++) {
|
||||
drawable.update(i > 0 ? stepTime : 0);
|
||||
final bounds = drawable.skeleton.getBounds();
|
||||
drawable.skeleton.updateWorldTransform(Physics.none);
|
||||
final bounds = drawable.skeleton.bounds;
|
||||
minX = min(minX, bounds.x);
|
||||
minY = min(minY, bounds.y);
|
||||
maxX = max(maxX, minX + bounds.width);
|
||||
maxY = max(maxY, minY + bounds.height);
|
||||
}
|
||||
}
|
||||
drawable.skeleton.setSkinByName("default");
|
||||
drawable.skeleton.setSkin("default");
|
||||
drawable.animationState.clearTracks();
|
||||
if (oldSkin != null) drawable.skeleton.setSkin(oldSkin);
|
||||
drawable.skeleton.setToSetupPose();
|
||||
if (oldSkin != null) drawable.skeleton.setSkin2(oldSkin);
|
||||
drawable.skeleton.setupPose();
|
||||
drawable.update(0);
|
||||
customSkin.dispose();
|
||||
return Bounds(minX, minY, maxX - minX, maxY - minY);
|
||||
return Bounds(x: minX, y: minY, width: maxX - minX, height: maxY - minY);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [StatefulWidget] to display a Spine skeleton. The skeleton can be loaded from an asset bundle ([SpineWidget.fromAsset],
|
||||
/// local files [SpineWidget.fromFile], URLs [SpineWidget.fromHttp], or a pre-loaded [SkeletonDrawable] ([SpineWidget.fromDrawable]).
|
||||
/// local files [SpineWidget.fromFile], URLs [SpineWidget.fromHttp], or a pre-loaded [SkeletonDrawableFlutter] ([SpineWidget.fromDrawable]).
|
||||
///
|
||||
/// The skeleton displayed by a `SpineWidget` can be controlled via a [SpineWidgetController].
|
||||
///
|
||||
@ -268,7 +274,7 @@ class SpineWidget extends StatefulWidget {
|
||||
final AssetBundle? _bundle;
|
||||
final String? _skeletonFile;
|
||||
final String? _atlasFile;
|
||||
final SkeletonDrawable? _drawable;
|
||||
final SkeletonDrawableFlutter? _drawable;
|
||||
final SpineWidgetController _controller;
|
||||
final BoxFit _fit;
|
||||
final Alignment _alignment;
|
||||
@ -361,7 +367,7 @@ class SpineWidget extends StatefulWidget {
|
||||
_sizedByBounds = sizedByBounds ?? false,
|
||||
_drawable = null;
|
||||
|
||||
/// Constructs a new [SpineWidget] from a [SkeletonDrawable].
|
||||
/// Constructs a new [SpineWidget] from a [SkeletonDrawableFlutter].
|
||||
///
|
||||
/// After initialization is complete, the provided [_controller] is invoked as per the [SpineWidgetController] semantics, to allow
|
||||
/// modifying how the skeleton inside the widget is animated and rendered.
|
||||
@ -394,7 +400,7 @@ class SpineWidget extends StatefulWidget {
|
||||
|
||||
class _SpineWidgetState extends State<SpineWidget> {
|
||||
late Bounds _computedBounds;
|
||||
SkeletonDrawable? _drawable;
|
||||
SkeletonDrawableFlutter? _drawable;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -436,7 +442,7 @@ class _SpineWidgetState extends State<SpineWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
void loadDrawable(SkeletonDrawable drawable) {
|
||||
void loadDrawable(SkeletonDrawableFlutter drawable) {
|
||||
_drawable = drawable;
|
||||
_computedBounds = widget._boundsProvider.computeBounds(drawable);
|
||||
widget._controller._initialize(drawable);
|
||||
@ -446,13 +452,13 @@ class _SpineWidgetState extends State<SpineWidget> {
|
||||
void loadFromAsset(AssetBundle? bundle, String atlasFile, String skeletonFile, _AssetType assetType) async {
|
||||
switch (assetType) {
|
||||
case _AssetType.asset:
|
||||
loadDrawable(await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle));
|
||||
loadDrawable(await SkeletonDrawableFlutter.fromAsset(atlasFile, skeletonFile, bundle: bundle));
|
||||
break;
|
||||
case _AssetType.file:
|
||||
loadDrawable(await SkeletonDrawable.fromFile(atlasFile, skeletonFile));
|
||||
loadDrawable(await SkeletonDrawableFlutter.fromFile(atlasFile, skeletonFile));
|
||||
break;
|
||||
case _AssetType.http:
|
||||
loadDrawable(await SkeletonDrawable.fromHttp(atlasFile, skeletonFile));
|
||||
loadDrawable(await SkeletonDrawableFlutter.fromHttp(atlasFile, skeletonFile));
|
||||
break;
|
||||
case _AssetType.drawable:
|
||||
throw Exception("Drawable can not be loaded via loadFromAsset().");
|
||||
@ -483,7 +489,7 @@ class _SpineWidgetState extends State<SpineWidget> {
|
||||
}
|
||||
|
||||
class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final SkeletonDrawable _skeletonDrawable;
|
||||
final SkeletonDrawableFlutter _skeletonDrawable;
|
||||
final SpineWidgetController _controller;
|
||||
final BoxFit _fit;
|
||||
final Alignment _alignment;
|
||||
@ -515,7 +521,7 @@ class _SpineRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
}
|
||||
|
||||
class _SpineRenderObject extends RenderBox {
|
||||
SkeletonDrawable _skeletonDrawable;
|
||||
SkeletonDrawableFlutter _skeletonDrawable;
|
||||
final SpineWidgetController _controller;
|
||||
double _deltaTime = 0;
|
||||
final Stopwatch _stopwatch = Stopwatch();
|
||||
@ -535,7 +541,7 @@ class _SpineRenderObject extends RenderBox {
|
||||
this._sizedByBounds,
|
||||
);
|
||||
|
||||
set skeletonDrawable(SkeletonDrawable skeletonDrawable) {
|
||||
set skeletonDrawable(SkeletonDrawableFlutter skeletonDrawable) {
|
||||
if (_skeletonDrawable == skeletonDrawable) return;
|
||||
|
||||
_skeletonDrawable = skeletonDrawable;
|
||||
@ -721,7 +727,11 @@ class _SpineRenderObject extends RenderBox {
|
||||
|
||||
if (_firstUpdated) {
|
||||
_controller.onBeforePaint?.call(_controller, canvas);
|
||||
final commands = _skeletonDrawable.renderToCanvas(canvas);
|
||||
final commands = _skeletonDrawable.renderFlutter();
|
||||
for (final cmd in commands) {
|
||||
final paint = _skeletonDrawable.atlasFlutter.atlasPagePaints[cmd.atlasPageIndex][cmd.blendMode]!;
|
||||
canvas.drawVertices(cmd.vertices, rendering.BlendMode.modulate, paint);
|
||||
}
|
||||
_controller.onAfterPaint?.call(_controller, canvas, commands);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
#include "../../src/spine-cpp-lite/spine-cpp-lite.cpp"
|
||||
@ -12,14 +12,9 @@ A new Flutter FFI plugin project.
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
# This will ensure the source files in Classes/ are included in the native
|
||||
# builds of apps using this FFI plugin. Podspec does not support relative
|
||||
# paths, so Classes contains a forwarder C file that relatively imports
|
||||
# `../src/*` so that the C sources can be shared among all target platforms.
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*.{cpp}'
|
||||
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/Classes/spine-cpp/include"' }
|
||||
s.source_files = 'Classes/spine-c/src/**/*.{cpp,h}', 'Classes/spine-cpp/src/**/*.{cpp,h}'
|
||||
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/Classes/spine-cpp/include" "$(PODS_TARGET_SRCROOT)/Classes/spine-c/include"' }
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
|
||||
@ -44,5 +44,3 @@ flutter:
|
||||
assets:
|
||||
- lib/assets/libspine_flutter.js
|
||||
- lib/assets/libspine_flutter.wasm
|
||||
- src/spine-cpp-lite/spine-cpp-lite.cpp
|
||||
- src/spine-cpp-lite/spine-cpp-lite.h
|
||||
|
||||
@ -16,12 +16,12 @@ void main() async {
|
||||
// Create skeleton drawable
|
||||
final drawable = SkeletonDrawable(skeletonData);
|
||||
print('SkeletonDrawable created successfully');
|
||||
|
||||
|
||||
// Test skeleton bounds
|
||||
print('\nTesting skeleton bounds:');
|
||||
final bounds = drawable.skeleton.bounds;
|
||||
print(' Initial bounds: $bounds');
|
||||
|
||||
|
||||
// Set skeleton to pose and update bounds
|
||||
drawable.skeleton.setupPose();
|
||||
drawable.skeleton.updateWorldTransform(Physics.none);
|
||||
@ -76,13 +76,13 @@ void main() async {
|
||||
for (final event in events) {
|
||||
print(' $event');
|
||||
}
|
||||
|
||||
|
||||
// Test bounds after animation updates
|
||||
print('\nTesting bounds after animation:');
|
||||
drawable.skeleton.updateWorldTransform(Physics.none);
|
||||
final boundsAfterAnimation = drawable.skeleton.bounds;
|
||||
print(' Bounds after animation: $boundsAfterAnimation');
|
||||
|
||||
|
||||
// Test with different animations that might have different bounds
|
||||
print('\nTesting bounds with jump animation:');
|
||||
drawable.animationState.setAnimation(0, 'jump', false);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user