mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
// examples/component-method-call.mms
//
// Demonstrates MMS method dispatch: anim.pause() and anim.play().
//
// A cube steps through 4 rotations driven by a looping Animation.
// • Click the BLUE cube  → anim.pause()
// • Click the GREEN cube → anim.play()
//
// The Animation component is bound to a local variable (let anim = A { ... }),
// and the signal handlers close over it, calling methods on the live handle.
//
// Run: cargo run --release --example component-method-call

RendererSettings {
    window_size(1280, 960)
}

BGC.rgba(0.10, 0.10, 0.14, 1.0)
AL.rgb(0.35, 0.35, 0.38)

T.position(3.0, 5.0, 4.0) {
    DL {}
}

// Camera + pointer
I.speed(3.0) {
    InputTransformMode.forward_z() {
        fps_rotation()
        roll_axis_y()
    }

    T.position(0.0, 0.5, 5.0) {
        C3D {
            Pointer {}
        }

        T.position(0.0, 1.6, -2.0).scale(0.055, 0.055, 1.0) {
            TXT {
                "click blue  → pause\nclick green → play\nwasd/rf/qe to move\nright-mouse drag to look"
                C.rgba(0.0, 0.0, 0.0, 1.0)
                TextureFiltering.linear()
            }
        }
    }
}

// The spinning cube handle is captured by the keyframe blocks and mutated live
// when each keyframe becomes due.
let cube_t = T.position(0.0, 0.0, 0.0).scale(0.5, 0.5, 0.5) {
    name = "cube"
    Transition {
        duration_beats(0.85)
        ease_in_out_sine()
        replace_same_target()
    }
    R.cube() {
        C.rgba(0.90, 0.75, 0.30, 1.0)
        Raycastable.enabled()
    }
}

cube_t

// Clock drives the beat (60 BPM → one beat per second).
Clock.bpm(60) {}

// Looping animation: four keyframes, 90° Y-rotation each beat.
// One full rotation every 4 beats = 4 seconds at 60 BPM.
let anim = A {
    Keyframe.at(0) {
        cube_t.update_transform([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.5, 0.5, 0.5])
    }
    Keyframe.at(1) {
        cube_t.update_transform([0.0, 0.0, 0.0], [0.0, 3.14159 / 2, 0.0], [0.5, 0.5, 0.5])
    }
    Keyframe.at(2) {
        cube_t.update_transform([0.0, 0.0, 0.0], [0.0, 3.14159, 0.0], [0.5, 0.5, 0.5])
    }
    Keyframe.at(3) {
        cube_t.update_transform([0.0, 0.0, 0.0], [0.0, 3.14159 * 1.5, 0.0], [0.5, 0.5, 0.5])
    }
}

anim

// BLUE cube — click to pause the animation.
let pause_btn = T.position(-1.2, -1.2, 0.0).scale(0.35, 0.35, 0.35) {
    R.cube() {
        C.rgba(0.25, 0.55, 1.0, 1.0)
        Raycastable.enabled()
    }
    T.position(0,0,0.6) {
        T.position(-0.25, 0, 0).scale(0.3, 0.8, 0.05) {
            R.cube() {
                C.rgba(1.0, 1.0, 1.0, 1.0)
            }
        }
        T.position( 0.25, 0, 0).scale(0.3, 0.8, 0.05) {
            R.cube() {
                C.rgba(1.0, 1.0, 1.0, 1.0)
            }
        }
    }
}

// GREEN cube — click to resume the animation.
let play_btn = T.position(1.2, -1.2, 0.0).scale(0.35, 0.35, 0.35) {
    R.cube() {
        C.rgba(0.30, 0.85, 0.45, 1.0)
        Raycastable.enabled()
    }
    T.position(0,0,0.6).rotation(0,0,-3.1415 / 2).scale(0.8, 0.8, 0.8) {
        R.triangle() {
            C.rgba(1.0, 1.0, 1.0, 1.0)
        }
    }
}

pause_btn
play_btn

// playback status
let playback_status = Text {
    name="playback_status"
    "Playing"
}

T.position(-0.6, -1.2, 0).scale(0.2, 0.2, 0.2) {
    playback_status
}

on(pause_btn, "Click", fn(event) {
    anim.pause()
    playback_status.set_text("Paused")
    print("anim.pause()")
})

on(play_btn, "Click", fn(event) {
    anim.play()
    playback_status.set_text("Playing")
    print("anim.play()")
})