com_croftsoft_lib_animation/web_sys/
mod.rs1use anyhow::{anyhow, Result};
17use futures::channel::mpsc::{unbounded, UnboundedReceiver};
18use js_sys::Function;
19use std::{cell::RefCell, rc::Rc};
20use wasm_bindgen::prelude::Closure;
21use wasm_bindgen::{JsCast, JsValue};
22use web_sys::{
23 console, window, Document, DomRect, Element, Event, EventTarget,
24 HtmlCanvasElement, HtmlElement, MouseEvent, Window,
25};
26
27type LoopClosure = Closure<dyn FnMut(f64)>;
28
29pub trait LoopUpdater {
31 fn update_loop(
32 &mut self,
33 update_time: f64,
34 );
35}
36
37pub fn add_change_handler(elem: HtmlElement) -> UnboundedReceiver<Event> {
38 let (mut change_sender, change_receiver) = unbounded();
39 let event_closure = move |event: Event| {
40 let _result: Result<(), futures::channel::mpsc::SendError> =
41 change_sender.start_send(event);
42 };
43 let event_closure_box: Box<dyn FnMut(Event)> = Box::new(event_closure);
44 let on_change_closure: Closure<dyn FnMut(Event)> =
45 Closure::wrap(event_closure_box);
46 let closure_as_js_value_ref: &JsValue = on_change_closure.as_ref();
47 let js_function_ref: &Function = closure_as_js_value_ref.unchecked_ref();
48 let js_function_ref_option: Option<&Function> = Some(js_function_ref);
49 elem.set_onchange(js_function_ref_option);
50 on_change_closure.forget();
51 change_receiver
52}
53
54pub fn add_change_handler_by_id(id: &str) -> Option<UnboundedReceiver<Event>> {
55 let html_element = get_html_element_by_id(id);
56 Some(add_change_handler(html_element))
58}
59
60pub fn add_click_handler(elem: HtmlElement) -> UnboundedReceiver<()> {
61 let (mut click_sender, click_receiver) = unbounded();
62 let on_click = Closure::wrap(Box::new(move || {
63 let _result: Result<(), futures::channel::mpsc::SendError> =
64 click_sender.start_send(());
65 }) as Box<dyn FnMut()>);
66 elem.set_onclick(Some(on_click.as_ref().unchecked_ref()));
67 on_click.forget();
68 click_receiver
69}
70
71pub fn add_click_handler_by_id(id: &str) -> Option<UnboundedReceiver<()>> {
72 let html_element = get_html_element_by_id(id);
73 Some(add_click_handler(html_element))
75}
76
77pub fn add_mouse_down_handler(
78 elem: HtmlElement
79) -> UnboundedReceiver<MouseEvent> {
80 let (mut mouse_down_sender, mouse_down_receiver) = unbounded();
81 let mouse_event_closure = move |mouse_event: MouseEvent| {
82 let _result: Result<(), futures::channel::mpsc::SendError> =
83 mouse_down_sender.start_send(mouse_event);
84 };
85 let mouse_event_closure_box: Box<dyn FnMut(MouseEvent)> =
86 Box::new(mouse_event_closure);
87 let on_mouse_down_closure: Closure<dyn FnMut(MouseEvent)> =
88 Closure::wrap(mouse_event_closure_box);
89 let closure_as_js_value_ref: &JsValue = on_mouse_down_closure.as_ref();
90 let js_function_ref: &Function = closure_as_js_value_ref.unchecked_ref();
91 let js_function_ref_option: Option<&Function> = Some(js_function_ref);
92 elem.set_onmousedown(js_function_ref_option);
93 on_mouse_down_closure.forget();
94 mouse_down_receiver
95}
96
97pub fn add_mouse_down_handler_by_id(
98 id: &str
99) -> Option<UnboundedReceiver<MouseEvent>> {
100 let html_element = get_html_element_by_id(id);
101 Some(add_mouse_down_handler(html_element))
103}
104
105pub fn get_canvas_xy(mouse_event: &MouseEvent) -> (usize, usize) {
106 let client_x: f64 = mouse_event.client_x() as f64;
107 let client_y: f64 = mouse_event.client_y() as f64;
108 let event_target: EventTarget = mouse_event.target().unwrap();
109 let html_canvas_element: HtmlCanvasElement = event_target.dyn_into().unwrap();
110 let dom_rect: DomRect = html_canvas_element.get_bounding_client_rect();
111 let scale_x = html_canvas_element.width() as f64 / dom_rect.width();
112 let scale_y = html_canvas_element.height() as f64 / dom_rect.height();
113 let canvas_x: usize = ((client_x - dom_rect.left()) * scale_x) as usize;
114 let canvas_y: usize = ((client_y - dom_rect.top()) * scale_y) as usize;
115 (canvas_x, canvas_y)
116}
117
118pub fn get_html_canvas_element_by_id(
119 canvas_element_id: &str
120) -> HtmlCanvasElement {
121 let document: Document = window().unwrap().document().unwrap();
122 let element: Element = document.get_element_by_id(canvas_element_id).unwrap();
123 element.dyn_into().unwrap()
124}
125
126pub fn get_html_element_by_id(id: &str) -> HtmlElement {
127 let document: Document = window().unwrap().document().unwrap();
128 let element: Element = document.get_element_by_id(id).unwrap();
129 element.dyn_into().unwrap()
130}
131
132pub fn get_window() -> Result<Window> {
133 web_sys::window().ok_or_else(|| anyhow!("No Window Found"))
134}
135
136pub fn log(message: &str) {
137 console::log_1(&JsValue::from_str(message));
138}
139
140pub fn request_animation_frame(
141 callback: &Closure<dyn FnMut(f64)>
142) -> Result<i32> {
143 get_window()?
144 .request_animation_frame(callback.as_ref().unchecked_ref())
145 .map_err(|err| anyhow!("Cannot request animation frame {:#?}", err))
146}
147
148pub fn spawn_local_loop<L: LoopUpdater + 'static>(loop_updater: L) {
149 wasm_bindgen_futures::spawn_local(async move {
150 start_looping(loop_updater).await.expect("loop start failed");
151 });
152}
153
154pub async fn start_looping<L: LoopUpdater + 'static>(
155 mut loop_updater: L
156) -> Result<()> {
157 let f: Rc<RefCell<Option<LoopClosure>>> = Rc::new(RefCell::new(None));
158 let g = f.clone();
159 *g.borrow_mut() = Some(Closure::wrap(Box::new(move |update_time: f64| {
160 loop_updater.update_loop(update_time);
161 let _result: Result<i32, anyhow::Error> =
162 request_animation_frame(f.borrow().as_ref().unwrap());
163 })));
164 request_animation_frame(
165 g.borrow().as_ref().ok_or_else(|| anyhow!("loop failed"))?,
166 )?;
167 Ok(())
168}