winit-core/window: wrap ImeCapabilities in struct

To prevent user from using `::all()` and thus writing not forward
compatible code wrap the bitflags struct and provide simpler interface
to it.
This commit is contained in:
DorotaC 2025-06-29 06:53:47 +02:00 committed by GitHub
parent 08907148ec
commit abed32eb80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 86 additions and 46 deletions

View file

@ -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();
};

View file

@ -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);

View file

@ -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<Self> {
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)

View file

@ -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<u32> = position.to_logical(scale_factor);
let size: LogicalSize<u32> = size.to_logical(scale_factor);
self.cursor_area = (position, size);
@ -280,11 +280,11 @@ impl ClientState {
}
pub fn content_type(&self) -> Option<ContentType> {
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<u32>, LogicalSize<u32>)> {
self.capabilities.contains(ImeCapabilities::CURSOR_AREA).then_some(self.cursor_area)
self.capabilities.cursor_area().then_some(self.cursor_area)
}
}

View file

@ -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,

View file

@ -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.");