1 use super::event;
2 use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
3 use crate::error::OsError as RootOE;
4 use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
5 use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
6 
7 use std::cell::RefCell;
8 use std::rc::Rc;
9 use stdweb::js;
10 use stdweb::traits::IPointerEvent;
11 use stdweb::unstable::TryInto;
12 use stdweb::web::event::{
13     BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, KeyDownEvent,
14     KeyPressEvent, KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent,
15     PointerOutEvent, PointerOverEvent, PointerUpEvent,
16 };
17 use stdweb::web::html_element::CanvasElement;
18 use stdweb::web::{
19     document, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement,
20 };
21 
22 pub struct Canvas {
23     /// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
24     raw: CanvasElement,
25     on_focus: Option<EventListenerHandle>,
26     on_blur: Option<EventListenerHandle>,
27     on_keyboard_release: Option<EventListenerHandle>,
28     on_keyboard_press: Option<EventListenerHandle>,
29     on_received_character: Option<EventListenerHandle>,
30     on_cursor_leave: Option<EventListenerHandle>,
31     on_cursor_enter: Option<EventListenerHandle>,
32     on_cursor_move: Option<EventListenerHandle>,
33     on_mouse_press: Option<EventListenerHandle>,
34     on_mouse_release: Option<EventListenerHandle>,
35     on_mouse_wheel: Option<EventListenerHandle>,
36     on_fullscreen_change: Option<EventListenerHandle>,
37     wants_fullscreen: Rc<RefCell<bool>>,
38 }
39 
40 impl Drop for Canvas {
drop(&mut self)41     fn drop(&mut self) {
42         self.raw.remove();
43     }
44 }
45 
46 impl Canvas {
create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE>47     pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result<Self, RootOE> {
48         let canvas = match attr.canvas {
49             Some(canvas) => canvas,
50             None => document()
51                 .create_element("canvas")
52                 .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?
53                 .try_into()
54                 .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?,
55         };
56 
57         // A tabindex is needed in order to capture local keyboard events.
58         // A "0" value means that the element should be focusable in
59         // sequential keyboard navigation, but its order is defined by the
60         // document's source order.
61         // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
62         canvas
63             .set_attribute("tabindex", "0")
64             .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
65 
66         Ok(Canvas {
67             raw: canvas,
68             on_blur: None,
69             on_focus: None,
70             on_keyboard_release: None,
71             on_keyboard_press: None,
72             on_received_character: None,
73             on_cursor_leave: None,
74             on_cursor_enter: None,
75             on_cursor_move: None,
76             on_mouse_release: None,
77             on_mouse_press: None,
78             on_mouse_wheel: None,
79             on_fullscreen_change: None,
80             wants_fullscreen: Rc::new(RefCell::new(false)),
81         })
82     }
83 
set_attribute(&self, attribute: &str, value: &str)84     pub fn set_attribute(&self, attribute: &str, value: &str) {
85         self.raw
86             .set_attribute(attribute, value)
87             .expect(&format!("Set attribute: {}", attribute));
88     }
89 
position(&self) -> LogicalPosition<f64>90     pub fn position(&self) -> LogicalPosition<f64> {
91         let bounds = self.raw.get_bounding_client_rect();
92 
93         LogicalPosition {
94             x: bounds.get_x(),
95             y: bounds.get_y(),
96         }
97     }
98 
size(&self) -> PhysicalSize<u32>99     pub fn size(&self) -> PhysicalSize<u32> {
100         PhysicalSize {
101             width: self.raw.width() as u32,
102             height: self.raw.height() as u32,
103         }
104     }
105 
raw(&self) -> &CanvasElement106     pub fn raw(&self) -> &CanvasElement {
107         &self.raw
108     }
109 
on_blur<F>(&mut self, mut handler: F) where F: 'static + FnMut(),110     pub fn on_blur<F>(&mut self, mut handler: F)
111     where
112         F: 'static + FnMut(),
113     {
114         self.on_blur = Some(self.add_event(move |_: BlurEvent| {
115             handler();
116         }));
117     }
118 
on_focus<F>(&mut self, mut handler: F) where F: 'static + FnMut(),119     pub fn on_focus<F>(&mut self, mut handler: F)
120     where
121         F: 'static + FnMut(),
122     {
123         self.on_focus = Some(self.add_event(move |_: FocusEvent| {
124             handler();
125         }));
126     }
127 
on_keyboard_release<F>(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),128     pub fn on_keyboard_release<F>(&mut self, mut handler: F)
129     where
130         F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
131     {
132         self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| {
133             event.prevent_default();
134             handler(
135                 event::scan_code(&event),
136                 event::virtual_key_code(&event),
137                 event::keyboard_modifiers(&event),
138             );
139         }));
140     }
141 
on_keyboard_press<F>(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),142     pub fn on_keyboard_press<F>(&mut self, mut handler: F)
143     where
144         F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
145     {
146         self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| {
147             event.prevent_default();
148             handler(
149                 event::scan_code(&event),
150                 event::virtual_key_code(&event),
151                 event::keyboard_modifiers(&event),
152             );
153         }));
154     }
155 
on_received_character<F>(&mut self, mut handler: F) where F: 'static + FnMut(char),156     pub fn on_received_character<F>(&mut self, mut handler: F)
157     where
158         F: 'static + FnMut(char),
159     {
160         // TODO: Use `beforeinput`.
161         //
162         // The `keypress` event is deprecated, but there does not seem to be a
163         // viable/compatible alternative as of now. `beforeinput` is still widely
164         // unsupported.
165         self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| {
166             handler(event::codepoint(&event));
167         }));
168     }
169 
on_cursor_leave<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32),170     pub fn on_cursor_leave<F>(&mut self, mut handler: F)
171     where
172         F: 'static + FnMut(i32),
173     {
174         self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| {
175             handler(event.pointer_id());
176         }));
177     }
178 
on_cursor_enter<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32),179     pub fn on_cursor_enter<F>(&mut self, mut handler: F)
180     where
181         F: 'static + FnMut(i32),
182     {
183         self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| {
184             handler(event.pointer_id());
185         }));
186     }
187 
on_mouse_release<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState),188     pub fn on_mouse_release<F>(&mut self, mut handler: F)
189     where
190         F: 'static + FnMut(i32, MouseButton, ModifiersState),
191     {
192         self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| {
193             handler(
194                 event.pointer_id(),
195                 event::mouse_button(&event),
196                 event::mouse_modifiers(&event),
197             );
198         }));
199     }
200 
on_mouse_press<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState),201     pub fn on_mouse_press<F>(&mut self, mut handler: F)
202     where
203         F: 'static + FnMut(i32, MouseButton, ModifiersState),
204     {
205         self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
206             handler(
207                 event.pointer_id(),
208                 event::mouse_button(&event),
209                 event::mouse_modifiers(&event),
210             );
211         }));
212     }
213 
on_cursor_move<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),214     pub fn on_cursor_move<F>(&mut self, mut handler: F)
215     where
216         F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
217     {
218         // todo
219         self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| {
220             handler(
221                 event.pointer_id(),
222                 event::mouse_position(&event).to_physical(super::scale_factor()),
223                 event::mouse_modifiers(&event),
224             );
225         }));
226     }
227 
on_mouse_wheel<F>(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),228     pub fn on_mouse_wheel<F>(&mut self, mut handler: F)
229     where
230         F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
231     {
232         self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| {
233             event.prevent_default();
234             if let Some(delta) = event::mouse_scroll_delta(&event) {
235                 handler(0, delta, event::mouse_modifiers(&event));
236             }
237         }));
238     }
239 
on_fullscreen_change<F>(&mut self, mut handler: F) where F: 'static + FnMut(),240     pub fn on_fullscreen_change<F>(&mut self, mut handler: F)
241     where
242         F: 'static + FnMut(),
243     {
244         self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler()));
245     }
246 
on_dark_mode<F>(&mut self, handler: F) where F: 'static + FnMut(bool),247     pub fn on_dark_mode<F>(&mut self, handler: F)
248     where
249         F: 'static + FnMut(bool),
250     {
251         // TODO: upstream to stdweb
252         js! {
253             var handler = @{handler};
254 
255             if (window.matchMedia) {
256                 window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) {
257                     handler(event.matches)
258                 });
259             }
260         }
261     }
262 
add_event<E, F>(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, F: 'static + FnMut(E),263     fn add_event<E, F>(&self, mut handler: F) -> EventListenerHandle
264     where
265         E: ConcreteEvent,
266         F: 'static + FnMut(E),
267     {
268         self.raw.add_event_listener(move |event: E| {
269             event.stop_propagation();
270             event.cancel_bubble();
271 
272             handler(event);
273         })
274     }
275 
276     // The difference between add_event and add_user_event is that the latter has a special meaning
277     // for browser security. A user event is a deliberate action by the user (like a mouse or key
278     // press) and is the only time things like a fullscreen request may be successfully completed.)
add_user_event<E, F>(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, F: 'static + FnMut(E),279     fn add_user_event<E, F>(&self, mut handler: F) -> EventListenerHandle
280     where
281         E: ConcreteEvent,
282         F: 'static + FnMut(E),
283     {
284         let wants_fullscreen = self.wants_fullscreen.clone();
285         let canvas = self.raw.clone();
286 
287         self.add_event(move |event: E| {
288             handler(event);
289 
290             if *wants_fullscreen.borrow() {
291                 canvas.request_fullscreen();
292                 *wants_fullscreen.borrow_mut() = false;
293             }
294         })
295     }
296 
request_fullscreen(&self)297     pub fn request_fullscreen(&self) {
298         *self.wants_fullscreen.borrow_mut() = true;
299     }
300 
is_fullscreen(&self) -> bool301     pub fn is_fullscreen(&self) -> bool {
302         super::is_fullscreen(&self.raw)
303     }
304 }
305