diff --git a/examples/application.rs b/examples/application.rs index d2abca07..3b181fff 100644 --- a/examples/application.rs +++ b/examples/application.rs @@ -669,7 +669,11 @@ impl WindowState { let request_data = ImeRequestData::default() .with_purpose(ImePurpose::Normal) .with_cursor_area(LogicalPosition { x: 0, y: 0 }.into(), IME_CURSOR_SIZE.into()); - let enable_request = ImeEnableRequest::new(ImeCapabilities::all(), request_data).unwrap(); + let enable_request = ImeEnableRequest::new( + ImeCapabilities::new().with_purpose().with_cursor_area(), + request_data, + ) + .unwrap(); let enable_ime = ImeRequest::Enable(enable_request); // Initial update @@ -714,8 +718,11 @@ impl WindowState { .unwrap_or(LogicalPosition { x: 0, y: 0 }.into()); let request_data = ImeRequestData::default().with_cursor_area(cursor_pos, IME_CURSOR_SIZE.into()); - let enable_request = - ImeEnableRequest::new(ImeCapabilities::all(), request_data).unwrap(); + let enable_request = ImeEnableRequest::new( + ImeCapabilities::new().with_purpose().with_cursor_area(), + request_data, + ) + .unwrap(); self.window.request_ime_update(ImeRequest::Enable(enable_request)).unwrap(); }; diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index f8f06016..9d9825f4 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -1698,7 +1698,7 @@ impl WindowDelegate { }; if let Some((spot, size)) = request_data.cursor_area { - if self.view().ime_capabilities().unwrap().contains(ImeCapabilities::CURSOR_AREA) { + if self.view().ime_capabilities().unwrap().cursor_area() { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y); diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 8926e948..bea235ee 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -1,6 +1,7 @@ //! The [`Window`] trait and associated types. use std::fmt; +use bitflags::bitflags; use cursor_icon::CursorIcon; use dpi::{ LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size, @@ -1100,11 +1101,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 #[deprecated = "use Window::request_ime_update instead"] fn set_ime_cursor_area(&self, position: Position, size: Size) { - if self - .ime_capabilities() - .map(|caps| caps.contains(ImeCapabilities::CURSOR_AREA)) - .unwrap_or(false) - { + if self.ime_capabilities().map(|caps| caps.cursor_area()).unwrap_or(false) { let _ = self.request_ime_update(ImeRequest::Update( ImeRequestData::default().with_cursor_area(position, size), )); @@ -1138,7 +1135,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { let action = if allowed { let position = LogicalPosition::new(0, 0); let size = LogicalSize::new(0, 0); - let ime_caps = ImeCapabilities::CURSOR_AREA | ImeCapabilities::PURPOSE; + let ime_caps = ImeCapabilities::new().with_purpose().with_cursor_area(); let request_data = ImeRequestData { purpose: Some(ImePurpose::Normal), // WARNING: there's nothing sensible to use here by default. @@ -1162,11 +1159,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[deprecated = "use Window::request_ime_update instead"] fn set_ime_purpose(&self, purpose: ImePurpose) { - if self - .ime_capabilities() - .map(|caps| caps.contains(ImeCapabilities::PURPOSE)) - .unwrap_or(false) - { + if self.ime_capabilities().map(|caps| caps.purpose()).unwrap_or(false) { let _ = self.request_ime_update(ImeRequest::Update(ImeRequestData { purpose: Some(purpose), ..ImeRequestData::default() @@ -1197,7 +1190,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// // Clear previous state by switching off IME /// window.request_ime_update(ImeRequest::Disable).expect("Disable cannot fail"); /// - /// let ime_caps = ImeCapabilities::CURSOR_AREA | ImeCapabilities::PURPOSE; + /// let ime_caps = ImeCapabilities::new().with_cursor_area().with_purpose(); /// let request_data = ImeRequestData::default() /// .with_purpose(ImePurpose::Normal) /// .with_cursor_area(cursor_pos, cursor_size); @@ -1666,12 +1659,11 @@ impl ImeEnableRequest { /// This will return [`None`] if some capability was requested but its initial value was not /// set by the user or value was set by the user, but capability not requested. pub const fn new(capabilities: ImeCapabilities, request_data: ImeRequestData) -> Option { - if capabilities.contains(ImeCapabilities::CURSOR_AREA) ^ request_data.cursor_area.is_some() - { + if capabilities.cursor_area() ^ request_data.cursor_area.is_some() { return None; } - if capabilities.contains(ImeCapabilities::PURPOSE) ^ request_data.purpose.is_some() { + if capabilities.purpose() ^ request_data.purpose.is_some() { return None; } @@ -1694,22 +1686,58 @@ impl ImeEnableRequest { } } -bitflags::bitflags! { - /// IME capabilities supported by client. +/// IME capabilities supported by client. +/// +/// For example, if the client doesn't support [`ImeCapabilities::cursor_area()`], then not enabling +/// it will make IME hide the popup window instead of placing it arbitrary over the +/// client's window surface. +/// +/// When the capability is not enabled or not supported by the IME, trying to update its' +/// corresponding data with [`ImeRequest`] will be ignored. +/// +/// New capabilities may be added to this struct in the future. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct ImeCapabilities(ImeCapabilitiesFlags); + +impl ImeCapabilities { + /// Returns a new empty set of capabilities. + pub fn new() -> Self { + Self::default() + } + + /// Marks `purpose` as supported. /// - /// For example, if the client doesn't support [`ImeCapabilities::CURSOR_AREA`] not enabling - /// it will make IME hide the popup window instead of placing it arbitrary over the - /// client's window surface. + /// For more details see [`ImeRequestData::with_purpose`]. + pub const fn with_purpose(self) -> Self { + Self(self.0.union(ImeCapabilitiesFlags::PURPOSE)) + } + + /// Returns `true` if `purpose` is supported. + pub const fn purpose(&self) -> bool { + self.0.contains(ImeCapabilitiesFlags::PURPOSE) + } + + /// Marks `cursor_area` as supported. /// - /// When the capability is not enabled or not supported by the IME, trying to update its' - /// corresponding data with [`ImeRequest`] will be ignored. - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub struct ImeCapabilities: u32 { + /// For more details see [`ImeRequestData::with_cursor_area`]. + pub const fn with_cursor_area(self) -> Self { + Self(self.0.union(ImeCapabilitiesFlags::CURSOR_AREA)) + } + + /// Returns `true` if `cursor_area` is supported. + pub const fn cursor_area(&self) -> bool { + self.0.contains(ImeCapabilitiesFlags::CURSOR_AREA) + } +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] + pub(crate) struct ImeCapabilitiesFlags : u8 { /// Client supports setting IME purpose. - const PURPOSE = 0b1; + const PURPOSE = 1 << 0; /// Client supports reporting cursor area for IME popup to /// appear. - const CURSOR_AREA = 0b10; + const CURSOR_AREA = 1 << 1; } } @@ -1856,20 +1884,25 @@ mod tests { let position: Position = LogicalPosition::new(0, 0).into(); let size: Size = LogicalSize::new(0, 0).into(); - assert!(ImeEnableRequest::new(ImeCapabilities::CURSOR_AREA, ImeRequestData::default()) - .is_none()); - assert!( - ImeEnableRequest::new(ImeCapabilities::PURPOSE, ImeRequestData::default()).is_none() - ); + assert!(ImeEnableRequest::new( + ImeCapabilities::new().with_cursor_area(), + ImeRequestData::default() + ) + .is_none()); + assert!(ImeEnableRequest::new( + ImeCapabilities::new().with_purpose(), + ImeRequestData::default() + ) + .is_none()); assert!(ImeEnableRequest::new( - ImeCapabilities::CURSOR_AREA, + ImeCapabilities::new().with_cursor_area(), ImeRequestData::default().with_purpose(ImePurpose::Normal) ) .is_none()); assert!(ImeEnableRequest::new( - ImeCapabilities::empty(), + ImeCapabilities::new(), ImeRequestData::default() .with_purpose(ImePurpose::Normal) .with_cursor_area(position, size) @@ -1877,7 +1910,7 @@ mod tests { .is_none()); assert!(ImeEnableRequest::new( - ImeCapabilities::CURSOR_AREA, + ImeCapabilities::new().with_cursor_area(), ImeRequestData::default() .with_purpose(ImePurpose::Normal) .with_cursor_area(position, size) @@ -1885,13 +1918,13 @@ mod tests { .is_none()); assert!(ImeEnableRequest::new( - ImeCapabilities::CURSOR_AREA, + ImeCapabilities::new().with_cursor_area(), ImeRequestData::default().with_cursor_area(position, size) ) .is_some()); assert!(ImeEnableRequest::new( - ImeCapabilities::all(), + ImeCapabilities::new().with_purpose().with_cursor_area(), ImeRequestData::default() .with_purpose(ImePurpose::Normal) .with_cursor_area(position, size) diff --git a/winit-wayland/src/seat/text_input/mod.rs b/winit-wayland/src/seat/text_input/mod.rs index 02f8a9fb..44e3e5ec 100644 --- a/winit-wayland/src/seat/text_input/mod.rs +++ b/winit-wayland/src/seat/text_input/mod.rs @@ -261,7 +261,7 @@ impl ClientState { /// Updates the fields of the state which are present in update_fields. pub fn update(&mut self, request_data: ImeRequestData, scale_factor: f64) { if let Some(purpose) = request_data.purpose { - if self.capabilities.contains(ImeCapabilities::PURPOSE) { + if self.capabilities.purpose() { self.content_type = purpose.into(); } else { warn!("discarding ImePurpose update without capability enabled."); @@ -269,7 +269,7 @@ impl ClientState { } if let Some((position, size)) = request_data.cursor_area { - if self.capabilities.contains(ImeCapabilities::CURSOR_AREA) { + if self.capabilities.cursor_area() { let position: LogicalPosition = position.to_logical(scale_factor); let size: LogicalSize = size.to_logical(scale_factor); self.cursor_area = (position, size); @@ -280,11 +280,11 @@ impl ClientState { } pub fn content_type(&self) -> Option { - self.capabilities.contains(ImeCapabilities::PURPOSE).then_some(self.content_type) + self.capabilities.purpose().then_some(self.content_type) } pub fn cursor_area(&self) -> Option<(LogicalPosition, LogicalSize)> { - self.capabilities.contains(ImeCapabilities::CURSOR_AREA).then_some(self.cursor_area) + self.capabilities.cursor_area().then_some(self.cursor_area) } } diff --git a/winit-win32/src/window.rs b/winit-win32/src/window.rs index 2286a319..4b1d4551 100644 --- a/winit-win32/src/window.rs +++ b/winit-win32/src/window.rs @@ -1051,7 +1051,7 @@ impl CoreWindow for Window { }; if let Some((spot, size)) = request_data.cursor_area { - if capabilities.contains(ImeCapabilities::CURSOR_AREA) { + if capabilities.cursor_area() { let scale_factor = state.scale_factor; ImeContext::current(window.hwnd()).set_ime_cursor_area( spot, diff --git a/winit-x11/src/window.rs b/winit-x11/src/window.rs index a9cc2191..7dacfcd6 100644 --- a/winit-x11/src/window.rs +++ b/winit-x11/src/window.rs @@ -2112,7 +2112,7 @@ impl UnownedWindow { }; if let Some((position, size)) = state.cursor_area { - if capabilities.contains(ImeCapabilities::CURSOR_AREA) { + if capabilities.cursor_area() { self.set_ime_cursor_area(position, size); } else { warn!("discarding IME cursor area update without capability enabled.");