use std::rc::Rc; use gpui::{ Capslock, DispatchEventResult, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, point, px,};use wasm_bindgen::prelude::*; use crate::window::WebWindowInner; pub struct WebEventListeners { _handles: Vec<EventListenerHandle>,} /// A DOM event listener that is removed from its target when dropped.////// Dropping the `Closure` alone would leave the listener attached to the DOM/// pointing at a freed function; the next event would then throw "closure/// invoked after being dropped". Keeping the target alongside the closure/// lets `Drop` unregister the listener first.pub(crate) struct EventListenerHandle { target: web_sys::EventTarget, event_name: &'static str, closure: Closure<dyn FnMut(JsValue)>,} impl EventListenerHandle { pub(crate) fn add( target: &web_sys::EventTarget, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> Self { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); target .add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref()) .ok(); Self { target: target.clone(), event_name, closure, } } /// Registers with `{passive: false}` so that `preventDefault()` works. /// Needed for events like `wheel` which are passive by default in modern /// browsers. Removal does not need to match the `passive` option, so /// `Drop` works the same as for [`EventListenerHandle::add`]. fn add_non_passive( target: &web_sys::EventTarget, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> Self { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); let target_js: &JsValue = target.as_ref(); let callback_js: &JsValue = closure.as_ref(); let options = js_sys::Object::new(); js_sys::Reflect::set(&options, &"passive".into(), &false.into()).ok(); if let Ok(add_fn_val) = js_sys::Reflect::get(target_js, &"addEventListener".into()) { if let Ok(add_fn) = add_fn_val.dyn_into::<js_sys::Function>() { add_fn .call3(target_js, &event_name.into(), callback_js, &options) .ok(); } } Self { target: target.clone(), event_name, closure, } }} impl Drop for EventListenerHandle { fn drop(&mut self) { self.target .remove_event_listener_with_callback( self.event_name, self.closure.as_ref().unchecked_ref(), ) .ok(); }} pub(crate) struct ClickState { last_position: Point<Pixels>, last_time: f64, current_count: usize,} impl Default for ClickState { fn default() -> Self { Self { last_position: Point::default(), last_time: 0.0, current_count: 0, } }} impl ClickState { fn register_click(&mut self, position: Point<Pixels>, time: f64) -> usize { let distance = ((f32::from(position.x) - f32::from(self.last_position.x)).powi(2) + (f32::from(position.y) - f32::from(self.last_position.y)).powi(2)) .sqrt(); if (time - self.last_time) < 400.0 && distance < 5.0 { self.current_count += 1; } else { self.current_count = 1; } self.last_position = position; self.last_time = time; self.current_count }} impl WebWindowInner { pub fn register_event_listeners(self: &Rc<Self>) -> WebEventListeners { let mut handles = vec![ self.register_pointer_down(), self.register_pointer_up(), self.register_pointer_move(), self.register_pointer_leave(), self.register_wheel(), self.register_context_menu(), self.register_dragover(), self.register_drop(), self.register_key_down(), self.register_key_up(), self.register_paste(), self.register_composition_start(), self.register_composition_update(), self.register_composition_end(), self.register_focus(), self.register_blur(), self.register_pointer_enter(), ]; handles.extend(self.register_visibility_change()); handles.extend(self.register_appearance_change()); handles.extend(self.register_fullscreen_change()); WebEventListeners { _handles: handles } } fn listen( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add(self.canvas.as_ref(), event_name, handler) } fn listen_input( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add(self.input_element.as_ref(), event_name, handler) } fn listen_non_passive( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add_non_passive(self.canvas.as_ref(), event_name, handler) } fn dispatch_input(&self, input: PlatformInput) -> Option<DispatchEventResult> { self.with_callback(|callbacks| &mut callbacks.input, |callback| callback(input)) } /// Records the latest modifier state and reports whether it changed, so /// that `ModifiersChanged` is only dispatched on actual transitions /// rather than for every key event. fn update_modifiers(&self, modifiers: Modifiers, capslock: Capslock) -> bool { let mut current_state = self.state.borrow_mut(); let changed = current_state.modifiers != modifiers || current_state.capslock != capslock; current_state.modifiers = modifiers; current_state.capslock = capslock; changed } fn register_pointer_down(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerdown", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); this.input_element.focus().ok(); // Capture the pointer so drags that leave the canvas keep // delivering pointermove/pointerup here; otherwise a release // outside the canvas is never seen and `pressed_button` stays // stuck. The capture is released implicitly on pointerup. this.canvas.set_pointer_capture(event.pointer_id()).ok(); let button = dom_mouse_button_to_gpui(event.button()); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let time = js_sys::Date::now(); this.pressed_button.set(Some(button)); let click_count = this.click_state.borrow_mut().register_click(position, time); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseDown(MouseDownEvent { button, position, modifiers, click_count, first_mouse: false, })); }) } fn register_pointer_up(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerup", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); let button = dom_mouse_button_to_gpui(event.button()); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); this.pressed_button.set(None); let click_count = this.click_state.borrow().current_count; { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseUp(MouseUpEvent { button, position, modifiers, click_count, })); }) } fn register_pointer_move(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointermove", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let current_pressed = this.pressed_button.get(); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: current_pressed, modifiers, })); }) } fn register_pointer_leave(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerleave", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let current_pressed = this.pressed_button.get(); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; current_state.is_hovered = false; } this.dispatch_input(PlatformInput::MouseExited(MouseExitEvent { position, pressed_button: current_pressed, modifiers, })); this.with_callback( |callbacks| &mut callbacks.hover_status_change, |callback| callback(false), ); }) } fn register_wheel(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_non_passive("wheel", move |event: JsValue| { let event: web_sys::WheelEvent = event.unchecked_into(); event.prevent_default(); let mouse_event: &web_sys::MouseEvent = event.as_ref(); let position = mouse_position_in_element(mouse_event); let modifiers = modifiers_from_wheel_event(mouse_event, this.is_mac); let delta_mode = event.delta_mode(); let delta = if delta_mode == 1 { ScrollDelta::Lines(point(-event.delta_x() as f32, -event.delta_y() as f32)) } else { ScrollDelta::Pixels(point( px(-event.delta_x() as f32), px(-event.delta_y() as f32), )) }; { let mut current_state = this.state.borrow_mut(); current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::ScrollWheel(ScrollWheelEvent { position, delta, modifiers, touch_phase: TouchPhase::Moved, })); }) } fn register_context_menu(self: &Rc<Self>) -> EventListenerHandle { self.listen("contextmenu", move |event: JsValue| { let event: web_sys::Event = event.unchecked_into(); event.prevent_default(); }) } /// Browsers only expose dropped files as `File` objects, never as /// filesystem paths, so no `FileDrop` input can be synthesized: GPUI's /// `ExternalPaths` consumers would try to read paths that don't exist. /// The events are still intercepted so the browser doesn't navigate to /// the dropped file. Delivering actual file drops would require plumbing /// `File` contents through a web-specific channel. fn register_dragover(self: &Rc<Self>) -> EventListenerHandle { self.listen("dragover", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); }) } fn register_drop(self: &Rc<Self>) -> EventListenerHandle { self.listen("drop", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); }) } fn register_key_down(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("keydown", move |event: JsValue| { let event: web_sys::KeyboardEvent = event.unchecked_into(); let modifiers = modifiers_from_keyboard_event(&event, this.is_mac); let capslock = capslock_from_keyboard_event(&event); if this.update_modifiers(modifiers, capslock) { this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); } let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } let is_held = event.repeat(); let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char: key_char.clone(), }; let result = this.dispatch_input(PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held, prefer_character_input: false, })); if let Some(result) = result { if !result.propagate { event.prevent_default(); return; } } if this.is_composing.get() || event.is_composing() { event.prevent_default(); return; } if keystroke_inserts_text(&modifiers, this.is_mac) && let Some(text) = key_char { this.with_input_handler(|handler| { handler.replace_text_in_range(None, &text); }); // The character went into the input handler; suppress browser // side-effects for the same keystroke (space scrolling the // page, quick-find, etc.). Everything not handled above falls // through so browser shortcuts keep their defaults. event.prevent_default(); } }) } fn register_key_up(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("keyup", move |event: JsValue| { let event: web_sys::KeyboardEvent = event.unchecked_into(); let modifiers = modifiers_from_keyboard_event(&event, this.is_mac); let capslock = capslock_from_keyboard_event(&event); if this.update_modifiers(modifiers, capslock) { this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); } let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char, }; let result = this.dispatch_input(PlatformInput::KeyUp(KeyUpEvent { keystroke })); if let Some(result) = result { if !result.propagate { event.prevent_default(); } } }) } /// Paste is delivered through the DOM `paste` event rather than /// `Platform::read_from_clipboard`: the browser's asynchronous clipboard /// read API cannot fit that synchronous signature, while `ClipboardEvent` /// exposes `clipboardData` synchronously inside the event. It fires for /// any browser-initiated paste (keyboard, menu bar, context menu). fn register_paste(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("paste", move |event: JsValue| { let event: web_sys::ClipboardEvent = event.unchecked_into(); let Some(clipboard_data) = event.clipboard_data() else { return; }; let Ok(text) = clipboard_data.get_data("text/plain") else { return; }; if text.is_empty() { return; } event.prevent_default(); this.with_input_handler(|handler| { handler.replace_text_in_range(None, &text); }); }) } fn register_composition_start(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("compositionstart", move |_event: JsValue| { this.is_composing.set(true); }) } fn register_composition_update(self: &Rc<Self>) -> EventListenerHandle {Lines 1–500 of 728. Load more
use std::rc::Rc; use gpui::{ Capslock, DispatchEventResult, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, point, px, }; use wasm_bindgen::prelude::*; use crate::window::WebWindowInner; pub struct WebEventListeners { _handles: Vec<EventListenerHandle>, } /// A DOM event listener that is removed from its target when dropped. /// /// Dropping the `Closure` alone would leave the listener attached to the DOM /// pointing at a freed function; the next event would then throw "closure /// invoked after being dropped". Keeping the target alongside the closure /// lets `Drop` unregister the listener first. pub(crate) struct EventListenerHandle { target: web_sys::EventTarget, event_name: &'static str, closure: Closure<dyn FnMut(JsValue)>, } impl EventListenerHandle { pub(crate) fn add( target: &web_sys::EventTarget, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> Self { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); target .add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref()) .ok(); Self { target: target.clone(), event_name, closure, } } /// Registers with `{passive: false}` so that `preventDefault()` works. /// Needed for events like `wheel` which are passive by default in modern /// browsers. Removal does not need to match the `passive` option, so /// `Drop` works the same as for [`EventListenerHandle::add`]. fn add_non_passive( target: &web_sys::EventTarget, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> Self { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); let target_js: &JsValue = target.as_ref(); let callback_js: &JsValue = closure.as_ref(); let options = js_sys::Object::new(); js_sys::Reflect::set(&options, &"passive".into(), &false.into()).ok(); if let Ok(add_fn_val) = js_sys::Reflect::get(target_js, &"addEventListener".into()) { if let Ok(add_fn) = add_fn_val.dyn_into::<js_sys::Function>() { add_fn .call3(target_js, &event_name.into(), callback_js, &options) .ok(); } } Self { target: target.clone(), event_name, closure, } } } impl Drop for EventListenerHandle { fn drop(&mut self) { self.target .remove_event_listener_with_callback( self.event_name, self.closure.as_ref().unchecked_ref(), ) .ok(); } } pub(crate) struct ClickState { last_position: Point<Pixels>, last_time: f64, current_count: usize, } impl Default for ClickState { fn default() -> Self { Self { last_position: Point::default(), last_time: 0.0, current_count: 0, } } } impl ClickState { fn register_click(&mut self, position: Point<Pixels>, time: f64) -> usize { let distance = ((f32::from(position.x) - f32::from(self.last_position.x)).powi(2) + (f32::from(position.y) - f32::from(self.last_position.y)).powi(2)) .sqrt(); if (time - self.last_time) < 400.0 && distance < 5.0 { self.current_count += 1; } else { self.current_count = 1; } self.last_position = position; self.last_time = time; self.current_count } } impl WebWindowInner { pub fn register_event_listeners(self: &Rc<Self>) -> WebEventListeners { let mut handles = vec![ self.register_pointer_down(), self.register_pointer_up(), self.register_pointer_move(), self.register_pointer_leave(), self.register_wheel(), self.register_context_menu(), self.register_dragover(), self.register_drop(), self.register_key_down(), self.register_key_up(), self.register_paste(), self.register_composition_start(), self.register_composition_update(), self.register_composition_end(), self.register_focus(), self.register_blur(), self.register_pointer_enter(), ]; handles.extend(self.register_visibility_change()); handles.extend(self.register_appearance_change()); handles.extend(self.register_fullscreen_change()); WebEventListeners { _handles: handles } } fn listen( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add(self.canvas.as_ref(), event_name, handler) } fn listen_input( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add(self.input_element.as_ref(), event_name, handler) } fn listen_non_passive( self: &Rc<Self>, event_name: &'static str, handler: impl FnMut(JsValue) + 'static, ) -> EventListenerHandle { EventListenerHandle::add_non_passive(self.canvas.as_ref(), event_name, handler) } fn dispatch_input(&self, input: PlatformInput) -> Option<DispatchEventResult> { self.with_callback(|callbacks| &mut callbacks.input, |callback| callback(input)) } /// Records the latest modifier state and reports whether it changed, so /// that `ModifiersChanged` is only dispatched on actual transitions /// rather than for every key event. fn update_modifiers(&self, modifiers: Modifiers, capslock: Capslock) -> bool { let mut current_state = self.state.borrow_mut(); let changed = current_state.modifiers != modifiers || current_state.capslock != capslock; current_state.modifiers = modifiers; current_state.capslock = capslock; changed } fn register_pointer_down(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerdown", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); this.input_element.focus().ok(); // Capture the pointer so drags that leave the canvas keep // delivering pointermove/pointerup here; otherwise a release // outside the canvas is never seen and `pressed_button` stays // stuck. The capture is released implicitly on pointerup. this.canvas.set_pointer_capture(event.pointer_id()).ok(); let button = dom_mouse_button_to_gpui(event.button()); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let time = js_sys::Date::now(); this.pressed_button.set(Some(button)); let click_count = this.click_state.borrow_mut().register_click(position, time); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseDown(MouseDownEvent { button, position, modifiers, click_count, first_mouse: false, })); }) } fn register_pointer_up(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerup", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); let button = dom_mouse_button_to_gpui(event.button()); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); this.pressed_button.set(None); let click_count = this.click_state.borrow().current_count; { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseUp(MouseUpEvent { button, position, modifiers, click_count, })); }) } fn register_pointer_move(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointermove", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let current_pressed = this.pressed_button.get(); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: current_pressed, modifiers, })); }) } fn register_pointer_leave(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen("pointerleave", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); let position = pointer_position_in_element(&event); let modifiers = modifiers_from_mouse_event(&event, this.is_mac); let current_pressed = this.pressed_button.get(); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; current_state.modifiers = modifiers; current_state.is_hovered = false; } this.dispatch_input(PlatformInput::MouseExited(MouseExitEvent { position, pressed_button: current_pressed, modifiers, })); this.with_callback( |callbacks| &mut callbacks.hover_status_change, |callback| callback(false), ); }) } fn register_wheel(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_non_passive("wheel", move |event: JsValue| { let event: web_sys::WheelEvent = event.unchecked_into(); event.prevent_default(); let mouse_event: &web_sys::MouseEvent = event.as_ref(); let position = mouse_position_in_element(mouse_event); let modifiers = modifiers_from_wheel_event(mouse_event, this.is_mac); let delta_mode = event.delta_mode(); let delta = if delta_mode == 1 { ScrollDelta::Lines(point(-event.delta_x() as f32, -event.delta_y() as f32)) } else { ScrollDelta::Pixels(point( px(-event.delta_x() as f32), px(-event.delta_y() as f32), )) }; { let mut current_state = this.state.borrow_mut(); current_state.modifiers = modifiers; } this.dispatch_input(PlatformInput::ScrollWheel(ScrollWheelEvent { position, delta, modifiers, touch_phase: TouchPhase::Moved, })); }) } fn register_context_menu(self: &Rc<Self>) -> EventListenerHandle { self.listen("contextmenu", move |event: JsValue| { let event: web_sys::Event = event.unchecked_into(); event.prevent_default(); }) } /// Browsers only expose dropped files as `File` objects, never as /// filesystem paths, so no `FileDrop` input can be synthesized: GPUI's /// `ExternalPaths` consumers would try to read paths that don't exist. /// The events are still intercepted so the browser doesn't navigate to /// the dropped file. Delivering actual file drops would require plumbing /// `File` contents through a web-specific channel. fn register_dragover(self: &Rc<Self>) -> EventListenerHandle { self.listen("dragover", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); }) } fn register_drop(self: &Rc<Self>) -> EventListenerHandle { self.listen("drop", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); }) } fn register_key_down(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("keydown", move |event: JsValue| { let event: web_sys::KeyboardEvent = event.unchecked_into(); let modifiers = modifiers_from_keyboard_event(&event, this.is_mac); let capslock = capslock_from_keyboard_event(&event); if this.update_modifiers(modifiers, capslock) { this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); } let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } let is_held = event.repeat(); let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char: key_char.clone(), }; let result = this.dispatch_input(PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held, prefer_character_input: false, })); if let Some(result) = result { if !result.propagate { event.prevent_default(); return; } } if this.is_composing.get() || event.is_composing() { event.prevent_default(); return; } if keystroke_inserts_text(&modifiers, this.is_mac) && let Some(text) = key_char { this.with_input_handler(|handler| { handler.replace_text_in_range(None, &text); }); // The character went into the input handler; suppress browser // side-effects for the same keystroke (space scrolling the // page, quick-find, etc.). Everything not handled above falls // through so browser shortcuts keep their defaults. event.prevent_default(); } }) } fn register_key_up(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("keyup", move |event: JsValue| { let event: web_sys::KeyboardEvent = event.unchecked_into(); let modifiers = modifiers_from_keyboard_event(&event, this.is_mac); let capslock = capslock_from_keyboard_event(&event); if this.update_modifiers(modifiers, capslock) { this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); } let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char, }; let result = this.dispatch_input(PlatformInput::KeyUp(KeyUpEvent { keystroke })); if let Some(result) = result { if !result.propagate { event.prevent_default(); } } }) } /// Paste is delivered through the DOM `paste` event rather than /// `Platform::read_from_clipboard`: the browser's asynchronous clipboard /// read API cannot fit that synchronous signature, while `ClipboardEvent` /// exposes `clipboardData` synchronously inside the event. It fires for /// any browser-initiated paste (keyboard, menu bar, context menu). fn register_paste(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("paste", move |event: JsValue| { let event: web_sys::ClipboardEvent = event.unchecked_into(); let Some(clipboard_data) = event.clipboard_data() else { return; }; let Ok(text) = clipboard_data.get_data("text/plain") else { return; }; if text.is_empty() { return; } event.prevent_default(); this.with_input_handler(|handler| { handler.replace_text_in_range(None, &text); }); }) } fn register_composition_start(self: &Rc<Self>) -> EventListenerHandle { let this = Rc::clone(self); self.listen_input("compositionstart", move |_event: JsValue| { this.is_composing.set(true); }) } fn register_composition_update(self: &Rc<Self>) -> EventListenerHandle {