Skip to content

Commit

Permalink
Improve IME support with new Event::Ime (#4358)
Browse files Browse the repository at this point in the history
* Closes #4354 

Fix: can't repeat input chinese words

AND

For Windows :
ImeEnable
ImeDisable

---------

Co-authored-by: Emil Ernerfeldt <[email protected]>
  • Loading branch information
rustbasic and emilk authored Apr 22, 2024
1 parent 587bc20 commit 436c671
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 62 deletions.
13 changes: 8 additions & 5 deletions crates/eframe/src/web/text_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
is_composing.set(true);
input_clone.set_value("");

runner.input.raw.events.push(egui::Event::CompositionStart);
let egui_event = egui::Event::Ime(egui::ImeEvent::Enabled);
runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap();
}
})?;
Expand All @@ -77,8 +78,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
&input,
"compositionupdate",
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
runner.input.raw.events.push(event);
if let Some(text) = event.data() {
let egui_event = egui::Event::Ime(egui::ImeEvent::Preedit(text));
runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap();
}
},
Expand All @@ -91,8 +93,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
is_composing.set(false);
input_clone.set_value("");

if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
runner.input.raw.events.push(event);
if let Some(text) = event.data() {
let egui_event = egui::Event::Ime(egui::ImeEvent::Commit(text));
runner.input.raw.events.push(egui_event);
runner.needs_repaint.repaint_asap();
}
}
Expand Down
45 changes: 31 additions & 14 deletions crates/egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub struct State {
pointer_touch_id: Option<u64>,

/// track ime state
input_method_editor_started: bool,
has_sent_ime_enabled: bool,

#[cfg(feature = "accesskit")]
accesskit: Option<accesskit_winit::Adapter>,
Expand Down Expand Up @@ -136,7 +136,7 @@ impl State {
simulate_touch_screen: false,
pointer_touch_id: None,

input_method_editor_started: false,
has_sent_ime_enabled: false,

#[cfg(feature = "accesskit")]
accesskit: None,
Expand Down Expand Up @@ -342,23 +342,39 @@ impl State {
// We use input_method_editor_started to manually insert CompositionStart
// between Commits.
match ime {
winit::event::Ime::Enabled | winit::event::Ime::Disabled => (),
winit::event::Ime::Commit(text) => {
self.input_method_editor_started = false;
winit::event::Ime::Enabled => {
self.egui_input
.events
.push(egui::Event::CompositionEnd(text.clone()));
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
self.has_sent_ime_enabled = true;
}
winit::event::Ime::Preedit(text, Some(_)) => {
if !self.input_method_editor_started {
self.input_method_editor_started = true;
self.egui_input.events.push(egui::Event::CompositionStart);
winit::event::Ime::Preedit(_, None) => {}
winit::event::Ime::Preedit(text, Some(_cursor)) => {
if !self.has_sent_ime_enabled {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
self.has_sent_ime_enabled = true;
}
self.egui_input
.events
.push(egui::Event::CompositionUpdate(text.clone()));
.push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
}
winit::event::Ime::Commit(text) => {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Disabled));
self.has_sent_ime_enabled = false;
}
winit::event::Ime::Disabled => {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Disabled));
self.has_sent_ime_enabled = false;
}
winit::event::Ime::Preedit(_, None) => {}
};

EventResponse {
Expand Down Expand Up @@ -601,7 +617,8 @@ impl State {
});
// If we're not yet translating a touch or we're translating this very
// touch …
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id {
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
{
// … emit PointerButton resp. PointerMoved events to emulate mouse
match touch.phase {
winit::event::TouchPhase::Started => {
Expand Down Expand Up @@ -1531,7 +1548,7 @@ pub fn create_winit_window_builder<T>(
// We set sizes and positions in egui:s own ui points, which depends on the egui
// zoom_factor and the native pixels per point, so we need to know that here.
// We don't know what monitor the window will appear on though, but
// we'll try to fix that after the window is created in the vall to `apply_viewport_builder_to_window`.
// we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`.
let native_pixels_per_point = event_loop
.primary_monitor()
.or_else(|| event_loop.available_monitors().next())
Expand Down
29 changes: 21 additions & 8 deletions crates/egui/src/data/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,14 +445,8 @@ pub enum Event {
/// * `zoom > 1`: pinch spread
Zoom(f32),

/// IME composition start.
CompositionStart,

/// A new IME candidate is being suggested.
CompositionUpdate(String),

/// IME composition ended with this final result.
CompositionEnd(String),
/// IME Event
Ime(ImeEvent),

/// On touch screens, report this *in addition to*
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
Expand Down Expand Up @@ -507,6 +501,25 @@ pub enum Event {
},
}

/// IME event.
///
/// See <https://docs.rs/winit/latest/winit/event/enum.Ime.html>
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ImeEvent {
/// Notifies when the IME was enabled.
Enabled,

/// A new IME candidate is being suggested.
Preedit(String),

/// IME composition ended with this final result.
Commit(String),

/// Notifies when the IME was disabled.
Disabled,
}

/// Mouse button (or similar for touch input)
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down
72 changes: 38 additions & 34 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,47 +950,51 @@ fn events(
..
} => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key),

Event::CompositionStart => {
state.has_ime = true;
None
}

Event::CompositionUpdate(text_mark) => {
// empty prediction can be produced when user press backspace
// or escape during ime. We should clear current text.
if text_mark != "\n" && text_mark != "\r" && state.has_ime {
let mut ccursor = text.delete_selected(&cursor_range);
let start_cursor = ccursor;
if !text_mark.is_empty() {
text.insert_text_at(&mut ccursor, text_mark, char_limit);
}
Event::Ime(ime_event) => match ime_event {
ImeEvent::Enabled => {
state.ime_enabled = true;
state.ime_cursor_range = cursor_range;
Some(CCursorRange::two(start_cursor, ccursor))
} else {
None
}
}

Event::CompositionEnd(prediction) => {
// CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first,
// so do not check `state.has_ime = true` in the following statement.
if prediction != "\n" && prediction != "\r" {
state.has_ime = false;
let mut ccursor;
if !prediction.is_empty()
&& cursor_range.secondary.ccursor.index
== state.ime_cursor_range.secondary.ccursor.index
{
ccursor = text.delete_selected(&cursor_range);
text.insert_text_at(&mut ccursor, prediction, char_limit);
ImeEvent::Preedit(text_mark) => {
if text_mark == "\n" || text_mark == "\r" {
None
} else {
// Empty prediction can be produced when user press backspace
// or escape during IME, so we clear current text.
let mut ccursor = text.delete_selected(&cursor_range);
let start_cursor = ccursor;
if !text_mark.is_empty() {
text.insert_text_at(&mut ccursor, text_mark, char_limit);
}
state.ime_cursor_range = cursor_range;
Some(CCursorRange::two(start_cursor, ccursor))
}
}
ImeEvent::Commit(prediction) => {
if prediction == "\n" || prediction == "\r" {
None
} else {
ccursor = cursor_range.primary.ccursor;
state.ime_enabled = false;

if !prediction.is_empty()
&& cursor_range.secondary.ccursor.index
== state.ime_cursor_range.secondary.ccursor.index
{
let mut ccursor = text.delete_selected(&cursor_range);
text.insert_text_at(&mut ccursor, prediction, char_limit);
Some(CCursorRange::one(ccursor))
} else {
let ccursor = cursor_range.primary.ccursor;
Some(CCursorRange::one(ccursor))
}
}
Some(CCursorRange::one(ccursor))
} else {
}
ImeEvent::Disabled => {
state.ime_enabled = false;
None
}
}
},

_ => None,
};
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widgets/text_edit/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct TextEditState {

// If IME candidate window is shown on this text edit.
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) has_ime: bool,
pub(crate) ime_enabled: bool,

// cursor range for IME candidate.
#[cfg_attr(feature = "serde", serde(skip))]
Expand Down

0 comments on commit 436c671

Please sign in to comment.