1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use dioxus::prelude::*;

use crate::runtime::{SplineApplication, SplineEvent, SplineEventName};
use wasm_bindgen::{JsCast, JsValue};

#[derive(Props, PartialEq, Clone)]
pub struct SplineProps {
    #[props(into)]
    pub scene: String,
    pub on_load: Option<EventHandler<SplineApplication>>,
    pub on_mouse_down: Option<EventHandler<SplineEvent>>,
    pub on_mouse_up: Option<EventHandler<SplineEvent>>,
    pub on_mouse_hover: Option<EventHandler<SplineEvent>>,
    pub on_key_down: Option<EventHandler<SplineEvent>>,
    pub on_key_up: Option<EventHandler<SplineEvent>>,
    pub on_start: Option<EventHandler<SplineEvent>>,
    pub on_look_at: Option<EventHandler<SplineEvent>>,
    pub on_follow: Option<EventHandler<SplineEvent>>,
    pub on_wheel: Option<EventHandler<SplineEvent>>,
    pub render_on_demand: Option<bool>,
}

/// Utility to get the raw canvas element from its mounted data.
fn get_raw_canvas_element(mounted: &MountedData) -> &web_sys::HtmlCanvasElement {
    mounted
        .downcast::<web_sys::Element>()
        .unwrap()
        .dyn_ref::<web_sys::HtmlCanvasElement>()
        .unwrap()
}

fn _event_factory(
    props: &SplineProps,
) -> Vec<(SplineEventName, Option<EventHandler<SplineEvent>>)> {
    vec![
        (SplineEventName::mouseDown, props.on_mouse_down),
        (SplineEventName::mouseUp, props.on_mouse_up),
        (SplineEventName::mouseHover, props.on_mouse_hover),
        (SplineEventName::keyDown, props.on_key_down),
        (SplineEventName::keyUp, props.on_key_up),
        (SplineEventName::start, props.on_start),
        (SplineEventName::lookAt, props.on_look_at),
        (SplineEventName::follow, props.on_follow),
        (SplineEventName::scroll, props.on_wheel),
    ]
}
/// You can pass just the scene `String` to this component for simple renders. Alternatively,
/// you can also attach event handlers to attributes defined in [SplineProps].
/// If you would like to store SPEObject and modify it with events, you can use `use_signal` the following way:
///
/// # Example
///
/// *In the component*
/// ```
/// let mut app = use_signal(|| None::<SplineApplication>);
///
/// rsx! {
///     Spline {
///         scene: String::from("my-awesome-scene.splinecode")
///         on_load: move |event: SplineApplication| {
///             // Store app
///             app.set(Some(event));
///         }
///     }
/// }
///
/// ```
///
/// Then you can listen to `SplineEvents` or rescale/move objects with other events:
///
/// # Example
///
/// ```
/// button {
///   onclick: move |_| {
///        let mut spe_object = app.unwrap().find_object_by_name(String::from("Cube"));
///        spe_object.scale.x *= 2;
///    },
///   "Make cube chonky!"
/// }
/// ```
/// **Notice the difference between on_click and onclick!**

#[component]
pub fn Spline(props: SplineProps) -> Element {
    let mut app = use_signal(|| None::<SplineApplication>);
    let scene = use_signal(|| props.scene.clone());
    let props_cloned = props.clone();
    let mut is_loading = use_signal(|| true);
    // Load scene and attach events
    let _ = use_resource(move || {
        let events = _event_factory(&props_cloned);

        async move {
            app.unwrap().load(scene()).await;

            is_loading.set(false);
            for (event_name, handler) in events {
                if let Some(handler) = handler {
                    let cb = move |event: JsValue| {
                        let event: SplineEvent = event.into();
                        handler.call(event);
                    };

                    tracing::info!("Adding event listener for {:?}", event_name);
                    app.unwrap()
                        .add_event_listener(event_name.to_string().as_str(), cb);
                }
            }
            if let Some(on_load) = props_cloned.on_load {
                on_load.call(app.unwrap())
            }
        }
    });

    rsx! {
        canvas {
            onmounted: move |event: Event<MountedData>| {
                let canvas_ref = get_raw_canvas_element(&event.data);
                let render_on_demand = props.render_on_demand.unwrap_or(true);
                app.set(Some(SplineApplication::new(canvas_ref, render_on_demand)));
            },
            style: match is_loading() {
                true => "display: none;",
                false => "display: block; width: 100%; height: 100%;",
            }
        }
    }
}