gpui_web: Tighten up web keyboard feel (#63533)
Fixes a few edge cases where we weren't correctly tracking whether to hide the keyboard when touch events happen Also forces a synchronous render on web after the viewport resizes (which happens when the keyboard appears/disappears). This prevents a 1 frame delay where the browser would use the old bitmap, stretched to the new size --- Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
1 parent
ce48461eaa
commit
a66fb6aebd
4 files changed
+88
-14
No files matched your search
@@ -3309,13 +3309,14 @@ impl Interactivity {
|
||||
if let Some(group_hover) = self.group_hover_style.as_ref() {
|
||||
let is_group_hovered =
|
||||
if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
|
||||
group_hitbox_id.is_hovered(window)
|
||||
!window.last_input_was_touch() && group_hitbox_id.is_hovered(window)
|
||||
} else if let Some(element_state) = element_state.as_ref() {
|
||||
element_state
|
||||
.hover_state
|
||||
.as_ref()
|
||||
.map(|state| state.borrow().group)
|
||||
.unwrap_or(false)
|
||||
!window.last_input_was_touch()
|
||||
&& element_state
|
||||
.hover_state
|
||||
.as_ref()
|
||||
.map(|state| state.borrow().group)
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -3327,13 +3328,14 @@ impl Interactivity {
|
||||
|
||||
if let Some(hover_style) = self.hover_style.as_ref() {
|
||||
let is_hovered = if let Some(hitbox) = hitbox {
|
||||
hitbox.is_hovered(window)
|
||||
!window.last_input_was_touch() && hitbox.is_hovered(window)
|
||||
} else if let Some(element_state) = element_state.as_ref() {
|
||||
element_state
|
||||
.hover_state
|
||||
.as_ref()
|
||||
.map(|state| state.borrow().element)
|
||||
.unwrap_or(false)
|
||||
!window.last_input_was_touch()
|
||||
&& element_state
|
||||
.hover_state
|
||||
.as_ref()
|
||||
.map(|state| state.borrow().element)
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
@@ -2849,6 +2849,10 @@ impl Window {
|
||||
self.last_input_modality == InputModality::Keyboard
|
||||
}
|
||||
|
||||
pub(crate) fn last_input_was_touch(&self) -> bool {
|
||||
self.last_input_modality == InputModality::Touch
|
||||
}
|
||||
|
||||
/// The current state of the keyboard's capslock
|
||||
pub fn capslock(&self) -> Capslock {
|
||||
self.capslock
|
||||
|
||||
@@ -264,6 +264,11 @@ impl WebWindowInner {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn focused_input_accepts_text(&self) -> bool {
|
||||
self.with_input_handler(|handler| handler.query_accepts_text_input())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn register_pointer_up(self: &Rc<Self>) -> EventListenerHandle {
|
||||
let this = Rc::clone(self);
|
||||
self.listen("pointerup", move |event: JsValue| {
|
||||
@@ -281,10 +286,11 @@ impl WebWindowInner {
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let focused_input_accepted_text_before_tap = this.focused_input_accepts_text();
|
||||
// A recognized tap is dispatched synchronously inside this
|
||||
// call, so the text-input check below sees the state the tap
|
||||
// produced.
|
||||
this.dispatch_input(PlatformInput::Touch(TouchEvent {
|
||||
let dispatch_result = this.dispatch_input(PlatformInput::Touch(TouchEvent {
|
||||
id: TouchId(event.pointer_id() as u64),
|
||||
phase: TouchPhase::Ended,
|
||||
position,
|
||||
@@ -302,7 +308,14 @@ impl WebWindowInner {
|
||||
let viewport_stable = this.gesture_start_visual_viewport_height.get()
|
||||
== this.visual_viewport_height();
|
||||
if completes_tap && viewport_stable {
|
||||
this.sync_virtual_keyboard(this.pointer_targets_text_input(position));
|
||||
let preserve_focused_input = should_preserve_focused_input(
|
||||
focused_input_accepted_text_before_tap,
|
||||
this.focused_input_accepts_text(),
|
||||
dispatch_result,
|
||||
);
|
||||
if !preserve_focused_input {
|
||||
this.sync_virtual_keyboard(this.pointer_targets_text_input(position));
|
||||
}
|
||||
}
|
||||
this.schedule_ime_mirror_sync();
|
||||
return;
|
||||
@@ -1231,6 +1244,16 @@ fn capslock_from_keyboard_event(event: &web_sys::KeyboardEvent) -> Capslock {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_preserve_focused_input(
|
||||
accepted_text_before_tap: bool,
|
||||
accepts_text_after_tap: bool,
|
||||
dispatch_result: Option<DispatchEventResult>,
|
||||
) -> bool {
|
||||
accepted_text_before_tap
|
||||
&& accepts_text_after_tap
|
||||
&& dispatch_result.is_some_and(|result| result.default_prevented)
|
||||
}
|
||||
|
||||
pub(crate) fn is_mac_platform(browser_window: &web_sys::Window) -> bool {
|
||||
let navigator = browser_window.navigator();
|
||||
|
||||
@@ -1364,3 +1387,34 @@ fn mouse_position_in_element(event: &web_sys::MouseEvent) -> Point<Pixels> {
|
||||
// offset_x/offset_y give position relative to the target element's padding edge
|
||||
point(px(event.offset_x() as f32), px(event.offset_y() as f32))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn handled_tap_preserves_unchanged_text_input() {
|
||||
assert!(should_preserve_focused_input(
|
||||
true,
|
||||
true,
|
||||
Some(DispatchEventResult {
|
||||
propagate: false,
|
||||
default_prevented: true,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tap_does_not_preserve_unhandled_or_unfocused_input() {
|
||||
let result = |default_prevented| {
|
||||
Some(DispatchEventResult {
|
||||
propagate: false,
|
||||
default_prevented,
|
||||
})
|
||||
};
|
||||
|
||||
assert!(!should_preserve_focused_input(true, true, result(false),));
|
||||
assert!(!should_preserve_focused_input(false, true, result(true),));
|
||||
assert!(!should_preserve_focused_input(true, false, result(true),));
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,20 @@ impl WebWindow {
|
||||
|callbacks| &mut callbacks.resize,
|
||||
|callback| callback(new_size, dpr_f32),
|
||||
);
|
||||
|
||||
// ResizeObserver runs after layout but before the browser paints.
|
||||
// Render synchronously here so the newly resized CSS canvas is
|
||||
// never presented with its previous backing image stretched into
|
||||
// the new viewport dimensions.
|
||||
inner.with_callback(
|
||||
|callbacks| &mut callbacks.request_frame,
|
||||
|callback| {
|
||||
callback(RequestFrameOptions {
|
||||
require_presentation: true,
|
||||
force_render: true,
|
||||
})
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user