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);
|
||||
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();
|
||||
}
|
||||
})?;
|
||||
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 => {
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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`]
|
||||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
Loading…
Reference in New Issue