728 lines · 24.9 KB · Rust
RawDownload
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.