diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index f97269704a1..edc463763b8 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -361,6 +361,8 @@ mod glow_integration { let theme = system_theme.unwrap_or(self.native_options.default_theme); integration.egui_ctx.set_visuals(theme.egui_visuals()); + gl_window.window().set_ime_allowed(true); + { let event_loop_proxy = self.repaint_proxy.clone(); integration.egui_ctx.set_request_repaint_callback(move || { @@ -729,6 +731,8 @@ mod wgpu_integration { let theme = system_theme.unwrap_or(self.native_options.default_theme); integration.egui_ctx.set_visuals(theme.egui_visuals()); + window.set_ime_allowed(true); + { let event_loop_proxy = self.repaint_proxy.clone(); integration.egui_ctx.set_request_repaint_callback(move || { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index d0b8323f6e5..e9e997831a7 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -83,6 +83,9 @@ pub struct State { /// /// Only one touch will be interpreted as pointer at any time. pointer_touch_id: Option, + + /// track ime state + input_method_editor_started: bool, } impl State { @@ -109,6 +112,8 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, + + input_method_editor_started: false, } } @@ -251,6 +256,44 @@ impl State { consumed, } } + WindowEvent::Ime(ime) => { + // on Mac even Cmd-C is preessed during ime, a `c` is pushed to Preedit. + // So no need to check is_mac_cmd. + // + // How winit produce `Ime::Enabled` and `Ime::Disabled` differs in MacOS + // and Windows. + // + // - On Windows, before and after each Commit will produce an Enable/Disabled + // event. + // - On MacOS, only when user explicit enable/disable ime. No Disabled + // after Commit. + // + // We use input_method_editor_started to mannualy 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; + self.egui_input + .events + .push(egui::Event::CompositionEnd(text.clone())); + } + winit::event::Ime::Preedit(text, ..) => { + if !self.input_method_editor_started { + self.input_method_editor_started = true; + self.egui_input.events.push(egui::Event::CompositionStart); + } + self.egui_input + .events + .push(egui::Event::CompositionUpdate(text.clone())); + } + }; + + EventResponse { + repaint: true, + consumed: egui_ctx.wants_keyboard_input(), + } + } WindowEvent::KeyboardInput { input, .. } => { self.on_keyboard_input(input); let consumed = egui_ctx.wants_keyboard_input() @@ -317,7 +360,6 @@ impl State { | WindowEvent::CloseRequested | WindowEvent::CursorEntered { .. } | WindowEvent::Destroyed - | WindowEvent::Ime(_) | WindowEvent::Occluded(_) | WindowEvent::Resized(_) | WindowEvent::ThemeChanged(_) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 26a81c13ca4..6d83389c577 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -618,7 +618,13 @@ impl<'t> TextEdit<'t> { if interactive { // eframe web uses `text_cursor_pos` when showing IME, // so only set it when text is editable and visible! - ui.ctx().output().text_cursor_pos = Some(cursor_pos.left_top()); + // But `winit` and `egui_web` differs in how to set the + // position of IME. + if cfg!(target_arch = "wasm32") { + ui.ctx().output().text_cursor_pos = Some(cursor_pos.left_top()); + } else { + ui.ctx().output().text_cursor_pos = Some(cursor_pos.left_bottom()); + } } } } @@ -816,11 +822,14 @@ fn events( } Event::CompositionUpdate(text_mark) => { - if !text_mark.is_empty() && text_mark != "\n" && text_mark != "\r" && state.has_ime - { + // 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 = delete_selected(text, &cursor_range); let start_cursor = ccursor; - insert_text(&mut ccursor, text, text_mark); + if !text_mark.is_empty() { + insert_text(&mut ccursor, text, text_mark); + } Some(CCursorRange::two(start_cursor, ccursor)) } else { None @@ -828,14 +837,12 @@ fn events( } Event::CompositionEnd(prediction) => { - if !prediction.is_empty() - && prediction != "\n" - && prediction != "\r" - && state.has_ime - { + if prediction != "\n" && prediction != "\r" && state.has_ime { state.has_ime = false; let mut ccursor = delete_selected(text, &cursor_range); - insert_text(&mut ccursor, text, prediction); + if !prediction.is_empty() { + insert_text(&mut ccursor, text, prediction); + } Some(CCursorRange::one(ccursor)) } else { None