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