From 7a27bf688a28a50e9cd0b39ecf285f2ce3ee37cf Mon Sep 17 00:00:00 2001 From: umajho Date: Thu, 11 Jun 2026 19:39:02 +0800 Subject: [PATCH] Feat: add features to ctrl IME visuals; keep Windows the legacy way by default --- crates/eframe/Cargo.toml | 17 +++++++++++++++-- crates/egui/Cargo.toml | 18 ++++++++++++++++++ crates/egui/src/widgets/text_edit/builder.rs | 14 ++++++++++++-- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 0530b64a8..a7d3102ce 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -59,6 +59,8 @@ default_fonts = ["egui/default_fonts"] ## and `WebOptions::renderer`. glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"] +_override_legacy_ime_composition_visuals = ["egui/_override_legacy_ime_composition_visuals"] + ## Enable saving app state to disk. persistence = ["dep:home", "egui-winit/serde", "egui/persistence", "ron", "serde"] @@ -101,6 +103,10 @@ wgpu = ["wgpu_no_default_features", "egui-wgpu/default"] ## ``` wgpu_no_default_features = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] +## Enable the new IME composition visuals on Windows. The legacy visuals remain +## the default on Windows due to known issues with the new implementation. +windows_new_ime_composition_visuals = ["egui/_override_legacy_ime_composition_visuals"] + ## Enables compiling for x11. x11 = [ "egui-winit/x11", @@ -117,8 +123,6 @@ x11 = [ __screenshot = [] [dependencies] -egui = { workspace = true, default-features = false, features = ["bytemuck"] } - ahash.workspace = true document-features.workspace = true log.workspace = true @@ -136,6 +140,10 @@ ron = { workspace = true, optional = true, features = ["integer128"] } serde = { workspace = true, optional = true } # ------------------------------------------- +# egui non-windows: +[target.'cfg(not(target_os = "windows"))'.dependencies] +egui = { workspace = true, default-features = false, features = ["bytemuck"] } + # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] egui-winit = { workspace = true, default-features = false, features = ["clipboard", "links"] } @@ -183,6 +191,11 @@ objc2-app-kit = { workspace = true, default-features = false, features = [ # windows: [target.'cfg(any(target_os = "windows"))'.dependencies] +egui = { workspace = true, default-features = false, features = [ + "bytemuck", + "legacy_ime_composition_visuals", +] } + windows-sys = { workspace = true, features = [ "Win32_Foundation", "Win32_System_Com", diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index fadc27c6c..83fdff3a4 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -44,6 +44,21 @@ color-hex = ["epaint/color-hex"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint/default_fonts"] +## The legacy IME composition visuals have several shortcomings. Most notably, +## they are visually identical to text selection, making it impossible to tell +## the two apart if there is no candidate window (e.g. when using a Korean IME). +## In addition, the cursor is always shown at the end of the composition no +## matter where the actual cursor is, which makes composing Chinese and Japanese +## text confusing. +## +## However, the new composition visuals are not yet fully reliable either. For +## example, on Windows, `winit` currently reports an incorrect cursor position +## for Korean IMEs: the cursor should be at the end of the composition, but +## `winit` reports it at the beginning. Therefore, we keep the legacy visuals +## as an option for now and plan to remove them once the new visuals work +## correctly across all supported platforms. +legacy_ime_composition_visuals = [] + ## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra). mint = ["epaint/mint"] @@ -62,6 +77,9 @@ serde = ["dep:serde", "epaint/serde", "accesskit/serde"] ## Change Vertex layout to be compatible with unity unity = ["epaint/unity"] +## Override and disable the legacy_ime_composition_visuals feature. +_override_legacy_ime_composition_visuals = [] + ## Override and disable the unity feature ## This exists, so that when testing with --all-features, snapshots render correctly. _override_unity = ["epaint/_override_unity"] diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 783fbec47..a7984882b 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -827,11 +827,16 @@ impl TextEdit<'_> { false }; + let should_paint_ime_visuals_the_legacy_way = cfg!(all( + feature = "legacy_ime_composition_visuals", + not(feature = "_override_legacy_ime_composition_visuals"), + )); + if ui.is_rect_visible(inner_rect) { let has_focus = ui.memory(|mem| mem.has_focus(id)); if has_focus - && state.cursor_purpose.is_selection() + && (state.cursor_purpose.is_selection() || should_paint_ime_visuals_the_legacy_way) && let Some(cursor_range) = state.cursor.range(&galley) { // Add text selection rectangles to the galley: @@ -866,7 +871,12 @@ impl TextEdit<'_> { let viewport_has_focus = ui.input(|i| i.focused); if viewport_has_focus { let time_since_last_interaction = now - state.last_interaction_time; - match &state.cursor_purpose { + let cursor_purpose = if should_paint_ime_visuals_the_legacy_way { + &TextEditCursorPurpose::Selection + } else { + &state.cursor_purpose + }; + match cursor_purpose { TextEditCursorPurpose::Selection => { text_selection::visuals::paint_text_cursor( ui,