Add new Ime event for desktop platforms

This commit brings new Ime event to account for preedit state of input
method, also adding `Window::set_ime_allowed` to toggle IME input on
the particular window.

This commit implements API as designed in #1497 for desktop platforms.

Co-authored-by: Artur Kovacs <kovacs.artur.barnabas@gmail.com>
Co-authored-by: Markus Siglreithmaier <m.siglreith@gmail.com>
Co-authored-by: Murarth <murarth@gmail.com>
Co-authored-by: Yusuke Kominami <yukke.konan@gmail.com>
Co-authored-by: moko256 <koutaro.mo@gmail.com>
This commit is contained in:
Kirill Chibisov 2022-05-07 05:29:25 +03:00 committed by GitHub
parent b4175c1454
commit f04fa5d54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1346 additions and 311 deletions

View file

@ -32,10 +32,9 @@ mod sink;
mod state;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
use sink::EventSink;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
pub struct EventLoopWindowTarget<T> {

View file

@ -5,11 +5,11 @@ use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input
Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::WindowEvent;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::event_loop::WinitState;
use super::{TextInputHandler, TextInputInner};
use super::{Preedit, TextInputHandler, TextInputInner};
#[inline]
pub(super) fn handle_text_input(
@ -30,8 +30,11 @@ pub(super) fn handle_text_input(
inner.target_window_id = Some(window_id);
// Enable text input on that surface.
text_input.enable();
text_input.commit();
if window_handle.ime_allowed.get() {
text_input.enable();
text_input.commit();
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
// Notify a window we're currently over about text input handler.
let text_input_handler = TextInputHandler {
@ -58,19 +61,45 @@ pub(super) fn handle_text_input(
text_input: text_input.detach(),
};
window_handle.text_input_left(text_input_handler);
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
let cursor_begin = usize::try_from(cursor_begin).ok();
let cursor_end = usize::try_from(cursor_end).ok();
let text = text.unwrap_or_default();
inner.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
});
}
TextInputEvent::CommitString { text } => {
// Update currenly commited string.
inner.commit_string = text;
// Update currenly commited string and reset previous preedit.
inner.pending_preedit = None;
inner.pending_commit = Some(text.unwrap_or_default());
}
TextInputEvent::Done { .. } => {
let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) {
(Some(window_id), Some(text)) => (window_id, text),
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
_ => return,
};
for ch in text.chars() {
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
if let Some(text) = inner.pending_commit.take() {
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Push preedit string we've got after latest commit.
if let Some(preedit) = inner.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
let event = Ime::Preedit(preedit.text, cursor_range);
event_sink.push_window_event(WindowEvent::Ime(event), window_id);
}
}
_ => (),

View file

@ -20,6 +20,17 @@ impl TextInputHandler {
self.text_input.set_cursor_rectangle(x, y, 0, 0);
self.text_input.commit();
}
#[inline]
pub fn set_input_allowed(&self, allowed: bool) {
if allowed {
self.text_input.enable();
} else {
self.text_input.disable();
}
self.text_input.commit();
}
}
/// A wrapper around text input to automatically destroy the object on `Drop`.
@ -52,15 +63,25 @@ struct TextInputInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
/// Pending string to commit.
commit_string: Option<String>,
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
pending_commit: Option<String>,
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
pending_preedit: Option<Preedit>,
}
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
impl TextInputInner {
fn new() -> Self {
Self {
target_window_id: None,
commit_string: None,
pending_commit: None,
pending_preedit: None,
}
}
}

View file

@ -496,7 +496,12 @@ impl Window {
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::IMEPosition(position));
self.send_request(WindowRequest::ImePosition(position));
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
self.send_request(WindowRequest::AllowIme(allowed));
}
#[inline]

View file

@ -12,10 +12,10 @@ use sctk::window::{Decorations, FallbackFrame, Window};
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::WindowEvent;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
@ -69,7 +69,10 @@ pub enum WindowRequest {
FrameSize(LogicalSize<u32>),
/// Set IME window position.
IMEPosition(LogicalPosition<u32>),
ImePosition(LogicalPosition<u32>),
/// Enable IME on the given window.
AllowIme(bool),
/// Request Attention.
///
@ -157,6 +160,9 @@ pub struct WindowHandle {
/// Whether the window is resizable.
pub is_resizable: Cell<bool>,
/// Allow IME events for that window.
pub ime_allowed: Cell<bool>,
/// Visible cursor or not.
cursor_visible: Cell<bool>,
@ -204,6 +210,7 @@ impl WindowHandle {
xdg_activation,
attention_requested: Cell::new(false),
compositor,
ime_allowed: Cell::new(false),
}
}
@ -333,6 +340,27 @@ impl WindowHandle {
}
}
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
if self.ime_allowed.get() == allowed {
return;
}
self.ime_allowed.replace(allowed);
let window_id = wayland::make_wid(self.window.surface());
for text_input in self.text_inputs.iter() {
text_input.set_input_allowed(allowed);
}
let event = if allowed {
WindowEvent::Ime(Ime::Enabled)
} else {
WindowEvent::Ime(Ime::Disabled)
};
event_sink.push_window_event(event, window_id);
}
pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible);
let cursor_icon = match visible {
@ -387,9 +415,13 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
WindowRequest::NewCursorIcon(cursor_icon) => {
window_handle.set_cursor_icon(cursor_icon);
}
WindowRequest::IMEPosition(position) => {
WindowRequest::ImePosition(position) => {
window_handle.set_ime_position(position);
}
WindowRequest::AllowIme(allow) => {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::GrabCursor(grab) => {
window_handle.set_cursor_grab(grab);
}