use std::rc::Rc;
use gpui::{
Capslock, ExternalPaths, FileDropEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
MouseUpEvent, NavigationDirection, Pixels, PlatformInput, Point, ScrollDelta, ScrollWheelEvent,
TouchPhase, point, px,
};
use smallvec::smallvec;
use wasm_bindgen::prelude::*;
use crate::window::WebWindowInner;
pub struct WebEventListeners {
#[allow(dead_code)]
closures: Vec<Closure<dyn FnMut(JsValue)>>,
}
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 closures = 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_dragleave(),
self.register_key_down(),
self.register_key_up(),
self.register_focus(),
self.register_blur(),
self.register_pointer_enter(),
self.register_pointer_leave_hover(),
];
closures.extend(self.register_visibility_change());
closures.extend(self.register_appearance_change());
WebEventListeners { closures }
}
fn listen(
self: &Rc<Self>,
event_name: &str,
handler: impl FnMut(JsValue) + 'static,
) -> Closure<dyn FnMut(JsValue)> {
let closure = Closure::<dyn FnMut(JsValue)>::new(handler);
self.canvas
.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())
.ok();
closure
}
/// Registers a listener with `{passive: false}` so that `preventDefault()` works.
/// Needed for events like `wheel` which are passive by default in modern browsers.
fn listen_non_passive(
self: &Rc<Self>,
event_name: &str,
handler: impl FnMut(JsValue) + 'static,
) -> Closure<dyn FnMut(JsValue)> {
let closure = Closure::<dyn FnMut(JsValue)>::new(handler);
let canvas_js: &JsValue = self.canvas.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(canvas_js, &"addEventListener".into()) {
if let Ok(add_fn) = add_fn_val.dyn_into::<js_sys::Function>() {
add_fn
.call3(canvas_js, &event_name.into(), callback_js, &options)
.ok();
}
}
closure
}
fn dispatch_input(&self, input: PlatformInput) {
let mut borrowed = self.callbacks.borrow_mut();
if let Some(ref mut callback) = borrowed.input {
callback(input);
}
}
fn register_pointer_down(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("pointerdown", move |event: JsValue| {
let event: web_sys::PointerEvent = event.unchecked_into();
event.prevent_default();
this.canvas.focus().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>) -> Closure<dyn FnMut(JsValue)> {
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>) -> Closure<dyn FnMut(JsValue)> {
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>) -> Closure<dyn FnMut(JsValue)> {
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;
}
this.dispatch_input(PlatformInput::MouseExited(MouseExitEvent {
position,
pressed_button: current_pressed,
modifiers,
}));
})
}
fn register_wheel(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
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>) -> Closure<dyn FnMut(JsValue)> {
self.listen("contextmenu", move |event: JsValue| {
let event: web_sys::Event = event.unchecked_into();
event.prevent_default();
})
}
fn register_dragover(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("dragover", move |event: JsValue| {
let event: web_sys::DragEvent = event.unchecked_into();
event.prevent_default();
let mouse_event: &web_sys::MouseEvent = event.as_ref();
let position = mouse_position_in_element(mouse_event);
{
let mut current_state = this.state.borrow_mut();
current_state.mouse_position = position;
}
this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Pending { position }));
})
}
fn register_drop(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("drop", move |event: JsValue| {
let event: web_sys::DragEvent = event.unchecked_into();
event.prevent_default();
let mouse_event: &web_sys::MouseEvent = event.as_ref();
let position = mouse_position_in_element(mouse_event);
{
let mut current_state = this.state.borrow_mut();
current_state.mouse_position = position;
}
let paths = extract_file_paths_from_drag(&event);
this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Entered {
position,
paths: ExternalPaths(paths),
}));
this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Submit { position }));
})
}
fn register_dragleave(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("dragleave", move |_event: JsValue| {
this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Exited));
})
}
fn register_key_down(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("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);
{
let mut current_state = this.state.borrow_mut();
current_state.modifiers = modifiers;
current_state.capslock = capslock;
}
this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers,
capslock,
}));
let key = dom_key_to_gpui_key(&event);
if is_modifier_only_key(&key) {
return;
}
event.prevent_default();
let is_held = event.repeat();
let key_char = compute_key_char(&event, &key, &modifiers);
let keystroke = Keystroke {
modifiers,
key,
key_char,
};
this.dispatch_input(PlatformInput::KeyDown(KeyDownEvent {
keystroke,
is_held,
prefer_character_input: false,
}));
})
}
fn register_key_up(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("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);
{
let mut current_state = this.state.borrow_mut();
current_state.modifiers = modifiers;
current_state.capslock = capslock;
}
this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers,
capslock,
}));
let key = dom_key_to_gpui_key(&event);
if is_modifier_only_key(&key) {
return;
}
event.prevent_default();
let key_char = compute_key_char(&event, &key, &modifiers);
let keystroke = Keystroke {
modifiers,
key,
key_char,
};
this.dispatch_input(PlatformInput::KeyUp(KeyUpEvent { keystroke }));
})
}
fn register_focus(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("focus", move |_event: JsValue| {
{
let mut state = this.state.borrow_mut();
state.is_active = true;
}
let mut callbacks = this.callbacks.borrow_mut();
if let Some(ref mut callback) = callbacks.active_status_change {
callback(true);
}
})
}
fn register_blur(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("blur", move |_event: JsValue| {
{
let mut state = this.state.borrow_mut();
state.is_active = false;
}
let mut callbacks = this.callbacks.borrow_mut();
if let Some(ref mut callback) = callbacks.active_status_change {
callback(false);
}
})
}
fn register_pointer_enter(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("pointerenter", move |_event: JsValue| {
{
let mut state = this.state.borrow_mut();
state.is_hovered = true;
}
let mut callbacks = this.callbacks.borrow_mut();
if let Some(ref mut callback) = callbacks.hover_status_change {
callback(true);
}
})
}
fn register_pointer_leave_hover(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> {
let this = Rc::clone(self);
self.listen("pointerleave", move |_event: JsValue| {
{
let mut state = this.state.borrow_mut();
state.is_hovered = false;
}
let mut callbacks = this.callbacks.borrow_mut();
if let Some(ref mut callback) = callbacks.hover_status_change {
callback(false);
}
})
}
}
fn dom_key_to_gpui_key(event: &web_sys::KeyboardEvent) -> String {
let key = event.key();
match key.as_str() {
"Enter" => "enter".to_string(),
"Backspace" => "backspace".to_string(),
"Tab" => "tab".to_string(),
"Escape" => "escape".to_string(),
"Delete" => "delete".to_string(),
" " => "space".to_string(),
"ArrowLeft" => "left".to_string(),
"ArrowRight" => "right".to_string(),
"ArrowUp" => "up".to_string(),
"ArrowDown" => "down".to_string(),
"Home" => "home".to_string(),
"End" => "end".to_string(),
"PageUp" => "pageup".to_string(),
"PageDown" => "pagedown".to_string(),
"Insert" => "insert".to_string(),
"Control" => "control".to_string(),
"Alt" => "alt".to_string(),
"Shift" => "shift".to_string(),
"Meta" => "platform".to_string(),
"CapsLock" => "capslock".to_string(),
other => {
if let Some(rest) = other.strip_prefix('F') {
if let Ok(number) = rest.parse::<u8>() {
if (1..=35).contains(&number) {
return format!("f{number}");
}
}
}
other.to_lowercase()
}
}
}
fn dom_mouse_button_to_gpui(button: i16) -> MouseButton {
match button {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
3 => MouseButton::Navigate(NavigationDirection::Back),
4 => MouseButton::Navigate(NavigationDirection::Forward),
_ => MouseButton::Left,
}
Lines 1–500 of 615. Load more
use std::rc::Rc; use gpui::{ Capslock, ExternalPaths, FileDropEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, point, px, }; use smallvec::smallvec; use wasm_bindgen::prelude::*; use crate::window::WebWindowInner; pub struct WebEventListeners { #[allow(dead_code)] closures: Vec<Closure<dyn FnMut(JsValue)>>, } 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 closures = 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_dragleave(), self.register_key_down(), self.register_key_up(), self.register_focus(), self.register_blur(), self.register_pointer_enter(), self.register_pointer_leave_hover(), ]; closures.extend(self.register_visibility_change()); closures.extend(self.register_appearance_change()); WebEventListeners { closures } } fn listen( self: &Rc<Self>, event_name: &str, handler: impl FnMut(JsValue) + 'static, ) -> Closure<dyn FnMut(JsValue)> { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); self.canvas .add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref()) .ok(); closure } /// Registers a listener with `{passive: false}` so that `preventDefault()` works. /// Needed for events like `wheel` which are passive by default in modern browsers. fn listen_non_passive( self: &Rc<Self>, event_name: &str, handler: impl FnMut(JsValue) + 'static, ) -> Closure<dyn FnMut(JsValue)> { let closure = Closure::<dyn FnMut(JsValue)>::new(handler); let canvas_js: &JsValue = self.canvas.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(canvas_js, &"addEventListener".into()) { if let Ok(add_fn) = add_fn_val.dyn_into::<js_sys::Function>() { add_fn .call3(canvas_js, &event_name.into(), callback_js, &options) .ok(); } } closure } fn dispatch_input(&self, input: PlatformInput) { let mut borrowed = self.callbacks.borrow_mut(); if let Some(ref mut callback) = borrowed.input { callback(input); } } fn register_pointer_down(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("pointerdown", move |event: JsValue| { let event: web_sys::PointerEvent = event.unchecked_into(); event.prevent_default(); this.canvas.focus().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>) -> Closure<dyn FnMut(JsValue)> { 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>) -> Closure<dyn FnMut(JsValue)> { 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>) -> Closure<dyn FnMut(JsValue)> { 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; } this.dispatch_input(PlatformInput::MouseExited(MouseExitEvent { position, pressed_button: current_pressed, modifiers, })); }) } fn register_wheel(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { 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>) -> Closure<dyn FnMut(JsValue)> { self.listen("contextmenu", move |event: JsValue| { let event: web_sys::Event = event.unchecked_into(); event.prevent_default(); }) } fn register_dragover(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("dragover", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); let mouse_event: &web_sys::MouseEvent = event.as_ref(); let position = mouse_position_in_element(mouse_event); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; } this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Pending { position })); }) } fn register_drop(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("drop", move |event: JsValue| { let event: web_sys::DragEvent = event.unchecked_into(); event.prevent_default(); let mouse_event: &web_sys::MouseEvent = event.as_ref(); let position = mouse_position_in_element(mouse_event); { let mut current_state = this.state.borrow_mut(); current_state.mouse_position = position; } let paths = extract_file_paths_from_drag(&event); this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Entered { position, paths: ExternalPaths(paths), })); this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Submit { position })); }) } fn register_dragleave(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("dragleave", move |_event: JsValue| { this.dispatch_input(PlatformInput::FileDrop(FileDropEvent::Exited)); }) } fn register_key_down(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("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); { let mut current_state = this.state.borrow_mut(); current_state.modifiers = modifiers; current_state.capslock = capslock; } this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } event.prevent_default(); let is_held = event.repeat(); let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char, }; this.dispatch_input(PlatformInput::KeyDown(KeyDownEvent { keystroke, is_held, prefer_character_input: false, })); }) } fn register_key_up(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("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); { let mut current_state = this.state.borrow_mut(); current_state.modifiers = modifiers; current_state.capslock = capslock; } this.dispatch_input(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers, capslock, })); let key = dom_key_to_gpui_key(&event); if is_modifier_only_key(&key) { return; } event.prevent_default(); let key_char = compute_key_char(&event, &key, &modifiers); let keystroke = Keystroke { modifiers, key, key_char, }; this.dispatch_input(PlatformInput::KeyUp(KeyUpEvent { keystroke })); }) } fn register_focus(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("focus", move |_event: JsValue| { { let mut state = this.state.borrow_mut(); state.is_active = true; } let mut callbacks = this.callbacks.borrow_mut(); if let Some(ref mut callback) = callbacks.active_status_change { callback(true); } }) } fn register_blur(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("blur", move |_event: JsValue| { { let mut state = this.state.borrow_mut(); state.is_active = false; } let mut callbacks = this.callbacks.borrow_mut(); if let Some(ref mut callback) = callbacks.active_status_change { callback(false); } }) } fn register_pointer_enter(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("pointerenter", move |_event: JsValue| { { let mut state = this.state.borrow_mut(); state.is_hovered = true; } let mut callbacks = this.callbacks.borrow_mut(); if let Some(ref mut callback) = callbacks.hover_status_change { callback(true); } }) } fn register_pointer_leave_hover(self: &Rc<Self>) -> Closure<dyn FnMut(JsValue)> { let this = Rc::clone(self); self.listen("pointerleave", move |_event: JsValue| { { let mut state = this.state.borrow_mut(); state.is_hovered = false; } let mut callbacks = this.callbacks.borrow_mut(); if let Some(ref mut callback) = callbacks.hover_status_change { callback(false); } }) } } fn dom_key_to_gpui_key(event: &web_sys::KeyboardEvent) -> String { let key = event.key(); match key.as_str() { "Enter" => "enter".to_string(), "Backspace" => "backspace".to_string(), "Tab" => "tab".to_string(), "Escape" => "escape".to_string(), "Delete" => "delete".to_string(), " " => "space".to_string(), "ArrowLeft" => "left".to_string(), "ArrowRight" => "right".to_string(), "ArrowUp" => "up".to_string(), "ArrowDown" => "down".to_string(), "Home" => "home".to_string(), "End" => "end".to_string(), "PageUp" => "pageup".to_string(), "PageDown" => "pagedown".to_string(), "Insert" => "insert".to_string(), "Control" => "control".to_string(), "Alt" => "alt".to_string(), "Shift" => "shift".to_string(), "Meta" => "platform".to_string(), "CapsLock" => "capslock".to_string(), other => { if let Some(rest) = other.strip_prefix('F') { if let Ok(number) = rest.parse::<u8>() { if (1..=35).contains(&number) { return format!("f{number}"); } } } other.to_lowercase() } } } fn dom_mouse_button_to_gpui(button: i16) -> MouseButton { match button { 0 => MouseButton::Left, 1 => MouseButton::Middle, 2 => MouseButton::Right, 3 => MouseButton::Navigate(NavigationDirection::Back), 4 => MouseButton::Navigate(NavigationDirection::Forward), _ => MouseButton::Left, }