Skip to content

Commit 436c671

Browse files
rustbasicemilk
andauthored
Improve IME support with new Event::Ime (#4358)
* Closes #4354 Fix: can't repeat input chinese words AND For Windows : ImeEnable ImeDisable --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
1 parent 587bc20 commit 436c671

File tree

5 files changed

+99
-62
lines changed

5 files changed

+99
-62
lines changed

crates/eframe/src/web/text_agent.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
6868
is_composing.set(true);
6969
input_clone.set_value("");
7070

71-
runner.input.raw.events.push(egui::Event::CompositionStart);
71+
let egui_event = egui::Event::Ime(egui::ImeEvent::Enabled);
72+
runner.input.raw.events.push(egui_event);
7273
runner.needs_repaint.repaint_asap();
7374
}
7475
})?;
@@ -77,8 +78,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
7778
&input,
7879
"compositionupdate",
7980
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
80-
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
81-
runner.input.raw.events.push(event);
81+
if let Some(text) = event.data() {
82+
let egui_event = egui::Event::Ime(egui::ImeEvent::Preedit(text));
83+
runner.input.raw.events.push(egui_event);
8284
runner.needs_repaint.repaint_asap();
8385
}
8486
},
@@ -91,8 +93,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
9193
is_composing.set(false);
9294
input_clone.set_value("");
9395

94-
if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
95-
runner.input.raw.events.push(event);
96+
if let Some(text) = event.data() {
97+
let egui_event = egui::Event::Ime(egui::ImeEvent::Commit(text));
98+
runner.input.raw.events.push(egui_event);
9699
runner.needs_repaint.repaint_asap();
97100
}
98101
}

crates/egui-winit/src/lib.rs

