diff --git a/Cargo.lock b/Cargo.lock index c781b851b5a..d0ede1ce6df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ dependencies = [ "accesskit_macos", "accesskit_unix", "accesskit_windows", - "winit", + "winit 0.28.6", ] [[package]] @@ -326,6 +326,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "atomic_refcell" version = "0.1.10" @@ -911,6 +917,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740bb192a8e2d1350119916954f4409ee7f62f149b536911eeb78ba5a20526bf" + [[package]] name = "custom_3d_glow" version = "0.1.0" @@ -1067,11 +1079,11 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.0", ] [[package]] @@ -1172,7 +1184,7 @@ dependencies = [ "web-sys", "wgpu", "winapi", - "winit", + "winit 0.29.0-beta.0", ] [[package]] @@ -1201,7 +1213,7 @@ dependencies = [ "thiserror", "type-map", "wgpu", - "winit", + "winit 0.29.0-beta.0", ] [[package]] @@ -1218,8 +1230,9 @@ dependencies = [ "raw-window-handle", "serde", "smithay-clipboard", + "smol_str", "webbrowser", - "winit", + "winit 0.29.0-beta.0", ] [[package]] @@ -1286,7 +1299,7 @@ dependencies = [ "glutin", "glutin-winit", "log", - "memoffset", + "memoffset 0.6.5", "puffin", "raw-window-handle", "wasm-bindgen", @@ -1724,7 +1737,7 @@ dependencies = [ "cfg_aliases", "glutin", "raw-window-handle", - "winit", + "winit 0.28.6", ] [[package]] @@ -2098,9 +2111,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -2262,6 +2275,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.24.0" @@ -2396,7 +2418,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -2409,10 +2431,23 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", "pin-utils", ] +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "static_assertions", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2698,7 +2733,7 @@ dependencies = [ "base64", "indexmap", "line-wrap", - "quick-xml", + "quick-xml 0.26.0", "serde", "time 0.3.21", ] @@ -2835,6 +2870,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.27" @@ -3136,14 +3180,14 @@ dependencies = [ [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "2704a44480b79e10d2185d1d246e86b02e177e33bdaaaab3b1f65fdf13771448" dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit", + "smithay-client-toolkit 0.17.0", "tiny-skia", ] @@ -3276,9 +3320,31 @@ dependencies = [ "memmap2", "nix 0.24.3", "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", + "wayland-client 0.29.5", + "wayland-cursor 0.29.5", + "wayland-protocols 0.29.5", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.26.2", + "thiserror", + "wayland-backend", + "wayland-client 0.30.2", + "wayland-cursor 0.30.0", + "wayland-protocols 0.30.1", + "wayland-protocols-wlr", + "wayland-scanner 0.30.1", ] [[package]] @@ -3287,8 +3353,17 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" dependencies = [ - "smithay-client-toolkit", - "wayland-client", + "smithay-client-toolkit 0.16.0", + "wayland-client 0.29.5", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", ] [[package]] @@ -3724,6 +3799,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" @@ -3851,9 +3932,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3861,9 +3942,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -3888,9 +3969,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3898,9 +3979,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -3911,9 +3992,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wayland-backend" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" +dependencies = [ + "cc", + "downcast-rs", + "io-lifetimes", + "nix 0.26.2", + "scoped-tls", + "smallvec", + "wayland-sys 0.30.1", +] [[package]] name = "wayland-client" @@ -3927,10 +4023,23 @@ dependencies = [ "nix 0.24.3", "scoped-tls", "wayland-commons", - "wayland-scanner", + "wayland-scanner 0.29.5", "wayland-sys 0.29.5", ] +[[package]] +name = "wayland-client" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "nix 0.26.2", + "wayland-backend", + "wayland-scanner 0.30.1", +] + [[package]] name = "wayland-commons" version = "0.29.5" @@ -3950,7 +4059,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" dependencies = [ "nix 0.24.3", - "wayland-client", + "wayland-client 0.29.5", + "xcursor", +] + +[[package]] +name = "wayland-cursor" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa" +dependencies = [ + "nix 0.26.2", + "wayland-client 0.30.2", "xcursor", ] @@ -3961,9 +4081,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ "bitflags 1.3.2", - "wayland-client", + "wayland-client 0.29.5", "wayland-commons", - "wayland-scanner", + "wayland-scanner 0.29.5", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client 0.30.2", + "wayland-scanner 0.30.1", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "wayland-scanner 0.30.1", ] [[package]] @@ -3977,6 +4122,17 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-scanner" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" +dependencies = [ + "proc-macro2", + "quick-xml 0.28.2", + "quote", +] + [[package]] name = "wayland-sys" version = "0.29.5" @@ -4002,14 +4158,25 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19353897b48e2c4d849a2d73cb0aeb16dc2be4e00c565abfc11eb65a806e47de" +dependencies = [ + "js-sys", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "webbrowser" version = "0.8.10" @@ -4400,18 +4567,59 @@ dependencies = [ "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", - "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.16.0", "wasm-bindgen", - "wayland-client", + "wayland-client 0.29.5", "wayland-commons", - "wayland-protocols", - "wayland-scanner", + "wayland-protocols 0.29.5", + "wayland-scanner 0.29.5", "web-sys", "windows-sys 0.45.0", "x11-dl", ] +[[package]] +name = "winit" +version = "0.29.0-beta.0" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.3.1", + "calloop", + "cfg_aliases", + "core-foundation", + "core-graphics", + "cursor-icon", + "dispatch", + "fnv", + "js-sys", + "libc", + "log", + "memmap2", + "ndk", + "ndk-sys", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall 0.3.5", + "sctk-adwaita", + "smithay-client-toolkit 0.17.0", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.4.6" @@ -4463,6 +4671,25 @@ dependencies = [ "nom", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" +dependencies = [ + "bitflags 2.3.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + [[package]] name = "xml-rs" version = "0.8.13" diff --git a/Cargo.toml b/Cargo.toml index 62d3fc4f2e9..a656002b237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,6 @@ opt-level = 2 [workspace.dependencies] thiserror = "1.0.37" + +[patch.crates-io] +winit = { path = "../github/winit"} diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 87ad0a2d760..9f73ad72431 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -109,7 +109,7 @@ image = { version = "0.24", default-features = false, features = [ "png", ] } # Needed for app icon raw-window-handle = { version = "0.5.0" } -winit = "0.28.1" +winit = "0.29.0-beta.0" # optional native: directories-next = { version = "2", optional = true } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 7771265a1ab..a9a407afd4c 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -729,7 +729,6 @@ 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); if self.native_options.mouse_passthrough { gl_window.window().set_cursor_hittest(false).unwrap(); } @@ -1193,8 +1192,6 @@ 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 diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4da93c52117..d3e7d5d3533 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -230,7 +230,8 @@ impl AppRunner { mutable_text_under_cursor, text_cursor_pos, #[cfg(feature = "accesskit")] - accesskit_update: _, // not currently implemented + accesskit_update: _, // not currently implemented + text_input_state: _, // not currently implemented } = platform_output; super::set_cursor_icon(cursor_icon); diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 7fc095b2d44..7cb581ce2f3 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -50,7 +50,7 @@ wgpu = "0.16.0" ## Enable this when generating docs. document-features = { version = "0.2", optional = true } -winit = { version = "0.28", optional = true } +winit = { version = "0.29.0-beta.0", optional = true } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index bf3f128f3e3..764e1d86cd6 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -57,7 +57,8 @@ egui = { version = "0.22.0", path = "../egui", default-features = false, feature "log", ] } log = { version = "0.4", features = ["std"] } -winit = { version = "0.28", default-features = false } +winit = { version = "0.29.0-beta.0", default-features = false } +smol_str = "0.2.0" raw-window-handle = "0.5.0" #! ### Optional dependencies diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 75c9028cfe3..68903b26086 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -11,17 +11,20 @@ #[cfg(feature = "accesskit")] pub use accesskit_winit; +use raw_window_handle::HasRawDisplayHandle; +pub use winit; +use winit::dpi::LogicalSize; +use winit::event::MouseButton; +use winit::keyboard::KeyCode; + pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -pub use winit; +pub use window_settings::WindowSettings; pub mod clipboard; mod window_settings; -pub use window_settings::WindowSettings; - -use raw_window_handle::HasRawDisplayHandle; pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 { window.scale_factor() as f32 @@ -77,6 +80,7 @@ pub struct State { /// track ime state input_method_editor_started: bool, + text_input_last_frame: bool, #[cfg(feature = "accesskit")] accesskit: Option, @@ -107,6 +111,7 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, + text_input_last_frame: false, input_method_editor_started: false, #[cfg(feature = "accesskit")] @@ -248,25 +253,6 @@ impl State { consumed, } } - WindowEvent::ReceivedCharacter(ch) => { - // On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc. - // We need to ignore these characters that are side-effects of commands. - let is_mac_cmd = cfg!(target_os = "macos") - && (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd); - - let consumed = if is_printable_char(*ch) && !is_mac_cmd { - self.egui_input - .events - .push(egui::Event::Text(ch.to_string())); - egui_ctx.wants_keyboard_input() - } else { - false - }; - EventResponse { - repaint: true, - consumed, - } - } WindowEvent::Ime(ime) => { // on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit. // So no need to check is_mac_cmd. @@ -298,6 +284,15 @@ impl State { .events .push(egui::Event::CompositionUpdate(text.clone())); } + winit::event::Ime::Replace { text, selection, compose_region } => { + self.egui_input + .events + .push(egui::Event::CompositionReplace { + content: text.clone(), + selection: selection.clone(), + compose_region: compose_region.clone(), + }); + } }; EventResponse { @@ -305,10 +300,21 @@ impl State { consumed: egui_ctx.wants_keyboard_input(), } } - WindowEvent::KeyboardInput { input, .. } => { - self.on_keyboard_input(input); - let consumed = egui_ctx.wants_keyboard_input() - || input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab); + WindowEvent::KeyboardInput { event, .. } => { + self.on_keyboard_input(event); + let consumed = if let Some(text) = &event.text { + if text.chars().all(|c| is_printable_char(c)) { + self.egui_input.events.push(egui::Event::Text(text.to_string())); + egui_ctx.wants_keyboard_input() + } else { + false + } + } else { + false + }; + + let consumed = consumed || egui_ctx.wants_keyboard_input() + || event.logical_key == winit::keyboard::Key::Tab; EventResponse { repaint: true, consumed, @@ -356,14 +362,14 @@ impl State { } } WindowEvent::ModifiersChanged(state) => { - self.egui_input.modifiers.alt = state.alt(); - self.egui_input.modifiers.ctrl = state.ctrl(); - self.egui_input.modifiers.shift = state.shift(); - self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && state.logo(); + self.egui_input.modifiers.alt = state.lalt_state() == winit::keyboard::ModifiersKeyState::Pressed || state.ralt_state() == winit::keyboard::ModifiersKeyState::Pressed; + self.egui_input.modifiers.ctrl = state.lcontrol_state() == winit::keyboard::ModifiersKeyState::Pressed || state.rcontrol_state() == winit::keyboard::ModifiersKeyState::Pressed; + self.egui_input.modifiers.shift = state.lshift_state() == winit::keyboard::ModifiersKeyState::Pressed || state.rshift_state() == winit::keyboard::ModifiersKeyState::Pressed; + self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && (state.lsuper_state() == winit::keyboard::ModifiersKeyState::Pressed || state.rsuper_state() == winit::keyboard::ModifiersKeyState::Pressed); self.egui_input.modifiers.command = if cfg!(target_os = "macos") { - state.logo() + state.lsuper_state() == winit::keyboard::ModifiersKeyState::Pressed || state.rsuper_state() == winit::keyboard::ModifiersKeyState::Pressed } else { - state.ctrl() + state.lcontrol_state() == winit::keyboard::ModifiersKeyState::Pressed || state.rcontrol_state() == winit::keyboard::ModifiersKeyState::Pressed }; EventResponse { repaint: true, @@ -506,10 +512,10 @@ impl State { force: match touch.force { Some(winit::event::Force::Normalized(force)) => force as f32, Some(winit::event::Force::Calibrated { - force, - max_possible_force, - .. - }) => (force / max_possible_force) as f32, + force, + max_possible_force, + .. + }) => (force / max_possible_force) as f32, None => 0_f32, }, }); @@ -557,9 +563,9 @@ impl State { (egui::MouseWheelUnit::Line, egui::vec2(x, y)) } winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition { - x, - y, - }) => ( + x, + y, + }) => ( egui::MouseWheelUnit::Point, egui::vec2(x as f32, y as f32) / self.pixels_per_point(), ), @@ -596,35 +602,34 @@ impl State { } } - fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) { - if let Some(keycode) = input.virtual_keycode { - let pressed = input.state == winit::event::ElementState::Pressed; - - if pressed { - // VirtualKeyCode::Paste etc in winit are broken/untrustworthy, - // so we detect these things manually: - if is_cut_command(self.egui_input.modifiers, keycode) { - self.egui_input.events.push(egui::Event::Cut); - } else if is_copy_command(self.egui_input.modifiers, keycode) { - self.egui_input.events.push(egui::Event::Copy); - } else if is_paste_command(self.egui_input.modifiers, keycode) { - if let Some(contents) = self.clipboard.get() { - let contents = contents.replace("\r\n", "\n"); - if !contents.is_empty() { - self.egui_input.events.push(egui::Event::Paste(contents)); - } + fn on_keyboard_input(&mut self, input: &winit::event::KeyEvent) { + let keycode = &input.logical_key.clone(); + let pressed = input.state == winit::event::ElementState::Pressed; + + if pressed { + // VirtualKeyCode::Paste etc in winit are broken/untrustworthy, + // so we detect these things manually: + if is_cut_command(self.egui_input.modifiers, keycode) { + self.egui_input.events.push(egui::Event::Cut); + } else if is_copy_command(self.egui_input.modifiers, keycode) { + self.egui_input.events.push(egui::Event::Copy); + } else if is_paste_command(self.egui_input.modifiers, keycode) { + if let Some(contents) = self.clipboard.get() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + self.egui_input.events.push(egui::Event::Paste(contents)); } } } + } - if let Some(key) = translate_virtual_key_code(keycode) { - self.egui_input.events.push(egui::Event::Key { - key, - pressed, - repeat: false, // egui will fill this in for us! - modifiers: self.egui_input.modifiers, - }); - } + if let Some(key) = translate_virtual_key_code(input.physical_key) { + self.egui_input.events.push(egui::Event::Key { + key, + pressed, + repeat: false, // egui will fill this in for us! + modifiers: self.egui_input.modifiers, + }); } } @@ -651,6 +656,7 @@ impl State { text_cursor_pos, #[cfg(feature = "accesskit")] accesskit_update, + surrounding_text, } = platform_output; self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI @@ -664,8 +670,18 @@ impl State { self.clipboard.set(copied_text); } + if let Some((text, selection)) = surrounding_text { + window.set_ime_surrounding_text(text, selection); + } + + let text_input_this_frame = text_cursor_pos.is_some(); + if self.text_input_last_frame != text_input_this_frame { + window.set_ime_allowed(text_input_this_frame); + } + self.text_input_last_frame = text_input_this_frame; + if let Some(egui::Pos2 { x, y }) = text_cursor_pos { - window.set_ime_position(winit::dpi::LogicalPosition { x, y }); + window.set_ime_cursor_area(winit::dpi::LogicalPosition { x, y }, LogicalSize::new(100, 100)); } #[cfg(feature = "accesskit")] @@ -724,25 +740,25 @@ fn is_printable_char(chr: char) -> bool { !is_in_private_use_area && !chr.is_ascii_control() } -fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::X) +fn is_cut_command(modifiers: egui::Modifiers, keycode: &winit::keyboard::Key) -> bool { + (modifiers.command && keycode.to_text() == Some("x")) || (cfg!(target_os = "windows") - && modifiers.shift - && keycode == winit::event::VirtualKeyCode::Delete) + && modifiers.shift + && keycode == &winit::keyboard::Key::Delete) } -fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::C) +fn is_copy_command(modifiers: egui::Modifiers, keycode: &winit::keyboard::Key) -> bool { + (modifiers.command && keycode.to_text() == Some("c")) || (cfg!(target_os = "windows") - && modifiers.ctrl - && keycode == winit::event::VirtualKeyCode::Insert) + && modifiers.ctrl + && keycode == &winit::keyboard::Key::Insert) } -fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::V) +fn is_paste_command(modifiers: egui::Modifiers, keycode: &winit::keyboard::Key) -> bool { + (modifiers.command && keycode.to_text() == Some("v")) || (cfg!(target_os = "windows") - && modifiers.shift - && keycode == winit::event::VirtualKeyCode::Insert) + && modifiers.shift + && keycode == &winit::keyboard::Key::Insert) } fn translate_mouse_button(button: winit::event::MouseButton) -> Option { @@ -753,95 +769,97 @@ fn translate_mouse_button(button: winit::event::MouseButton) -> Option Some(egui::PointerButton::Extra1), winit::event::MouseButton::Other(2) => Some(egui::PointerButton::Extra2), winit::event::MouseButton::Other(_) => None, + MouseButton::Back => None, + MouseButton::Forward => None, } } -fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option { +fn translate_virtual_key_code(key: winit::keyboard::KeyCode) -> Option { use egui::Key; - use winit::event::VirtualKeyCode; + use winit::keyboard::KeyCode; Some(match key { - VirtualKeyCode::Down => Key::ArrowDown, - VirtualKeyCode::Left => Key::ArrowLeft, - VirtualKeyCode::Right => Key::ArrowRight, - VirtualKeyCode::Up => Key::ArrowUp, - - VirtualKeyCode::Escape => Key::Escape, - VirtualKeyCode::Tab => Key::Tab, - VirtualKeyCode::Back => Key::Backspace, - VirtualKeyCode::Return => Key::Enter, - VirtualKeyCode::Space => Key::Space, - - VirtualKeyCode::Insert => Key::Insert, - VirtualKeyCode::Delete => Key::Delete, - VirtualKeyCode::Home => Key::Home, - VirtualKeyCode::End => Key::End, - VirtualKeyCode::PageUp => Key::PageUp, - VirtualKeyCode::PageDown => Key::PageDown, - - VirtualKeyCode::Minus => Key::Minus, - // Using Mac the key with the Plus sign on it is reported as the Equals key - // (with both English and Swedish keyboard). - VirtualKeyCode::Equals => Key::PlusEquals, - - VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0, - VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1, - VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2, - VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3, - VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4, - VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5, - VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6, - VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7, - VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8, - VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9, - - VirtualKeyCode::A => Key::A, - VirtualKeyCode::B => Key::B, - VirtualKeyCode::C => Key::C, - VirtualKeyCode::D => Key::D, - VirtualKeyCode::E => Key::E, - VirtualKeyCode::F => Key::F, - VirtualKeyCode::G => Key::G, - VirtualKeyCode::H => Key::H, - VirtualKeyCode::I => Key::I, - VirtualKeyCode::J => Key::J, - VirtualKeyCode::K => Key::K, - VirtualKeyCode::L => Key::L, - VirtualKeyCode::M => Key::M, - VirtualKeyCode::N => Key::N, - VirtualKeyCode::O => Key::O, - VirtualKeyCode::P => Key::P, - VirtualKeyCode::Q => Key::Q, - VirtualKeyCode::R => Key::R, - VirtualKeyCode::S => Key::S, - VirtualKeyCode::T => Key::T, - VirtualKeyCode::U => Key::U, - VirtualKeyCode::V => Key::V, - VirtualKeyCode::W => Key::W, - VirtualKeyCode::X => Key::X, - VirtualKeyCode::Y => Key::Y, - VirtualKeyCode::Z => Key::Z, - - VirtualKeyCode::F1 => Key::F1, - VirtualKeyCode::F2 => Key::F2, - VirtualKeyCode::F3 => Key::F3, - VirtualKeyCode::F4 => Key::F4, - VirtualKeyCode::F5 => Key::F5, - VirtualKeyCode::F6 => Key::F6, - VirtualKeyCode::F7 => Key::F7, - VirtualKeyCode::F8 => Key::F8, - VirtualKeyCode::F9 => Key::F9, - VirtualKeyCode::F10 => Key::F10, - VirtualKeyCode::F11 => Key::F11, - VirtualKeyCode::F12 => Key::F12, - VirtualKeyCode::F13 => Key::F13, - VirtualKeyCode::F14 => Key::F14, - VirtualKeyCode::F15 => Key::F15, - VirtualKeyCode::F16 => Key::F16, - VirtualKeyCode::F17 => Key::F17, - VirtualKeyCode::F18 => Key::F18, - VirtualKeyCode::F19 => Key::F19, - VirtualKeyCode::F20 => Key::F20, + KeyCode::ArrowDown => Key::ArrowDown, + KeyCode::ArrowLeft => Key::ArrowLeft, + KeyCode::ArrowRight => Key::ArrowRight, + KeyCode::ArrowUp => Key::ArrowUp, + + KeyCode::Escape => Key::Escape, + KeyCode::Tab => Key::Tab, + KeyCode::Backspace => Key::Backspace, + KeyCode::NumpadBackspace => Key::Backspace, + KeyCode::Enter => Key::Enter, + KeyCode::Space => Key::Space, + + KeyCode::Insert => Key::Insert, + KeyCode::Delete => Key::Delete, + KeyCode::Home => Key::Home, + KeyCode::End => Key::End, + KeyCode::PageUp => Key::PageUp, + KeyCode::PageDown => Key::PageDown, + + KeyCode::Minus => Key::Minus, + KeyCode::Equal => Key::PlusEquals, + KeyCode::NumpadEqual => Key::PlusEquals, + + KeyCode::Numpad0 | KeyCode::Digit0 => Key::Num0, + KeyCode::Numpad1 | KeyCode::Digit1 => Key::Num1, + KeyCode::Numpad2 | KeyCode::Digit2 => Key::Num2, + KeyCode::Numpad3 | KeyCode::Digit3 => Key::Num3, + KeyCode::Numpad4 | KeyCode::Digit4 => Key::Num4, + KeyCode::Numpad5 | KeyCode::Digit5 => Key::Num5, + KeyCode::Numpad6 | KeyCode::Digit6 => Key::Num6, + KeyCode::Numpad7 | KeyCode::Digit7 => Key::Num7, + KeyCode::Numpad8 | KeyCode::Digit8 => Key::Num8, + KeyCode::Numpad9 | KeyCode::Digit9 => Key::Num9, + + KeyCode::KeyA => Key::A, + KeyCode::KeyB => Key::B, + KeyCode::KeyC => Key::C, + KeyCode::KeyD => Key::D, + KeyCode::KeyE => Key::E, + KeyCode::KeyF => Key::F, + KeyCode::KeyG => Key::G, + KeyCode::KeyH => Key::H, + KeyCode::KeyI => Key::I, + KeyCode::KeyJ => Key::J, + KeyCode::KeyK => Key::K, + KeyCode::KeyL => Key::L, + KeyCode::KeyM => Key::M, + KeyCode::KeyN => Key::N, + KeyCode::KeyO => Key::O, + KeyCode::KeyP => Key::P, + KeyCode::KeyQ => Key::Q, + KeyCode::KeyR => Key::R, + KeyCode::KeyS => Key::S, + KeyCode::KeyT => Key::T, + KeyCode::KeyU => Key::U, + KeyCode::KeyV => Key::V, + KeyCode::KeyW => Key::W, + KeyCode::KeyX => Key::X, + KeyCode::KeyY => Key::Y, + KeyCode::KeyZ => Key::Z, + + KeyCode::F1 => Key::F1, + KeyCode::F2 => Key::F2, + KeyCode::F3 => Key::F3, + KeyCode::F4 => Key::F4, + KeyCode::F5 => Key::F5, + KeyCode::F6 => Key::F6, + KeyCode::F7 => Key::F7, + KeyCode::F8 => Key::F8, + KeyCode::F9 => Key::F9, + KeyCode::F10 => Key::F10, + KeyCode::F11 => Key::F11, + KeyCode::F12 => Key::F12, + KeyCode::F13 => Key::F13, + KeyCode::F14 => Key::F14, + KeyCode::F15 => Key::F15, + KeyCode::F16 => Key::F16, + KeyCode::F17 => Key::F17, + KeyCode::F18 => Key::F18, + KeyCode::F19 => Key::F19, + KeyCode::F20 => Key::F20, _ => { return None; @@ -866,7 +884,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option Some(winit::window::CursorIcon::Move), egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop), egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed), - egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand), + egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer), egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress), egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize), diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 29ff3f5dd52..95dea93c745 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -257,6 +257,8 @@ pub enum Event { /// IME composition ended with this final result. CompositionEnd(String), + CompositionReplace { content: String, selection: (usize, usize), compose_region: Option<(usize, usize)> }, + /// On touch screens, report this *in addition to* /// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`] Touch { diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index cea031ed0af..41e83f5276a 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -1,6 +1,6 @@ //! All the data egui returns to the backend at the end of each frame. -use crate::WidgetType; +use crate::{TextInputState, WidgetType}; /// What egui emits each frame from [`crate::Context::run`]. /// @@ -86,6 +86,9 @@ pub struct PlatformOutput { /// Screen-space position of text edit cursor (used for IME). pub text_cursor_pos: Option, + /// Set surrounding text and current selection of the current text input (used for IME). + pub surrounding_text: Option<(String, (usize, usize))>, + #[cfg(feature = "accesskit")] pub accesskit_update: Option, } @@ -126,6 +129,7 @@ impl PlatformOutput { text_cursor_pos, #[cfg(feature = "accesskit")] accesskit_update, + surrounding_text, } = newer; self.cursor_icon = cursor_icon; @@ -139,6 +143,10 @@ impl PlatformOutput { self.mutable_text_under_cursor = mutable_text_under_cursor; self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos); + if surrounding_text.is_some() { + self.surrounding_text = surrounding_text; + } + #[cfg(feature = "accesskit")] { // egui produces a complete AccessKit tree for each frame, diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 1b49757bcec..a23fecf82cf 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -53,6 +53,7 @@ pub struct InputState { /// Position and size of the egui area. pub screen_rect: Rect, + previous_screen_rect: Rect, /// Also known as device pixel ratio, > 1 for high resolution screens. pub pixels_per_point: f32, @@ -128,6 +129,7 @@ impl Default for InputState { scroll_delta: Vec2::ZERO, zoom_factor_delta: 1.0, screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)), + previous_screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)), pixels_per_point: 1.0, max_texture_side: 2048, time: 0.0, @@ -160,6 +162,7 @@ impl InputState { new.predicted_dt }; + let previous_screen_rect = self.screen_rect; let screen_rect = new.screen_rect.unwrap_or(self.screen_rect); self.create_touch_states_for_new_devices(&new.events); for touch_state in self.touch_states.values_mut() { @@ -215,6 +218,7 @@ impl InputState { scroll_delta, zoom_factor_delta, screen_rect, + previous_screen_rect, pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point), max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side), time, @@ -234,6 +238,11 @@ impl InputState { self.screen_rect } + #[inline(always)] + pub fn screen_rect_changed(&self) -> bool { + self.screen_rect != self.previous_screen_rect + } + /// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). /// * `zoom = 1`: no change /// * `zoom < 1`: pinch together @@ -958,6 +967,7 @@ impl InputState { scroll_delta, zoom_factor_delta, screen_rect, + previous_screen_rect, pixels_per_point, max_texture_side, time, @@ -993,6 +1003,7 @@ impl InputState { ui.label(format!("scroll_delta: {:?} points", scroll_delta)); ui.label(format!("zoom_factor_delta: {:4.2}x", zoom_factor_delta)); ui.label(format!("screen_rect: {:?} points", screen_rect)); + ui.label(format!("previous_screen_rect: {:?} points", previous_screen_rect)); ui.label(format!( "{} physical pixels for each logical point", pixels_per_point diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c9eaa1d4b70..d628bfc6912 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -570,6 +570,18 @@ impl<'t> TextEdit<'t> { ui.ctx().set_cursor_icon(CursorIcon::Text); } + // Update the InputState if we're interacting (E.g. updating seleciton or cursor position) + if interactive + && state.soft_keyboard_visible + && (response.drag_released() || response.clicked()) + { + update_text_input( + ui.ctx(), + state.cursor_range(&galley), + text.as_str().to_owned(), + ); + } + let mut cursor_range = None; let prev_cursor_range = state.cursor_range(&galley); if interactive && ui.memory(|mem| mem.has_focus(id)) { @@ -643,7 +655,16 @@ impl<'t> TextEdit<'t> { false }; - if ui.is_rect_visible(rect) { + if ui.memory(|memory| memory.lost_focus(id)) { + state.soft_keyboard_visible = false; + } + + + if ui.memory(|mem| mem.has_focus(id)) && ui.input(|i| i.screen_rect_changed()) { + ui.scroll_to_rect(rect, None); + } + + if ui.is_rect_visible(rect) || ui.memory(|mem| mem.has_focus(id)) { painter.galley(text_draw_pos, galley.clone()); if text.as_str().is_empty() && !hint_text.is_empty() { @@ -678,6 +699,16 @@ impl<'t> TextEdit<'t> { } if interactive { + // Send the text input only when the keyboard is initially shown. + if !state.soft_keyboard_visible { + update_text_input( + ui.ctx(), + state.cursor_range(&galley), + text.as_str().to_owned(), + ); + state.soft_keyboard_visible = true; + } + // eframe web uses `text_cursor_pos` when showing IME, // so only set it when text is editable and visible! // But `winit` and `egui_web` differs in how to set the @@ -848,6 +879,18 @@ fn mask_if_password(is_password: bool, text: &str) -> String { // ---------------------------------------------------------------------------- +fn update_text_input(ctx: &Context, cursor_range: Option, text: String) { + ctx.output_mut(|o| { + let selection = if let Some(cursor_range) = cursor_range { + (cursor_range.primary.ccursor.index, cursor_range.secondary.ccursor.index) + } else { + (0, 0) + }; + + o.surrounding_text = Some((text, selection)) + }); +} + #[cfg(feature = "accesskit")] fn ccursor_from_accesskit_text_position( id: Id, @@ -1038,6 +1081,23 @@ fn events( } } + Event::CompositionReplace { content, selection, compose_region } => { + text.replace(content); + + if let Some((start, end)) = compose_region { + let ccursor = CCursorRange::two( + CCursor::new(*start), + CCursor::new(*end), + ); + Some(ccursor) + } else { + Some(CCursorRange::two( + CCursor::new(selection.0), + CCursor::new(selection.1), + )) + } + } + #[cfg(feature = "accesskit")] Event::AccessKitActionRequest(accesskit::ActionRequest { action: accesskit::Action::SetTextSelection, diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index b4c408619f9..76e7b544421 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -48,6 +48,9 @@ pub struct TextEditState { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) has_ime: bool, + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) soft_keyboard_visible: bool, + // Visual offset when editing singleline text bigger than the width. #[cfg_attr(feature = "serde", serde(skip))] pub(crate) singleline_offset: f32, diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index af3eef61f50..d680f9c0ab9 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -65,6 +65,6 @@ env_logger = "0.10" # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.86" +wasm-bindgen = "0.2.86" wasm-bindgen-futures = "0.4" web-sys = "0.3"