mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-19 08:16:41 +08:00
[flutter] handle implicit enum values in codegen, extension for animation state listeners + test
This commit is contained in:
parent
71eb86c15e
commit
0e5de94529
@ -212,9 +212,10 @@ export class DartWriter {
|
||||
private transformEnum (cEnum: CEnum): DartEnum {
|
||||
return {
|
||||
name: this.toDartTypeName(cEnum.name),
|
||||
values: cEnum.values.map((value) => ({
|
||||
values: cEnum.values.map((value, index) => ({
|
||||
name: this.toDartEnumValueName(value.name, cEnum.name),
|
||||
value: Number.parseInt(value.value ?? "0")
|
||||
// C enums without explicit values are implicitly numbered 0, 1, 2, etc.
|
||||
value: value.value !== undefined ? Number.parseInt(value.value) : index
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,12 +32,12 @@
|
||||
/// AttachmentType enum
|
||||
enum AttachmentType {
|
||||
region(0),
|
||||
boundingbox(0),
|
||||
mesh(0),
|
||||
linkedmesh(0),
|
||||
path(0),
|
||||
point(0),
|
||||
clipping(0);
|
||||
boundingbox(1),
|
||||
mesh(2),
|
||||
linkedmesh(3),
|
||||
path(4),
|
||||
point(5),
|
||||
clipping(6);
|
||||
|
||||
const AttachmentType(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,9 +32,9 @@
|
||||
/// BlendMode enum
|
||||
enum BlendMode {
|
||||
normal(0),
|
||||
additive(0),
|
||||
multiply(0),
|
||||
screen(0);
|
||||
additive(1),
|
||||
multiply(2),
|
||||
screen(3);
|
||||
|
||||
const BlendMode(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,11 +32,11 @@
|
||||
/// EventType enum
|
||||
enum EventType {
|
||||
start(0),
|
||||
interrupt(0),
|
||||
end(0),
|
||||
dispose(0),
|
||||
complete(0),
|
||||
event(0);
|
||||
interrupt(1),
|
||||
end(2),
|
||||
dispose(3),
|
||||
complete(4),
|
||||
event(5);
|
||||
|
||||
const EventType(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,12 +32,12 @@
|
||||
/// Format enum
|
||||
enum Format {
|
||||
alpha(0),
|
||||
intensity(0),
|
||||
luminanceAlpha(0),
|
||||
rgb565(0),
|
||||
rgba4444(0),
|
||||
rgb888(0),
|
||||
rgba8888(0);
|
||||
intensity(1),
|
||||
luminanceAlpha(2),
|
||||
rgb565(3),
|
||||
rgba4444(4),
|
||||
rgb888(5),
|
||||
rgba8888(6);
|
||||
|
||||
const Format(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,10 +32,10 @@
|
||||
/// Inherit enum
|
||||
enum Inherit {
|
||||
normal(0),
|
||||
onlyTranslation(0),
|
||||
noRotationOrReflection(0),
|
||||
noScale(0),
|
||||
noScaleOrReflection(0);
|
||||
onlyTranslation(1),
|
||||
noRotationOrReflection(2),
|
||||
noScale(3),
|
||||
noScaleOrReflection(4);
|
||||
|
||||
const Inherit(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,9 +32,9 @@
|
||||
/// MixBlend enum
|
||||
enum MixBlend {
|
||||
setup(0),
|
||||
first(0),
|
||||
replace(0),
|
||||
add(0);
|
||||
first(1),
|
||||
replace(2),
|
||||
add(3);
|
||||
|
||||
const MixBlend(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
/// MixDirection enum
|
||||
enum MixDirection {
|
||||
directionIn(0),
|
||||
directionOut(0);
|
||||
directionOut(1);
|
||||
|
||||
const MixDirection(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,9 +32,9 @@
|
||||
/// Physics enum
|
||||
enum Physics {
|
||||
none(0),
|
||||
reset(0),
|
||||
update(0),
|
||||
pose(0);
|
||||
reset(1),
|
||||
update(2),
|
||||
pose(3);
|
||||
|
||||
const Physics(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
/// PositionMode enum
|
||||
enum PositionMode {
|
||||
fixed(0),
|
||||
percent(0);
|
||||
percent(1);
|
||||
|
||||
const PositionMode(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
/// RotateMode enum
|
||||
enum RotateMode {
|
||||
tangent(0),
|
||||
chain(0),
|
||||
chainScale(0);
|
||||
chain(1),
|
||||
chainScale(2);
|
||||
|
||||
const RotateMode(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,9 +32,9 @@
|
||||
/// SpacingMode enum
|
||||
enum SpacingMode {
|
||||
length(0),
|
||||
fixed(0),
|
||||
percent(0),
|
||||
proportional(0);
|
||||
fixed(1),
|
||||
percent(2),
|
||||
proportional(3);
|
||||
|
||||
const SpacingMode(this.value);
|
||||
final int value;
|
||||
|
||||
@ -6749,6 +6749,83 @@ class SpineDartBindings {
|
||||
_spine_skeleton_drawable_get_animation_state_eventsPtr
|
||||
.asFunction<spine_animation_state_events Function(spine_skeleton_drawable)>();
|
||||
|
||||
/// Animation state events functions
|
||||
int spine_animation_state_events_get_num_events(
|
||||
spine_animation_state_events events,
|
||||
) {
|
||||
return _spine_animation_state_events_get_num_events(
|
||||
events,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_animation_state_events_get_num_eventsPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Int32 Function(spine_animation_state_events)>>(
|
||||
'spine_animation_state_events_get_num_events');
|
||||
late final _spine_animation_state_events_get_num_events =
|
||||
_spine_animation_state_events_get_num_eventsPtr.asFunction<int Function(spine_animation_state_events)>();
|
||||
|
||||
int spine_animation_state_events_get_event_type(
|
||||
spine_animation_state_events events,
|
||||
int index,
|
||||
) {
|
||||
return _spine_animation_state_events_get_event_type(
|
||||
events,
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_animation_state_events_get_event_typePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Int32 Function(spine_animation_state_events, ffi.Int32)>>(
|
||||
'spine_animation_state_events_get_event_type');
|
||||
late final _spine_animation_state_events_get_event_type =
|
||||
_spine_animation_state_events_get_event_typePtr.asFunction<int Function(spine_animation_state_events, int)>();
|
||||
|
||||
spine_track_entry spine_animation_state_events_get_track_entry(
|
||||
spine_animation_state_events events,
|
||||
int index,
|
||||
) {
|
||||
return _spine_animation_state_events_get_track_entry(
|
||||
events,
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_animation_state_events_get_track_entryPtr =
|
||||
_lookup<ffi.NativeFunction<spine_track_entry Function(spine_animation_state_events, ffi.Int32)>>(
|
||||
'spine_animation_state_events_get_track_entry');
|
||||
late final _spine_animation_state_events_get_track_entry = _spine_animation_state_events_get_track_entryPtr
|
||||
.asFunction<spine_track_entry Function(spine_animation_state_events, int)>();
|
||||
|
||||
spine_event spine_animation_state_events_get_event(
|
||||
spine_animation_state_events events,
|
||||
int index,
|
||||
) {
|
||||
return _spine_animation_state_events_get_event(
|
||||
events,
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_animation_state_events_get_eventPtr =
|
||||
_lookup<ffi.NativeFunction<spine_event Function(spine_animation_state_events, ffi.Int32)>>(
|
||||
'spine_animation_state_events_get_event');
|
||||
late final _spine_animation_state_events_get_event =
|
||||
_spine_animation_state_events_get_eventPtr.asFunction<spine_event Function(spine_animation_state_events, int)>();
|
||||
|
||||
void spine_animation_state_events_reset(
|
||||
spine_animation_state_events events,
|
||||
) {
|
||||
return _spine_animation_state_events_reset(
|
||||
events,
|
||||
);
|
||||
}
|
||||
|
||||
late final _spine_animation_state_events_resetPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(spine_animation_state_events)>>(
|
||||
'spine_animation_state_events_reset');
|
||||
late final _spine_animation_state_events_reset =
|
||||
_spine_animation_state_events_resetPtr.asFunction<void Function(spine_animation_state_events)>();
|
||||
|
||||
/// Skin functions
|
||||
spine_skin_entries spine_skin_get_entries(
|
||||
spine_skin skin,
|
||||
|
||||
@ -32,13 +32,13 @@
|
||||
/// TextureFilter enum
|
||||
enum TextureFilter {
|
||||
unknown(0),
|
||||
nearest(0),
|
||||
linear(0),
|
||||
mipMap(0),
|
||||
mipMapNearestNearest(0),
|
||||
mipMapLinearNearest(0),
|
||||
mipMapNearestLinear(0),
|
||||
mipMapLinearLinear(0);
|
||||
nearest(1),
|
||||
linear(2),
|
||||
mipMap(3),
|
||||
mipMapNearestNearest(4),
|
||||
mipMapLinearNearest(5),
|
||||
mipMapNearestLinear(6),
|
||||
mipMapLinearLinear(7);
|
||||
|
||||
const TextureFilter(this.value);
|
||||
final int value;
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
/// TextureWrap enum
|
||||
enum TextureWrap {
|
||||
mirroredRepeat(0),
|
||||
clampToEdge(0),
|
||||
repeat(0);
|
||||
clampToEdge(1),
|
||||
repeat(2);
|
||||
|
||||
const TextureWrap(this.value);
|
||||
final int value;
|
||||
|
||||
@ -43,6 +43,12 @@ import 'generated/clipping_attachment.dart';
|
||||
import 'generated/path_attachment.dart';
|
||||
import 'generated/point_attachment.dart';
|
||||
import 'generated/rtti.dart';
|
||||
import 'generated/skeleton.dart';
|
||||
import 'generated/animation_state.dart';
|
||||
import 'generated/animation_state_data.dart';
|
||||
import 'generated/track_entry.dart';
|
||||
import 'generated/event.dart';
|
||||
import 'generated/event_type.dart';
|
||||
|
||||
// Export generated classes
|
||||
export 'generated/api.dart';
|
||||
@ -166,14 +172,14 @@ extension SkinExtensions on Skin {
|
||||
final slotIndex = SpineBindings.bindings.spine_skin_entry_get_slot_index(entryPtr.cast());
|
||||
final namePtr = SpineBindings.bindings.spine_skin_entry_get_name(entryPtr.cast());
|
||||
final name = namePtr.cast<Utf8>().toDartString();
|
||||
|
||||
|
||||
final attachmentPtr = SpineBindings.bindings.spine_skin_entry_get_attachment(entryPtr.cast());
|
||||
Attachment? attachment;
|
||||
if (attachmentPtr.address != 0) {
|
||||
// Use RTTI to determine the concrete attachment type
|
||||
final rtti = SpineBindings.bindings.spine_attachment_get_rtti(attachmentPtr);
|
||||
final className = SpineBindings.bindings.spine_rtti_get_class_name(rtti).cast<Utf8>().toDartString();
|
||||
|
||||
|
||||
switch (className) {
|
||||
case 'spine_region_attachment':
|
||||
attachment = RegionAttachment.fromPointer(attachmentPtr.cast());
|
||||
@ -213,3 +219,108 @@ extension SkinExtensions on Skin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Event listener callback for animation state events
|
||||
typedef AnimationStateListener = void Function(EventType type, TrackEntry entry, Event? event);
|
||||
|
||||
/// Convenient drawable that combines skeleton, animation state, and rendering
|
||||
class SkeletonDrawable {
|
||||
final Pointer<spine_skeleton_drawable_wrapper> _drawable;
|
||||
final Map<TrackEntry, AnimationStateListener> _trackEntryListeners = {};
|
||||
AnimationStateListener? _stateListener;
|
||||
|
||||
late final Skeleton skeleton;
|
||||
late final AnimationState animationState;
|
||||
late final AnimationStateData animationStateData;
|
||||
|
||||
SkeletonDrawable(SkeletonData skeletonData)
|
||||
: _drawable = SpineBindings.bindings.spine_skeleton_drawable_create(skeletonData.nativePtr.cast()) {
|
||||
if (_drawable == nullptr) {
|
||||
throw Exception("Failed to create skeleton drawable");
|
||||
}
|
||||
|
||||
// Get references to the skeleton and animation state
|
||||
final skeletonPtr = SpineBindings.bindings.spine_skeleton_drawable_get_skeleton(_drawable.cast());
|
||||
skeleton = Skeleton.fromPointer(skeletonPtr);
|
||||
|
||||
final animationStatePtr = SpineBindings.bindings.spine_skeleton_drawable_get_animation_state(_drawable.cast());
|
||||
animationState = AnimationState.fromPointer(animationStatePtr);
|
||||
|
||||
final animationStateDataPtr =
|
||||
SpineBindings.bindings.spine_skeleton_drawable_get_animation_state_data(_drawable.cast());
|
||||
animationStateData = AnimationStateData.fromPointer(animationStateDataPtr);
|
||||
}
|
||||
|
||||
/// Update the animation state and process events
|
||||
void update(double delta) {
|
||||
// Update animation state
|
||||
animationState.update(delta);
|
||||
|
||||
// Process events
|
||||
final eventsPtr = SpineBindings.bindings.spine_skeleton_drawable_get_animation_state_events(_drawable.cast());
|
||||
if (eventsPtr != nullptr) {
|
||||
final numEvents = SpineBindings.bindings.spine_animation_state_events_get_num_events(eventsPtr.cast());
|
||||
|
||||
for (int i = 0; i < numEvents; i++) {
|
||||
// Get event type
|
||||
final eventTypeValue = SpineBindings.bindings.spine_animation_state_events_get_event_type(eventsPtr.cast(), i);
|
||||
final type = EventType.fromValue(eventTypeValue);
|
||||
|
||||
// Get track entry
|
||||
final trackEntryPtr = SpineBindings.bindings.spine_animation_state_events_get_track_entry(eventsPtr.cast(), i);
|
||||
final trackEntry = TrackEntry.fromPointer(trackEntryPtr);
|
||||
|
||||
// Get event (may be null)
|
||||
final eventPtr = SpineBindings.bindings.spine_animation_state_events_get_event(eventsPtr.cast(), i);
|
||||
final event = eventPtr.address == 0 ? null : Event.fromPointer(eventPtr);
|
||||
|
||||
// Call track entry listener if registered
|
||||
if (_trackEntryListeners.containsKey(trackEntry)) {
|
||||
_trackEntryListeners[trackEntry]?.call(type, trackEntry, event);
|
||||
}
|
||||
|
||||
// Call global state listener
|
||||
_stateListener?.call(type, trackEntry, event);
|
||||
|
||||
// Remove listener if track entry is being disposed
|
||||
if (type == EventType.dispose) {
|
||||
_trackEntryListeners.remove(trackEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset events for next frame
|
||||
SpineBindings.bindings.spine_animation_state_events_reset(eventsPtr.cast());
|
||||
}
|
||||
|
||||
// Apply animation state to skeleton
|
||||
animationState.apply(skeleton);
|
||||
}
|
||||
|
||||
/// Set a listener for all animation state events
|
||||
void setListener(AnimationStateListener? listener) {
|
||||
_stateListener = listener;
|
||||
}
|
||||
|
||||
/// Internal method to set a listener for a specific track entry
|
||||
void _setTrackEntryListener(TrackEntry entry, AnimationStateListener? listener) {
|
||||
if (listener == null) {
|
||||
_trackEntryListeners.remove(entry);
|
||||
} else {
|
||||
_trackEntryListeners[entry] = listener;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_trackEntryListeners.clear();
|
||||
_stateListener = null;
|
||||
SpineBindings.bindings.spine_skeleton_drawable_dispose(_drawable.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension to add setListener to TrackEntry
|
||||
extension TrackEntryExtensions on TrackEntry {
|
||||
/// Set a listener for events from this track entry
|
||||
void setListener(SkeletonDrawable drawable, AnimationStateListener? listener) {
|
||||
drawable._setTrackEntryListener(this, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ void main() async {
|
||||
if (defaultSkin != null) {
|
||||
final entries = defaultSkin.getEntries();
|
||||
print('Default skin has ${entries.length} entries');
|
||||
|
||||
|
||||
// Print first few entries
|
||||
for (int i = 0; i < 5 && i < entries.length; i++) {
|
||||
final entry = entries[i];
|
||||
|
||||
74
spine-flutter/test/skeleton_drawable_test.dart
Normal file
74
spine-flutter/test/skeleton_drawable_test.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'dart:io';
|
||||
import 'package:spine_flutter/spine_dart.dart';
|
||||
|
||||
void main() async {
|
||||
print('Testing SkeletonDrawable and event listeners...');
|
||||
|
||||
// Initialize with debug extension enabled
|
||||
await initSpineDart(enableMemoryDebugging: true);
|
||||
|
||||
// Load atlas and skeleton data
|
||||
final atlasData = File('../example/assets/spineboy.atlas').readAsStringSync();
|
||||
final atlas = loadAtlas(atlasData);
|
||||
final skeletonJson = File('../example/assets/spineboy-pro.json').readAsStringSync();
|
||||
final skeletonData = loadSkeletonDataJson(atlas, skeletonJson);
|
||||
|
||||
// Create skeleton drawable
|
||||
final drawable = SkeletonDrawable(skeletonData);
|
||||
print('SkeletonDrawable created successfully');
|
||||
|
||||
// Track events
|
||||
final events = <String>[];
|
||||
|
||||
// Set global animation state listener
|
||||
drawable.setListener((type, entry, event) {
|
||||
final eventName = event?.data.name ?? 'null';
|
||||
events.add('State: $type, event: $eventName');
|
||||
});
|
||||
|
||||
// Set an animation
|
||||
final trackEntry = drawable.animationState.setAnimation(0, 'walk', true);
|
||||
print('Set animation: walk');
|
||||
|
||||
// Set track entry specific listener
|
||||
trackEntry.setListener(drawable, (type, entry, event) {
|
||||
final eventName = event?.data.name ?? 'null';
|
||||
events.add('TrackEntry: $type, event: $eventName');
|
||||
});
|
||||
|
||||
// Update several times to trigger events
|
||||
print('\nUpdating animation state...');
|
||||
for (int i = 0; i < 5; i++) {
|
||||
drawable.update(0.016); // ~60fps
|
||||
}
|
||||
|
||||
// Print collected events
|
||||
print('\nCollected events:');
|
||||
for (final event in events) {
|
||||
print(' $event');
|
||||
}
|
||||
|
||||
// Test switching animations
|
||||
print('\nSwitching to run animation...');
|
||||
events.clear();
|
||||
drawable.animationState.setAnimation(0, 'run', true);
|
||||
|
||||
// Update a few more times
|
||||
for (int i = 0; i < 3; i++) {
|
||||
drawable.update(0.016);
|
||||
}
|
||||
|
||||
print('\nEvents after switching:');
|
||||
for (final event in events) {
|
||||
print(' $event');
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
drawable.dispose();
|
||||
skeletonData.dispose();
|
||||
atlas.dispose();
|
||||
|
||||
// Report memory leaks
|
||||
reportLeaks();
|
||||
print('\nTest complete');
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user