winit-core: add Ime::DeleteSurroundingText API
This completes the basic API required for e.g. Wayland.
This commit is contained in:
parent
120f21a010
commit
d7fdfb1bca
4 changed files with 81 additions and 5 deletions
|
|
@ -9,7 +9,7 @@ use std::sync::mpsc::{self, Receiver, Sender};
|
|||
use std::sync::Arc;
|
||||
#[cfg(all(not(android_platform), not(web_platform)))]
|
||||
use std::time::Instant;
|
||||
use std::{fmt, mem};
|
||||
use std::{cmp, fmt, mem};
|
||||
|
||||
use ::tracing::{error, info};
|
||||
use cursor_icon::CursorIcon;
|
||||
|
|
@ -525,6 +525,26 @@ impl ApplicationHandler for Application {
|
|||
let request_data = window.get_ime_update();
|
||||
window.window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
|
||||
},
|
||||
Ime::DeleteSurrounding { before_bytes, after_bytes } => {
|
||||
let (text, cursor) = &window.text_field_contents;
|
||||
|
||||
// To anyone copying this, keep in mind that this doesn't take text selection
|
||||
// into account. The deletion happens *around* the pre-edit,
|
||||
// and may remove the whole selection or a part of it.
|
||||
let delete_start = cursor.saturating_sub(before_bytes);
|
||||
let delete_end = cmp::min(cursor.saturating_add(after_bytes), text.len());
|
||||
if text.is_char_boundary(delete_start) && text.is_char_boundary(delete_end) {
|
||||
let new_text = {
|
||||
let mut t = String::from(&text[..delete_start]);
|
||||
t.push_str(&text[delete_end..]);
|
||||
t
|
||||
};
|
||||
window.text_field_contents = (new_text, delete_start);
|
||||
info!("IME deleted bytes: {before_bytes}, {after_bytes}");
|
||||
} else {
|
||||
error!("Buggy IME tried to delete with indices not on char boundary.");
|
||||
}
|
||||
},
|
||||
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
|
||||
},
|
||||
WindowEvent::PinchGesture { delta, .. } => {
|
||||
|
|
|
|||
|
|
@ -879,6 +879,8 @@ impl From<ModifiersState> for Modifiers {
|
|||
|
||||
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
|
||||
///
|
||||
/// The `Ime` events must be applied in the order they arrive.
|
||||
///
|
||||
/// This is also called a "composition event".
|
||||
///
|
||||
/// Most keypresses using a latin-like keyboard layout simply generate a
|
||||
|
|
@ -935,7 +937,7 @@ pub enum Ime {
|
|||
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
|
||||
/// this indicates that preedit was cleared.
|
||||
///
|
||||
/// The cursor position is byte-wise indexed.
|
||||
/// The cursor position is byte-wise indexed, assuming UTF-8.
|
||||
Preedit(String, Option<(usize, usize)>),
|
||||
|
||||
/// Notifies when text should be inserted into the editor widget.
|
||||
|
|
@ -943,6 +945,20 @@ pub enum Ime {
|
|||
/// Right before this event winit will send empty [`Self::Preedit`] event.
|
||||
Commit(String),
|
||||
|
||||
/// Delete text surrounding the cursor or selection.
|
||||
///
|
||||
/// This event does not affect either the pre-edit string.
|
||||
/// This means that the application must first remove the pre-edit,
|
||||
/// then execute the deletion, then insert the removed text back.
|
||||
///
|
||||
/// This event assumes text is stored in UTF-8.
|
||||
DeleteSurrounding {
|
||||
/// Bytes to remove before the selection
|
||||
before_bytes: usize,
|
||||
/// Bytes to remove after the selection
|
||||
after_bytes: usize,
|
||||
},
|
||||
|
||||
/// Notifies when the IME was disabled.
|
||||
///
|
||||
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
|
||||
|
|
|
|||
|
|
@ -115,12 +115,42 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
|||
text_input_data.pending_preedit = None;
|
||||
text_input_data.pending_commit = text;
|
||||
},
|
||||
TextInputEvent::DeleteSurroundingText { before_length, after_length } => {
|
||||
text_input_data.pending_delete = Some(DeleteSurroundingText {
|
||||
before: before_length as usize,
|
||||
after: after_length as usize,
|
||||
});
|
||||
},
|
||||
TextInputEvent::Done { .. } => {
|
||||
let window_id = match text_input_data.surface.as_ref() {
|
||||
Some(surface) => crate::make_wid(surface),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// The events are sent to the user separately, so
|
||||
// CAUTION: events must always arrive in the order compatible with the application
|
||||
// order specified by the text-input-v3 protocol:
|
||||
//
|
||||
// As of version 1:
|
||||
// 1. Replace existing preedit string with the cursor.
|
||||
// 2. Delete requested surrounding text.
|
||||
// 3. Insert commit string with the cursor at its end.
|
||||
// 4. Calculate surrounding text to send.
|
||||
// 5. Insert new preedit text in cursor position.
|
||||
// 6. Place cursor inside preedit text.
|
||||
|
||||
if let Some(DeleteSurroundingText { before, after }) =
|
||||
text_input_data.pending_delete
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::DeleteSurrounding {
|
||||
before_bytes: before,
|
||||
after_bytes: after,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Clear preedit, unless all we'll be doing next is sending a new preedit.
|
||||
if text_input_data.pending_commit.is_some()
|
||||
|| text_input_data.pending_preedit.is_none()
|
||||
|
|
@ -149,9 +179,6 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
|||
);
|
||||
}
|
||||
},
|
||||
TextInputEvent::DeleteSurroundingText { .. } => {
|
||||
// Not handled.
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +246,9 @@ pub struct TextInputDataInner {
|
|||
|
||||
/// The preedit to submit on `done`.
|
||||
pending_preedit: Option<Preedit>,
|
||||
|
||||
/// The text around the cursor to delete on `done`
|
||||
pending_delete: Option<DeleteSurroundingText>,
|
||||
}
|
||||
|
||||
/// The state of the preedit.
|
||||
|
|
@ -229,6 +259,15 @@ struct Preedit {
|
|||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
/// The delete request
|
||||
#[derive(Clone)]
|
||||
struct DeleteSurroundingText {
|
||||
/// Bytes before cursor
|
||||
before: usize,
|
||||
/// Bytes after cursor
|
||||
after: usize,
|
||||
}
|
||||
|
||||
/// State change requested by the application.
|
||||
///
|
||||
/// This is a version that uses text_input abstractions translated from the ones used in
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ changelog entry.
|
|||
- Each platform now has corresponding `WindowAttributes` struct instead of trait extension.
|
||||
- On Wayland, added implementation for `Window::set_window_icon`
|
||||
- Add `Window::request_ime_update` to atomically apply set of IME changes.
|
||||
- Add `Ime::DeleteSurrounding` to let the input method delete text.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue