- Custom handling of dead keys has been removed. UX for dead keys is now the same as other applications on Windows. - We could bring back some kind of custom UI, but only if UX is fully compatible with expected Windows behavior (e.g. ability to move the cursor after typing a dead key). - Fixes https://github.com/zed-industries/zed/issues/38838 - Character input via AltGr shift state now always has priority over keybindings. This applies regardless of whether the keystroke used the AltGr key or Ctrl+Alt to enter the shift state. - In particular, we use the following heuristic to determine whether a keystroke should trigger character input first or trigger keybindings first: - If the keystroke does not have any of Ctrl/Alt/Win down, trigger keybindings first. - Otherwise, determine the character that would be entered by the keystroke. If it is a control character, or no character at all, trigger keybindings first. - Otherwise, the keystroke has _any_ of Ctrl/Alt/Win down and generates a printable character. Compare this character against the character that would be generated if the keystroke had _none_ of Ctrl/Alt/Win down: - If the character is the same, the modifiers are not significant; trigger keybindings first. - If there is no active input handler, or the active input handler indicates that it isn't accepting text input (e.g. when an operator is pending in Vim mode), character entry is not useful; trigger keybindings first. - Otherwise, assume the modifiers enable access to an otherwise difficult-to-enter key; trigger character entry first. - Fixes https://github.com/zed-industries/zed/issues/35862 - Fixes https://github.com/zed-industries/zed/issues/40054#issuecomment-3447833349 - Fixes https://github.com/zed-industries/zed/issues/41486 - TranslateMessage calls are no longer skipped for unhandled keystrokes. This fixes language input keys on Japanese and Korean keyboards (and surely other cases as well). - To avoid any other missing-TranslateMessage headaches in the future, the message loop has been rewritten in a "traditional" Win32 style, where accelerators are handled in the message loop and TranslateMessage is called in the intended manner. - Fixes https://github.com/zed-industries/zed/issues/39971 - Fixes https://github.com/zed-industries/zed/issues/40300 - Fixes https://github.com/zed-industries/zed/issues/40321 - Fixes https://github.com/zed-industries/zed/issues/40335 - Fixes https://github.com/zed-industries/zed/issues/40592 - Fixes https://github.com/zed-industries/zed/issues/40638 - As a bonus, Alt+Space now opens the system menu, since it is triggered by the WM_SYSCHAR generated by TranslateMessage. - VK_PROCESSKEYs are now ignored rather than being unwrapped and matched against keybindings. This ensures that IMEs will reliably receieve keystrokes that they express interest in. This matches the behavior of native Windows applications. - Fixes https://github.com/zed-industries/zed/issues/36736 - Fixes https://github.com/zed-industries/zed/issues/39608 - Fixes https://github.com/zed-industries/zed/issues/39991 - Fixes https://github.com/zed-industries/zed/issues/41223 - Fixes https://github.com/zed-industries/zed/issues/41656 - Fixes https://github.com/zed-industries/zed/issues/34180 - Fixes https://github.com/zed-industries/zed/issues/41766 Release Notes: - windows: Improved keyboard input handling for international keyboard layouts and IMEs
191 lines
5.7 KiB
Rust
191 lines
5.7 KiB
Rust
use crate::{App, Bounds, Context, Entity, InputHandler, Pixels, UTF16Selection, Window};
|
|
use std::ops::Range;
|
|
|
|
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
|
|
///
|
|
/// Once your view implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
|
|
/// This input handler can then be assigned during paint by calling [`Window::handle_input`].
|
|
///
|
|
/// See [`InputHandler`] for details on how to implement each method.
|
|
pub trait EntityInputHandler: 'static + Sized {
|
|
/// See [`InputHandler::text_for_range`] for details
|
|
fn text_for_range(
|
|
&mut self,
|
|
range: Range<usize>,
|
|
adjusted_range: &mut Option<Range<usize>>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<String>;
|
|
|
|
/// See [`InputHandler::selected_text_range`] for details
|
|
fn selected_text_range(
|
|
&mut self,
|
|
ignore_disabled_input: bool,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<UTF16Selection>;
|
|
|
|
/// See [`InputHandler::marked_text_range`] for details
|
|
fn marked_text_range(
|
|
&self,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<Range<usize>>;
|
|
|
|
/// See [`InputHandler::unmark_text`] for details
|
|
fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
|
|
|
|
/// See [`InputHandler::replace_text_in_range`] for details
|
|
fn replace_text_in_range(
|
|
&mut self,
|
|
range: Option<Range<usize>>,
|
|
text: &str,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
);
|
|
|
|
/// See [`InputHandler::replace_and_mark_text_in_range`] for details
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
);
|
|
|
|
/// See [`InputHandler::bounds_for_range`] for details
|
|
fn bounds_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
element_bounds: Bounds<Pixels>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<Bounds<Pixels>>;
|
|
|
|
/// See [`InputHandler::character_index_for_point`] for details
|
|
fn character_index_for_point(
|
|
&mut self,
|
|
point: crate::Point<Pixels>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<usize>;
|
|
|
|
/// See [`InputHandler::accepts_text_input`] for details
|
|
fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
/// The canonical implementation of [`crate::PlatformInputHandler`]. Call [`Window::handle_input`]
|
|
/// with an instance during your element's paint.
|
|
pub struct ElementInputHandler<V> {
|
|
view: Entity<V>,
|
|
element_bounds: Bounds<Pixels>,
|
|
}
|
|
|
|
impl<V: 'static> ElementInputHandler<V> {
|
|
/// Used in [`Element::paint`][element_paint] with the element's bounds, a `Window`, and a `App` context.
|
|
///
|
|
/// [element_paint]: crate::Element::paint
|
|
pub fn new(element_bounds: Bounds<Pixels>, view: Entity<V>) -> Self {
|
|
ElementInputHandler {
|
|
view,
|
|
element_bounds,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
|
|
fn selected_text_range(
|
|
&mut self,
|
|
ignore_disabled_input: bool,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Option<UTF16Selection> {
|
|
self.view.update(cx, |view, cx| {
|
|
view.selected_text_range(ignore_disabled_input, window, cx)
|
|
})
|
|
}
|
|
|
|
fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>> {
|
|
self.view
|
|
.update(cx, |view, cx| view.marked_text_range(window, cx))
|
|
}
|
|
|
|
fn text_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
adjusted_range: &mut Option<Range<usize>>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Option<String> {
|
|
self.view.update(cx, |view, cx| {
|
|
view.text_for_range(range_utf16, adjusted_range, window, cx)
|
|
})
|
|
}
|
|
|
|
fn replace_text_in_range(
|
|
&mut self,
|
|
replacement_range: Option<Range<usize>>,
|
|
text: &str,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) {
|
|
self.view.update(cx, |view, cx| {
|
|
view.replace_text_in_range(replacement_range, text, window, cx)
|
|
});
|
|
}
|
|
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range_utf16: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) {
|
|
self.view.update(cx, |view, cx| {
|
|
view.replace_and_mark_text_in_range(
|
|
range_utf16,
|
|
new_text,
|
|
new_selected_range,
|
|
window,
|
|
cx,
|
|
)
|
|
});
|
|
}
|
|
|
|
fn unmark_text(&mut self, window: &mut Window, cx: &mut App) {
|
|
self.view
|
|
.update(cx, |view, cx| view.unmark_text(window, cx));
|
|
}
|
|
|
|
fn bounds_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Option<Bounds<Pixels>> {
|
|
self.view.update(cx, |view, cx| {
|
|
view.bounds_for_range(range_utf16, self.element_bounds, window, cx)
|
|
})
|
|
}
|
|
|
|
fn character_index_for_point(
|
|
&mut self,
|
|
point: crate::Point<Pixels>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Option<usize> {
|
|
self.view.update(cx, |view, cx| {
|
|
view.character_index_for_point(point, window, cx)
|
|
})
|
|
}
|
|
|
|
fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
|
|
self.view
|
|
.update(cx, |view, cx| view.accepts_text_input(window, cx))
|
|
}
|
|
}
|