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