spine2d 0.4.0

Pure Rust runtime for Spine 4.3 (unofficial)
Documentation
use crate::{
    Animation, AttachmentTimeline, DrawOrderTimeline, Event, EventData, EventTimeline,
    SkeletonData, TimelineKind, TimelineRef,
};
use std::sync::Arc;

#[test]
fn json_animation_preserves_object_order_in_timeline_order() {
    let json = r#"
    {
      "bones": [{"name": "root"}],
      "slots": [{"name": "slot", "bone": "root"}],
      "animations": {
        "mix": {
          "slots": {
            "slot": {
              "attachment": [
                {"time": 0.0, "name": "a"}
              ],
              "rgba": [
                {"time": 0.0, "color": "FFFFFFFF"}
              ]
            }
          },
          "bones": {
            "root": {
              "translate": [
                {"time": 0.0, "x": 1.0, "y": 2.0}
              ],
              "rotate": [
                {"time": 0.0, "angle": 3.0}
              ]
            }
          },
          "drawOrder": [
            {"time": 0.0}
          ]
        }
      }
    }
    "#;

    let data = SkeletonData::from_json_str(json).expect("parse json");
    let animation = data.find_animation("mix").expect("animation exists");
    assert_eq!(
        animation.timeline_order(),
        vec![
            TimelineKind::SlotAttachment(0),
            TimelineKind::SlotColor(0),
            TimelineKind::Bone(1),
            TimelineKind::Bone(0),
            TimelineKind::DrawOrder,
        ]
    );
}

#[test]
fn finalize_animation_keeps_explicit_timeline_order() {
    let animation = Animation {
        name: "mix".to_string(),
        duration: 0.0,
        color: crate::Animation::DEFAULT_COLOR,
        event_timeline: None,
        bone_timelines: Vec::new(),
        deform_timelines: Vec::new(),
        sequence_timelines: Vec::new(),
        slot_attachment_timelines: Vec::new(),
        slot_color_timelines: Vec::new(),
        slot_rgb_timelines: Vec::new(),
        slot_alpha_timelines: Vec::new(),
        slot_rgba2_timelines: Vec::new(),
        slot_rgb2_timelines: Vec::new(),
        ik_constraint_timelines: Vec::new(),
        transform_constraint_timelines: Vec::new(),
        path_constraint_timelines: Vec::new(),
        physics_constraint_timelines: Vec::new(),
        physics_reset_timelines: Vec::new(),
        slider_time_timelines: Vec::new(),
        slider_mix_timelines: Vec::new(),
        draw_order_timeline: None,
        draw_order_folder_timelines: Vec::new(),
        timeline_order: vec![TimelineKind::DrawOrder],
    };

    let finalized = crate::runtime::finalize_animation(animation);
    assert_eq!(finalized.timeline_order(), vec![TimelineKind::DrawOrder]);
}

#[test]
fn animation_timelines_exposes_unified_cpp_order() {
    let animation = Animation {
        name: "mix".to_string(),
        duration: 0.0,
        color: crate::Animation::DEFAULT_COLOR,
        event_timeline: Some(EventTimeline::from_events(vec![Event::new(
            0.0,
            Arc::new(EventData::new("hit")),
        )])),
        bone_timelines: Vec::new(),
        deform_timelines: Vec::new(),
        sequence_timelines: Vec::new(),
        slot_attachment_timelines: vec![AttachmentTimeline {
            slot_index: 0,
            frames: Vec::new(),
        }],
        slot_color_timelines: Vec::new(),
        slot_rgb_timelines: Vec::new(),
        slot_alpha_timelines: Vec::new(),
        slot_rgba2_timelines: Vec::new(),
        slot_rgb2_timelines: Vec::new(),
        ik_constraint_timelines: Vec::new(),
        transform_constraint_timelines: Vec::new(),
        path_constraint_timelines: Vec::new(),
        physics_constraint_timelines: Vec::new(),
        physics_reset_timelines: Vec::new(),
        slider_time_timelines: Vec::new(),
        slider_mix_timelines: Vec::new(),
        draw_order_timeline: Some(DrawOrderTimeline { frames: Vec::new() }),
        draw_order_folder_timelines: Vec::new(),
        timeline_order: vec![TimelineKind::SlotAttachment(0), TimelineKind::DrawOrder],
    };

    let labels = animation
        .get_timelines()
        .map(|timeline| match timeline {
            TimelineRef::SlotAttachment { index, .. } => format!("SlotAttachment({index})"),
            TimelineRef::DrawOrder { .. } => "DrawOrder".to_string(),
            TimelineRef::Event { .. } => "Event".to_string(),
            other => format!("{other:?}"),
        })
        .collect::<Vec<_>>();

    assert_eq!(labels, vec!["SlotAttachment(0)", "DrawOrder", "Event"]);
}