winit-core: add surrounding_text for IME
Allow communicating surrounding text to IME to better handle user input and account for content around for preedit.
This commit is contained in:
parent
eb66c25980
commit
e7a6034b55
3 changed files with 250 additions and 17 deletions
|
|
@ -40,8 +40,8 @@ use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb};
|
|||
#[cfg(x11_platform)]
|
||||
use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11};
|
||||
use winit::window::{
|
||||
CursorGrabMode, ImeCapabilities, ImeEnableRequest, ImePurpose, ImeRequestData, ResizeDirection,
|
||||
Theme, Window, WindowAttributes, WindowId,
|
||||
CursorGrabMode, ImeCapabilities, ImeEnableRequest, ImePurpose, ImeRequestData,
|
||||
ImeSurroundingText, ResizeDirection, Theme, Window, WindowAttributes, WindowId,
|
||||
};
|
||||
use winit_core::application::macos::ApplicationHandlerExtMacOS;
|
||||
use winit_core::window::ImeRequest;
|
||||
|
|
@ -518,7 +518,12 @@ impl ApplicationHandler for Application {
|
|||
info!("Preedit: {}, with caret at {:?}", text, caret_pos);
|
||||
},
|
||||
Ime::Commit(text) => {
|
||||
window.text_field_contents.0.push_str(&text);
|
||||
window.text_field_contents.1 += text.len();
|
||||
|
||||
info!("Committed: {}", text);
|
||||
let request_data = window.get_ime_update();
|
||||
window.window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
|
||||
},
|
||||
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
|
||||
},
|
||||
|
|
@ -611,6 +616,9 @@ impl ApplicationHandlerExtMacOS for Application {
|
|||
/// State of the window.
|
||||
struct WindowState {
|
||||
ime_enabled: bool,
|
||||
/// The contents of the emulated text field for IME purposes (not displayed).
|
||||
/// (text, cursor position in bytes).
|
||||
text_field_contents: (String, usize),
|
||||
/// Render surface.
|
||||
///
|
||||
/// NOTE: This surface must be dropped before the `Window`.
|
||||
|
|
@ -668,9 +676,10 @@ impl WindowState {
|
|||
// Allow IME out of the box.
|
||||
let request_data = ImeRequestData::default()
|
||||
.with_purpose(ImePurpose::Normal)
|
||||
.with_cursor_area(LogicalPosition { x: 0, y: 0 }.into(), IME_CURSOR_SIZE.into());
|
||||
.with_cursor_area(LogicalPosition { x: 0, y: 0 }.into(), IME_CURSOR_SIZE.into())
|
||||
.with_surrounding_text(ImeSurroundingText::new(String::new(), 0, 0).unwrap());
|
||||
let enable_request = ImeEnableRequest::new(
|
||||
ImeCapabilities::new().with_purpose().with_cursor_area(),
|
||||
ImeCapabilities::new().with_purpose().with_cursor_area().with_surrounding_text(),
|
||||
request_data,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -695,6 +704,7 @@ impl WindowState {
|
|||
#[cfg(not(android_platform))]
|
||||
start_time: Instant::now(),
|
||||
ime_enabled: true,
|
||||
text_field_contents: (String::new(), 0),
|
||||
cursor_position: Default::default(),
|
||||
cursor_hidden: Default::default(),
|
||||
modifiers: Default::default(),
|
||||
|
|
@ -708,20 +718,42 @@ impl WindowState {
|
|||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn get_ime_update(&self) -> ImeRequestData {
|
||||
let (text, cursor) = &self.text_field_contents;
|
||||
// A rudimentary text field emulation: the caret moves right by a constant amount for each
|
||||
// code point.
|
||||
|
||||
let text_before_caret = if text.is_char_boundary(*cursor) { &text[..*cursor] } else { "" };
|
||||
let chars_before_caret = text_before_caret.chars().count();
|
||||
let cursor_pos = LogicalPosition { x: 10 * chars_before_caret as u32, y: 0 }.into();
|
||||
|
||||
// Limit text field size
|
||||
const MAX_BYTES: usize = ImeSurroundingText::MAX_TEXT_BYTES;
|
||||
let minimal_offset = cursor / MAX_BYTES * MAX_BYTES;
|
||||
let first_char_boundary =
|
||||
(minimal_offset..*cursor).find(|off| text.is_char_boundary(*off)).unwrap_or(*cursor);
|
||||
let last_char_boundary = (*cursor..(first_char_boundary + MAX_BYTES))
|
||||
.rev()
|
||||
.find(|off| text.is_char_boundary(*off))
|
||||
.unwrap_or(*cursor);
|
||||
let surrounding_text = &text[first_char_boundary..last_char_boundary];
|
||||
let relative_cursor = cursor - first_char_boundary;
|
||||
let surrounding_text =
|
||||
ImeSurroundingText::new(surrounding_text.into(), relative_cursor, relative_cursor)
|
||||
.expect("Bug in example: bad byte calculations");
|
||||
ImeRequestData::default()
|
||||
.with_purpose(ImePurpose::Normal)
|
||||
.with_cursor_area(cursor_pos, IME_CURSOR_SIZE.into())
|
||||
.with_surrounding_text(surrounding_text)
|
||||
}
|
||||
|
||||
pub fn toggle_ime(&mut self) {
|
||||
if self.ime_enabled {
|
||||
self.window.request_ime_update(ImeRequest::Disable).expect("disable can not fail");
|
||||
} else {
|
||||
let cursor_pos = self
|
||||
.cursor_position
|
||||
.map(Into::into)
|
||||
.unwrap_or(LogicalPosition { x: 0, y: 0 }.into());
|
||||
let request_data = ImeRequestData::default()
|
||||
.with_purpose(ImePurpose::Normal)
|
||||
.with_cursor_area(cursor_pos, IME_CURSOR_SIZE.into());
|
||||
let enable_request = ImeEnableRequest::new(
|
||||
ImeCapabilities::new().with_purpose().with_cursor_area(),
|
||||
request_data,
|
||||
ImeCapabilities::new().with_purpose().with_cursor_area().with_surrounding_text(),
|
||||
self.get_ime_update(),
|
||||
)
|
||||
.unwrap();
|
||||
self.window.request_ime_update(ImeRequest::Enable(enable_request)).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue