⎇ 52acaabcf42e81de31acf884f80c607fe8b829fe
crates/apps/gioser-web/src/lib.rs
Historia
Quién tocó qué
crudo
1//! Entrypoint WASM de la landing GioSer.
2//!
3//! Monta el canvas, instala listeners de mouse y resize, corre el loop
4//! con `requestAnimationFrame`, y reposiciona los 4 botones DOM
5//! `#tip-{aire|fuego|tierra|agua}` sobre las puntas proyectadas
6//! de la chacana cada frame.
7
8use std::cell::RefCell;
9use std::rc::Rc;
10
11use gioser_canvas_web::{tips, Renderer};
12use wasm_bindgen::prelude::*;
13use wasm_bindgen::JsCast;
14use web_sys::{Document, HtmlCanvasElement, HtmlElement, MouseEvent, Window};
15
16#[wasm_bindgen(start)]
17pub fn boot() -> Result<(), JsValue> {
18 install_panic_hook();
19 let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
20 let document = window
21 .document()
22 .ok_or_else(|| JsValue::from_str("no document"))?;
23
24 let canvas: HtmlCanvasElement = document
25 .get_element_by_id("gioser-canvas")
26 .ok_or_else(|| JsValue::from_str("no canvas#gioser-canvas"))?
27 .dyn_into()?;
28
29 fit_canvas(&canvas, &window);
30 let renderer = Rc::new(RefCell::new(Renderer::new(&canvas)?));
31 renderer
32 .borrow_mut()
33 .resize(canvas.width(), canvas.height());
34
35 install_resize(&window, &canvas, &renderer)?;
36 install_mouse(&document, &canvas, &renderer)?;
37 install_raf(&window, &document, &canvas, &renderer);
38
39 Ok(())
40}
41
42fn install_resize(
43 window: &Window,
44 canvas: &HtmlCanvasElement,
45 renderer: &Rc<RefCell<Renderer>>,
46) -> Result<(), JsValue> {
47 let canvas = canvas.clone();
48 let win2 = window.clone();
49 let r = renderer.clone();
50 let cb = Closure::<dyn FnMut()>::new(move || {
51 fit_canvas(&canvas, &win2);
52 r.borrow_mut().resize(canvas.width(), canvas.height());
53 });
54 window.add_event_listener_with_callback("resize", cb.as_ref().unchecked_ref())?;
55 cb.forget();
56 Ok(())
57}
58
59fn install_mouse(
60 document: &Document,
61 canvas: &HtmlCanvasElement,
62 renderer: &Rc<RefCell<Renderer>>,
63) -> Result<(), JsValue> {
64 let canvas = canvas.clone();
65 let r = renderer.clone();
66 let cb = Closure::<dyn FnMut(MouseEvent)>::new(move |e: MouseEvent| {
67 let w = canvas.client_width().max(1) as f32;
68 let h = canvas.client_height().max(1) as f32;
69 // Origen en el centro del canvas, +y arriba.
70 let x = e.client_x() as f32 - w * 0.5;
71 let y = h * 0.5 - e.client_y() as f32;
72 r.borrow_mut().set_mouse_px(x, y);
73 });
74 document.add_event_listener_with_callback("mousemove", cb.as_ref().unchecked_ref())?;
75 cb.forget();
76 Ok(())
77}
78
79fn install_raf(
80 window: &Window,
81 document: &Document,
82 canvas: &HtmlCanvasElement,
83 renderer: &Rc<RefCell<Renderer>>,
84) {
85 let f = Rc::new(RefCell::new(None::<Closure<dyn FnMut(f64)>>));
86 let g = f.clone();
87 let renderer = renderer.clone();
88 let canvas = canvas.clone();
89 let document = document.clone();
90 let window2 = window.clone();
91 *g.borrow_mut() = Some(Closure::<dyn FnMut(f64)>::new(move |time_ms: f64| {
92 renderer.borrow_mut().render(time_ms);
93 position_tips(&document, &canvas, &renderer.borrow());
94 if let Some(cb) = f.borrow().as_ref() {
95 let _ = window2.request_animation_frame(cb.as_ref().unchecked_ref());
96 }
97 }));
98 let _ = window.request_animation_frame(g.borrow().as_ref().unwrap().as_ref().unchecked_ref());
99}
100
101fn fit_canvas(canvas: &HtmlCanvasElement, window: &Window) {
102 let dpr = window.device_pixel_ratio() as f32;
103 let w = window
104 .inner_width()
105 .ok()
106 .and_then(|v| v.as_f64())
107 .unwrap_or(1280.0) as f32;
108 let h = window
109 .inner_height()
110 .ok()
111 .and_then(|v| v.as_f64())
112 .unwrap_or(720.0) as f32;
113 canvas.set_width((w * dpr) as u32);
114 canvas.set_height((h * dpr) as u32);
115 let el: &HtmlElement = canvas.unchecked_ref();
116 let style = el.style();
117 let _ = style.set_property("width", &format!("{}px", w));
118 let _ = style.set_property("height", &format!("{}px", h));
119}
120
121fn position_tips(document: &Document, canvas: &HtmlCanvasElement, renderer: &Renderer) {
122 let clips = renderer.tips_ndc();
123 let cw = canvas.client_width().max(1) as f32;
124 let ch = canvas.client_height().max(1) as f32;
125 for (i, (id, _color, _label)) in tips::ORDER.iter().enumerate() {
126 let (nx, ny) = clips[i];
127 let px = (nx + 1.0) * 0.5 * cw;
128 let py = (1.0 - (ny + 1.0) * 0.5) * ch;
129 let sel = format!("tip-{}", id);
130 if let Some(el) = document.get_element_by_id(&sel) {
131 if let Ok(el) = el.dyn_into::<HtmlElement>() {
132 let _ = el.style().set_property(
133 "transform",
134 &format!("translate({:.2}px, {:.2}px) translate(-50%, -50%)", px, py),
135 );
136 }
137 }
138 }
139}
140
141fn install_panic_hook() {
142 static SET: std::sync::Once = std::sync::Once::new();
143 SET.call_once(|| {
144 std::panic::set_hook(Box::new(|info| {
145 let msg = format!("{}", info);
146 web_sys::console::error_1(&JsValue::from_str(&msg));
147 }));
148 });
149}