dioxus_spline/
spline.rs

1use dioxus::prelude::*;
2
3use crate::runtime::{SplineApplication, SplineEvent, SplineEventName};
4use wasm_bindgen::{JsCast, JsValue};
5
6#[derive(Props, PartialEq, Clone)]
7pub struct SplineProps {
8    #[props(into)]
9    pub scene: String,
10    pub on_load: Option<EventHandler<SplineApplication>>,
11    pub on_mouse_down: Option<EventHandler<SplineEvent>>,
12    pub on_mouse_up: Option<EventHandler<SplineEvent>>,
13    pub on_mouse_hover: Option<EventHandler<SplineEvent>>,
14    pub on_key_down: Option<EventHandler<SplineEvent>>,
15    pub on_key_up: Option<EventHandler<SplineEvent>>,
16    pub on_start: Option<EventHandler<SplineEvent>>,
17    pub on_look_at: Option<EventHandler<SplineEvent>>,
18    pub on_follow: Option<EventHandler<SplineEvent>>,
19    pub on_wheel: Option<EventHandler<SplineEvent>>,
20    pub render_on_demand: Option<bool>,
21}
22
23/// Utility to get the raw canvas element from its mounted data.
24fn get_raw_canvas_element(mounted: &MountedData) -> &web_sys::HtmlCanvasElement {
25    mounted
26        .downcast::<web_sys::Element>()
27        .unwrap()
28        .dyn_ref::<web_sys::HtmlCanvasElement>()
29        .unwrap()
30}
31
32fn _event_factory(
33    props: &SplineProps,
34) -> Vec<(SplineEventName, Option<EventHandler<SplineEvent>>)> {
35    vec![
36        (SplineEventName::mouseDown, props.on_mouse_down),
37        (SplineEventName::mouseUp, props.on_mouse_up),
38        (SplineEventName::mouseHover, props.on_mouse_hover),
39        (SplineEventName::keyDown, props.on_key_down),
40        (SplineEventName::keyUp, props.on_key_up),
41        (SplineEventName::start, props.on_start),
42        (SplineEventName::lookAt, props.on_look_at),
43        (SplineEventName::follow, props.on_follow),
44        (SplineEventName::scroll, props.on_wheel),
45    ]
46}
47/// You can pass just the scene `String` to this component for simple renders. Alternatively,
48/// you can also attach event handlers to attributes defined in [SplineProps].
49/// If you would like to store SPEObject and modify it with events, you can use `use_signal` the following way:
50///
51/// # Example
52///
53/// *In the component*
54/// ```
55/// let mut app = use_signal(|| None::<SplineApplication>);
56///
57/// rsx! {
58///     Spline {
59///         scene: String::from("my-awesome-scene.splinecode")
60///         on_load: move |event: SplineApplication| {
61///             // Store app
62///             app.set(Some(event));
63///         }
64///     }
65/// }
66///
67/// ```
68///
69/// Then you can listen to `SplineEvents` or rescale/move objects with other events:
70///
71/// # Example
72///
73/// ```
74/// button {
75///   onclick: move |_| {
76///        let mut spe_object = app.unwrap().find_object_by_name(String::from("Cube"));
77///        spe_object.scale.x *= 2;
78///    },
79///   "Make cube chonky!"
80/// }
81/// ```
82/// **Notice the difference between on_click and onclick!**
83
84#[component]
85pub fn Spline(props: SplineProps) -> Element {
86    let mut app = use_signal(|| None::<SplineApplication>);
87    let scene = use_signal(|| props.scene.clone());
88    let props_cloned = props.clone();
89    let mut is_loading = use_signal(|| true);
90    // Load scene and attach events
91    let _ = use_resource(move || {
92        let events = _event_factory(&props_cloned);
93
94        async move {
95            app.unwrap().load(scene()).await;
96
97            is_loading.set(false);
98            for (event_name, handler) in events {
99                if let Some(handler) = handler {
100                    let cb = move |event: JsValue| {
101                        let event: SplineEvent = event.into();
102                        handler.call(event);
103                    };
104
105                    tracing::info!("Adding event listener for {:?}", event_name);
106                    app.unwrap()
107                        .add_event_listener(event_name.to_string().as_str(), cb);
108                }
109            }
110            if let Some(on_load) = props_cloned.on_load {
111                on_load.call(app.unwrap())
112            }
113        }
114    });
115
116    rsx! {
117        canvas {
118            onmounted: move |event: Event<MountedData>| {
119                let canvas_ref = get_raw_canvas_element(&event.data);
120                let render_on_demand = props.render_on_demand.unwrap_or(true);
121                app.set(Some(SplineApplication::new(canvas_ref, render_on_demand)));
122            },
123            style: match is_loading() {
124                true => "display: none;",
125                false => "display: block; width: 100%; height: 100%;",
126            }
127        }
128    }
129}