+31-14
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub struct State {
9595
pointer_touch_id: Option<u64>,
9696

9797
/// track ime state
98-
input_method_editor_started: bool,
98+
has_sent_ime_enabled: bool,
9999

100100
#[cfg(feature = "accesskit")]
101101
accesskit: Option<accesskit_winit::Adapter>,
@@ -136,7 +136,7 @@ impl State {
136136
simulate_touch_screen: false,
137137
pointer_touch_id: None,
138138

139-
input_method_editor_started: false,
139+
has_sent_ime_enabled: false,
140140

141141
#[cfg(feature = "accesskit")]
142142
accesskit: None,
@@ -342,23 +342,39 @@ impl State {
342342
// We use input_method_editor_started to manually insert CompositionStart
343343
// between Commits.
344344
match ime {
345-
winit::event::Ime::Enabled | winit::event::Ime::Disabled => (),
346-
winit::event::Ime::Commit(text) => {
347-
self.input_method_editor_started = false;
345+
winit::event::Ime::Enabled => {
348346
self.egui_input
349347
.events
350-
.push(egui::Event::CompositionEnd(text.clone()));
348+
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
349+
self.has_sent_ime_enabled = true;
351350
}
352-
winit::event::Ime::Preedit(text, Some(_)) => {
353-
if !self.input_method_editor_started {
354-
self.input_method_editor_started = true;
355-
self.egui_input.events.push(egui::Event::CompositionStart);
351+
winit::event::Ime::Preedit(_, None) => {}
352+
winit::event::Ime::Preedit(text, Some(_cursor)) => {
353+
if !self.has_sent_ime_enabled {
354+
self.egui_input
355+
.events
356+
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
357+
self.has_sent_ime_enabled = true;
356358
}
357359
self.egui_input
358360
.events
359-
.push(egui::Event::CompositionUpdate(text.clone()));
361+
.push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
362+
}
363+
winit::event::Ime::Commit(text) => {
364+
self.egui_input
365+
.events
366+
.push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
367+
self.egui_input
368+
.events
369+
.push(egui::Event::Ime(egui::ImeEvent::Disabled));
370+
self.has_sent_ime_enabled = false;
371+
}
372+
winit::event::Ime::Disabled => {
373+
self.egui_input
374+
.events
375+
.push(egui::Event::Ime(egui::ImeEvent::Disabled));
376+
self.has_sent_ime_enabled = false;
360377
}
361-
winit::event::Ime::Preedit(_, None) => {}
362378
};
363379

364380
EventResponse {
@@ -601,7 +617,8 @@ impl State {
601617
});
602618
// If we're not yet translating a touch or we're translating this very
603619
// touch …
604-
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id {
620+
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
621+
{
605622
// … emit PointerButton resp. PointerMoved events to emulate mouse
606623
match touch.phase {
607624
winit::event::TouchPhase::Started => {
@@ -1531,7 +1548,7 @@ pub fn create_winit_window_builder<T>(
15311548
// We set sizes and positions in egui:s own ui points, which depends on the egui
15321549
// zoom_factor and the native pixels per point, so we need to know that here.
15331550
// We don't know what monitor the window will appear on though, but
1534-
// we'll try to fix that after the window is created in the vall to `apply_viewport_builder_to_window`.
1551+
// we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`.
15351552
let native_pixels_per_point = event_loop
15361553
.primary_monitor()
15371554
.or_else(|| event_loop.available_monitors().next())

crates/egui/src/data/input.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -445,14 +445,8 @@ pub enum Event {
445445
/// * `zoom > 1`: pinch spread
446446
Zoom(f32),
447447

448-
/// IME composition start.
449-
CompositionStart,
450-
451-
/// A new IME candidate is being suggested.
452-
CompositionUpdate(String),
453-
454-
/// IME composition ended with this final result.
455-
CompositionEnd(String),
448+
/// IME Event
449+
Ime(ImeEvent),
456450

457451
/// On touch screens, report this *in addition to*
458452
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
@@ -507,6 +501,25 @@ pub enum Event {
507501
},
508502
}
509503

504+
/// IME event.
505+
///
506+
/// See <https://docs.rs/winit/latest/winit/event/enum.Ime.html>
507+
#[derive(Clone, Debug, Eq, PartialEq)]
508+
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
509+
pub enum ImeEvent {
510+
/// Notifies when the IME was enabled.
511+
Enabled,
512+
513+
/// A new IME candidate is being suggested.
514+
Preedit(String),
515+
516+
/// IME composition ended with this final result.
517+
Commit(String),
518+
519+
/// Notifies when the IME was disabled.
520+
Disabled,
521+
}
522+
510523
/// Mouse button (or similar for touch input)
511524
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
512525
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

crates/egui/src/widgets/text_edit/builder.rs

+38-34
Original file line numberDiff line numberDiff line change
@@ -950,47 +950,51 @@ fn events(
950950
..
951951
} => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key),
952952

953-
Event::CompositionStart => {
954-
state.has_ime = true;
955-
None
956-
}
957-
958-
Event::CompositionUpdate(text_mark) => {
959-
// empty prediction can be produced when user press backspace
960-
// or escape during ime. We should clear current text.
961-
if text_mark != "\n" && text_mark != "\r" && state.has_ime {
962-
let mut ccursor = text.delete_selected(&cursor_range);
963-
let start_cursor = ccursor;
964-
if !text_mark.is_empty() {
965-
text.insert_text_at(&mut ccursor, text_mark, char_limit);
966-
}
953+
Event::Ime(ime_event) => match ime_event {
954+
ImeEvent::Enabled => {
955+
state.ime_enabled = true;
967956
state.ime_cursor_range = cursor_range;
968-
Some(CCursorRange::two(start_cursor, ccursor))
969-
} else {
970957
None
971958
}
972-
}
973-
974-
Event::CompositionEnd(prediction) => {
975-
// CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first,
976-
// so do not check `state.has_ime = true` in the following statement.
977-
if prediction != "\n" && prediction != "\r" {
978-
state.has_ime = false;
979-
let mut ccursor;
980-
if !prediction.is_empty()
981-
&& cursor_range.secondary.ccursor.index
982-
== state.ime_cursor_range.secondary.ccursor.index
983-
{
984-
ccursor = text.delete_selected(&cursor_range);
985-
text.insert_text_at(&mut ccursor, prediction, char_limit);
959+
ImeEvent::Preedit(text_mark) => {
960+
if text_mark == "\n" || text_mark == "\r" {
961+
None
962+
} else {
963+
// Empty prediction can be produced when user press backspace
964+
// or escape during IME, so we clear current text.
965+
let mut ccursor = text.delete_selected(&cursor_range);
966+
let start_cursor = ccursor;
967+
if !text_mark.is_empty() {
968+
text.insert_text_at(&mut ccursor, text_mark, char_limit);
969+
}
970+
state.ime_cursor_range = cursor_range;
971+
Some(CCursorRange::two(start_cursor, ccursor))
972+
}
973+
}
974+
ImeEvent::Commit(prediction) => {
975+
if prediction == "\n" || prediction == "\r" {
976+
None
986977
} else {
987-
ccursor = cursor_range.primary.ccursor;
978+
state.ime_enabled = false;
979+
980+
if !prediction.is_empty()
981+
&& cursor_range.secondary.ccursor.index
982+
== state.ime_cursor_range.secondary.ccursor.index
983+
{
984+
let mut ccursor = text.delete_selected(&cursor_range);
985+
text.insert_text_at(&mut ccursor, prediction, char_limit);
986+
Some(CCursorRange::one(ccursor))
987+
} else {
988+
let ccursor = cursor_range.primary.ccursor;
989+
Some(CCursorRange::one(ccursor))
990+
}
988991
}
989-
Some(CCursorRange::one(ccursor))
990-
} else {
992+
}
993+
ImeEvent::Disabled => {
994+
state.ime_enabled = false;
991995
None
992996
}
993-
}
997+
},
994998

995999
_ => None,
9961000
};

crates/egui/src/widgets/text_edit/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct TextEditState {
4242

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

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

0 commit comments

Comments
 (0)