diff --git a/CHANGELOG.md b/CHANGELOG.md index ab27f2d9..71afebd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusize zone. - On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them. - On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually. - **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants. diff --git a/examples/ime.rs b/examples/ime.rs index 3f33210e..d108c8fc 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -3,7 +3,7 @@ use log::LevelFilter; use simple_logger::SimpleLogger; use winit::{ - dpi::PhysicalPosition, + dpi::{PhysicalPosition, PhysicalSize}, event::{ElementState, Event, Ime, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{Key, KeyCode}, @@ -66,7 +66,7 @@ fn main() { ); ime_pos = cursor_position; if may_show_ime { - window.set_ime_position(ime_pos); + window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10)); } } Event::WindowEvent { @@ -76,7 +76,7 @@ fn main() { println!("{event:?}"); may_show_ime = event != Ime::Disabled; if may_show_ime { - window.set_ime_position(ime_pos); + window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10)); } } Event::WindowEvent { diff --git a/src/event.rs b/src/event.rs index 4111e1f6..e2f94151 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1034,7 +1034,7 @@ impl From for Modifiers { /// ``` /// /// Additionally, certain input devices are configured to display a candidate box that allow the user to select the -/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_position`].) +/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_cursor_area`].) /// /// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the following event /// sequence could be obtained: @@ -1058,7 +1058,7 @@ pub enum Ime { /// /// After getting this event you could receive [`Preedit`](Self::Preedit) and /// [`Commit`](Self::Commit) events. You should also start performing IME related requests - /// like [`Window::set_ime_position`]. + /// like [`Window::set_ime_cursor_area`]. Enabled, /// Notifies when a new composing text should be set at the cursor position. @@ -1079,7 +1079,7 @@ pub enum Ime { /// /// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or /// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should - /// also stop issuing IME related requests like [`Window::set_ime_position`] and clear pending + /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending /// preedit text. Disabled, } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index b48d3acc..e607f625 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -872,7 +872,7 @@ impl Window { pub fn set_window_icon(&self, _window_icon: Option) {} - pub fn set_ime_position(&self, _position: Position) {} + pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} pub fn set_ime_allowed(&self, _allowed: bool) {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index f773db5e..222c7a15 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -295,8 +295,8 @@ impl Inner { warn!("`Window::set_window_icon` is ignored on iOS") } - pub fn set_ime_position(&self, _position: Position) { - warn!("`Window::set_ime_position` is ignored on iOS") + pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { + warn!("`Window::set_ime_cursor_area` is ignored on iOS") } pub fn set_ime_allowed(&self, _allowed: bool) { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 03db21ed..5df360e8 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -512,8 +512,8 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, position: Position) { - x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) + pub fn set_ime_cursor_area(&self, position: Position, size: Size) { + x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size)) } #[inline] diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 26073da3..f6eb476c 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -534,12 +534,13 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, position: Position) { + pub fn set_ime_cursor_area(&self, position: Position, size: Size) { let window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() { let scale_factor = window_state.scale_factor(); let position = position.to_logical(scale_factor); - window_state.set_ime_position(position); + let size = size.to_logical(scale_factor); + window_state.set_ime_cursor_area(position, size); } } diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 12d55c96..5f1ee112 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -741,13 +741,14 @@ impl WindowState { } /// Set the IME position. - pub fn set_ime_position(&self, position: LogicalPosition) { + pub fn set_ime_cursor_area(&self, position: LogicalPosition, size: LogicalSize) { // XXX This won't fly unless user will have a way to request IME window per seat, since // the ime windows will be overlapping, but winit doesn't expose API to specify for // which seat we're setting IME position. let (x, y) = (position.x as i32, position.y as i32); + let (width, height) = (size.width as i32, size.height as i32); for text_input in self.text_inputs.iter() { - text_input.set_cursor_rectangle(x, y, 0, 0); + text_input.set_cursor_rectangle(x, y, width, height); text_input.commit(); } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index adbcf34e..550b612c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1510,7 +1510,7 @@ impl UnownedWindow { } #[inline] - pub fn set_ime_position(&self, spot: Position) { + pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) { let (x, y) = spot.to_physical::(self.scale_factor()).into(); let _ = self .ime_sender diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index d3b10831..b8e781d1 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -202,11 +202,15 @@ pub(crate) fn close_sync(window: &NSWindow) { }); } -pub(crate) fn set_ime_position_sync(window: &WinitWindow, logical_spot: LogicalPosition) { +pub(crate) fn set_ime_cursor_area_sync( + window: &WinitWindow, + logical_spot: LogicalPosition, + size: LogicalSize, +) { let window = MainThreadSafe(window); run_on_main(move || { // TODO(madsmtm): Remove the need for this - unsafe { Id::from_shared(window.view()) }.set_ime_position(logical_spot); + unsafe { Id::from_shared(window.view()) }.set_ime_cursor_area(logical_spot, size); }); } diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 0ae81874..d5375b26 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -122,6 +122,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode { pub(super) struct ViewState { pub cursor_state: Mutex, ime_position: LogicalPosition, + ime_size: LogicalSize, pub(super) modifiers: Modifiers, phys_modifiers: HashMap, tracking_rect: Option, @@ -167,6 +168,7 @@ declare_class!( let state = ViewState { cursor_state: Default::default(), ime_position: LogicalPosition::new(0.0, 0.0), + ime_size: Default::default(), modifiers: Default::default(), phys_modifiers: Default::default(), tracking_rect: None, @@ -416,11 +418,8 @@ declare_class!( let base_y = (content_rect.origin.y + content_rect.size.height) as f64; let x = base_x + self.state.ime_position.x; let y = base_y - self.state.ime_position.y; - // This is not ideal: We _should_ return a different position based on - // the currently selected character (which varies depending on the type - // and size of the character), but in the current `winit` API there is - // no way to express this. Same goes for the `NSSize`. - NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) + let LogicalSize { width, height } = self.state.ime_size; + NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) } #[sel(insertText:replacementRange:)] @@ -871,8 +870,13 @@ impl WinitView { } } - pub(super) fn set_ime_position(&mut self, position: LogicalPosition) { + pub(super) fn set_ime_cursor_area( + &mut self, + position: LogicalPosition, + size: LogicalSize, + ) { self.state.ime_position = position; + self.state.ime_size = size; let input_context = self.inputContext().expect("input context"); input_context.invalidateCharacterCoordinates(); } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 2ba6acaa..7e356eec 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1161,10 +1161,11 @@ impl WinitWindow { } #[inline] - pub fn set_ime_position(&self, spot: Position) { + pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); - util::set_ime_position_sync(self, logical_spot); + let size = size.to_logical(scale_factor); + util::set_ime_cursor_area_sync(self, logical_spot, size); } #[inline] diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 1afe5cdd..922c6c47 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -324,7 +324,7 @@ impl Window { pub fn set_window_icon(&self, _window_icon: Option) {} #[inline] - pub fn set_ime_position(&self, _position: Position) {} + pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} #[inline] pub fn set_ime_allowed(&self, _allowed: bool) {} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 6a379921..0d577b86 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -327,7 +327,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _position: Position) { + pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { // Currently a no-op as it does not seem there is good support for this on web } diff --git a/src/platform_impl/windows/ime.rs b/src/platform_impl/windows/ime.rs index 6e6d31ea..2f9320f8 100644 --- a/src/platform_impl/windows/ime.rs +++ b/src/platform_impl/windows/ime.rs @@ -1,12 +1,11 @@ use std::{ ffi::{c_void, OsString}, - mem::zeroed, os::windows::prelude::OsStringExt, ptr::null_mut, }; use windows_sys::Win32::{ - Foundation::POINT, + Foundation::{POINT, RECT}, Globalization::HIMC, UI::{ Input::Ime::{ @@ -19,7 +18,10 @@ use windows_sys::Win32::{ }, }; -use crate::{dpi::Position, platform::windows::HWND}; +use crate::{ + dpi::{Position, Size}, + platform::windows::HWND, +}; pub struct ImeContext { hwnd: HWND, @@ -109,17 +111,24 @@ impl ImeContext { } } - pub unsafe fn set_ime_position(&self, spot: Position, scale_factor: f64) { + pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) { if !ImeContext::system_has_ime() { return; } let (x, y) = spot.to_physical::(scale_factor).into(); + let (width, height): (i32, i32) = size.to_physical::(scale_factor).into(); + let rc_area = RECT { + left: x, + top: y, + right: x + width, + bottom: y - height, + }; let candidate_form = CANDIDATEFORM { dwIndex: 0, dwStyle: CFS_EXCLUDE, ptCurrentPos: POINT { x, y }, - rcArea: zeroed(), + rcArea: rc_area, }; ImmSetCandidateWindow(self.himc, &candidate_form); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 1556c1bb..23ec2947 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -722,9 +722,9 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, spot: Position) { + pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { unsafe { - ImeContext::current(self.hwnd()).set_ime_position(spot, self.scale_factor()); + ImeContext::current(self.hwnd()).set_ime_cursor_area(spot, size, self.scale_factor()); } } diff --git a/src/window.rs b/src/window.rs index 55d9f704..1b370cd1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1017,37 +1017,45 @@ impl Window { self.window.set_window_icon(window_icon) } - /// Sets location of IME candidate box in client area coordinates relative to the top left. + /// Set the IME cursor editing area, where the `position` is the top left corner of that area + /// and `size` is the size of this area starting from the position. An example of such area + /// could be a input field in the UI or line in the editor. /// - /// This is the window / popup / overlay that allows you to select the desired characters. - /// The look of this box may differ between input devices, even on the same platform. + /// The windowing system could place a candidate box close to that area, but try to not obscure + /// the specified area, so the user input to it stays visible. + /// + /// The candidate box is the window / popup / overlay that allows you to select the desired + /// characters. The look of this box may differ between input devices, even on the same + /// platform. /// /// (Apple's official term is "candidate window", see their [chinese] and [japanese] guides). /// /// ## Example /// /// ```no_run - /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; + /// # use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the position in logical dimensions like this: - /// window.set_ime_position(LogicalPosition::new(400.0, 200.0)); + /// window.set_ime_cursor_area(LogicalPosition::new(400.0, 200.0), LogicalSize::new(100, 100)); /// /// // Or specify the position in physical dimensions like this: - /// window.set_ime_position(PhysicalPosition::new(400, 200)); + /// window.set_ime_cursor_area(PhysicalPosition::new(400, 200), PhysicalSize::new(100, 100)); /// ``` /// /// ## Platform-specific /// + /// - **X11:** - area is not supported, only position. /// - **iOS / Android / Web / Orbital:** Unsupported. /// /// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0 /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 #[inline] - pub fn set_ime_position>(&self, position: P) { - self.window.set_ime_position(position.into()) + pub fn set_ime_cursor_area, S: Into>(&self, position: P, size: S) { + self.window + .set_ime_cursor_area(position.into(), size.into()) } /// Sets whether the window should get IME events