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>
This commit is contained in:
parent
587bc2034a
commit
436c671331
|
|
@ -68,7 +68,8 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
|
||||||
is_composing.set(true);
|
is_composing.set(true);
|
||||||
input_clone.set_value("");
|
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();
|
runner.needs_repaint.repaint_asap();
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -77,8 +78,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
|
||||||
&input,
|
&input,
|
||||||
"compositionupdate",
|
"compositionupdate",
|
||||||
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
move |event: web_sys::CompositionEvent, runner: &mut AppRunner| {
|
||||||
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
|
if let Some(text) = event.data() {
|
||||||
runner.input.raw.events.push(event);
|
let egui_event = egui::Event::Ime(egui::ImeEvent::Preedit(text));
|
||||||
|
runner.input.raw.events.push(egui_event);
|
||||||
runner.needs_repaint.repaint_asap();
|
runner.needs_repaint.repaint_asap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -91,8 +93,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> {
|
||||||
is_composing.set(false);
|
is_composing.set(false);
|
||||||
input_clone.set_value("");
|
input_clone.set_value("");
|
||||||
|
|
||||||
if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
|
if let Some(text) = event.data() {
|
||||||
runner.input.raw.events.push(event);
|
let egui_event = egui::Event::Ime(egui::ImeEvent::Commit(text));
|
||||||
|
runner.input.raw.events.push(egui_event);
|
||||||
runner.needs_repaint.repaint_asap();
|
runner.needs_repaint.repaint_asap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ pub struct State {
|
||||||
pointer_touch_id: Option<u64>,
|
pointer_touch_id: Option<u64>,
|
||||||
|
|
||||||
/// track ime state
|
/// track ime state
|
||||||
input_method_editor_started: bool,
|
has_sent_ime_enabled: bool,
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit: Option<accesskit_winit::Adapter>,
|
accesskit: Option<accesskit_winit::Adapter>,
|
||||||
|
|
@ -136,7 +136,7 @@ impl State {
|
||||||
simulate_touch_screen: false,
|
simulate_touch_screen: false,
|
||||||
pointer_touch_id: None,
|
pointer_touch_id: None,
|
||||||
|
|
||||||
input_method_editor_started: false,
|
has_sent_ime_enabled: false,
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit: None,
|
accesskit: None,
|
||||||
|
|
@ -342,23 +342,39 @@ impl State {
|
||||||
// We use input_method_editor_started to manually insert CompositionStart
|
// We use input_method_editor_started to manually insert CompositionStart
|
||||||
// between Commits.
|
// between Commits.
|
||||||
match ime {
|
match ime {
|
||||||
winit::event::Ime::Enabled | winit::event::Ime::Disabled => (),
|
winit::event::Ime::Enabled => {
|
||||||
winit::event::Ime::Commit(text) => {
|
|
||||||
self.input_method_editor_started = false;
|
|
||||||
self.egui_input
|
self.egui_input
|
||||||
.events
|
.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(_)) => {
|
winit::event::Ime::Preedit(_, None) => {}
|
||||||
if !self.input_method_editor_started {
|
winit::event::Ime::Preedit(text, Some(_cursor)) => {
|
||||||
self.input_method_editor_started = true;
|
if !self.has_sent_ime_enabled {
|
||||||
self.egui_input.events.push(egui::Event::CompositionStart);
|
self.egui_input
|
||||||
|
.events
|
||||||
|
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
|
||||||
|
self.has_sent_ime_enabled = true;
|
||||||
}
|
}
|
||||||
self.egui_input
|
self.egui_input
|
||||||
.events
|
.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 {
|
EventResponse {
|
||||||
|
|
@ -601,7 +617,8 @@ impl State {
|
||||||
});
|
});
|
||||||
// If we're not yet translating a touch or we're translating this very
|
// If we're not yet translating a touch or we're translating this very
|
||||||
// touch …
|
// 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
|
// … emit PointerButton resp. PointerMoved events to emulate mouse
|
||||||
match touch.phase {
|
match touch.phase {
|
||||||
winit::event::TouchPhase::Started => {
|
winit::event::TouchPhase::Started => {
|
||||||
|
|
@ -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
|
// 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.
|
// 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 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
|
let native_pixels_per_point = event_loop
|
||||||
.primary_monitor()
|
.primary_monitor()
|
||||||
.or_else(|| event_loop.available_monitors().next())
|
.or_else(|| event_loop.available_monitors().next())
|
||||||
|
|
|
||||||
|
|
@ -445,14 +445,8 @@ pub enum Event {
|
||||||
/// * `zoom > 1`: pinch spread
|
/// * `zoom > 1`: pinch spread
|
||||||
Zoom(f32),
|
Zoom(f32),
|
||||||
|
|
||||||
/// IME composition start.
|
/// IME Event
|
||||||
CompositionStart,
|
Ime(ImeEvent),
|
||||||
|
|
||||||
/// A new IME candidate is being suggested.
|
|
||||||
CompositionUpdate(String),
|
|
||||||
|
|
||||||
/// IME composition ended with this final result.
|
|
||||||
CompositionEnd(String),
|
|
||||||
|
|
||||||
/// On touch screens, report this *in addition to*
|
/// On touch screens, report this *in addition to*
|
||||||
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
|
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
|
||||||
|
|
@ -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)
|
/// Mouse button (or similar for touch input)
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
|
||||||
|
|
@ -950,47 +950,51 @@ fn events(
|
||||||
..
|
..
|
||||||
} => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key),
|
} => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key),
|
||||||
|
|
||||||
Event::CompositionStart => {
|
Event::Ime(ime_event) => match ime_event {
|
||||||
state.has_ime = true;
|
ImeEvent::Enabled => {
|
||||||
None
|
state.ime_enabled = true;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
state.ime_cursor_range = cursor_range;
|
state.ime_cursor_range = cursor_range;
|
||||||
Some(CCursorRange::two(start_cursor, ccursor))
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
ImeEvent::Preedit(text_mark) => {
|
||||||
|
if text_mark == "\n" || text_mark == "\r" {
|
||||||
Event::CompositionEnd(prediction) => {
|
None
|
||||||
// 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);
|
|
||||||
} else {
|
} else {
|
||||||
ccursor = cursor_range.primary.ccursor;
|
// 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))
|
||||||
}
|
}
|
||||||
Some(CCursorRange::one(ccursor))
|
}
|
||||||
} else {
|
ImeEvent::Commit(prediction) => {
|
||||||
|
if prediction == "\n" || prediction == "\r" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImeEvent::Disabled => {
|
||||||
|
state.ime_enabled = false;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub struct TextEditState {
|
||||||
|
|
||||||
// If IME candidate window is shown on this text edit.
|
// If IME candidate window is shown on this text edit.
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub(crate) has_ime: bool,
|
pub(crate) ime_enabled: bool,
|
||||||
|
|
||||||
// cursor range for IME candidate.
|
// cursor range for IME candidate.
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue