mirror of
https://github.com/rust-windowing/winit.git
synced 2026-09-02 23:00:05 -04:00
winit-core/window: add Window::request_ime_update
Allow updating IME state atomically to make it easier for platforms where it's atomic by its nature, like Wayland. The old API is marked as deprecated and is routed to the new atomic API. Co-authored-by: dcz <gilapfco.dcz@porcupinefactory.org>
This commit is contained in:
@@ -26,9 +26,11 @@ use keyboard::{KeyboardData, KeyboardState};
|
||||
pub use pointer::relative_pointer::RelativePointerState;
|
||||
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
|
||||
use text_input::TextInputData;
|
||||
pub use text_input::{TextInputState, ZwpTextInputV3Ext};
|
||||
pub use text_input::{ClientState as TextInputClientState, TextInputState};
|
||||
use touch::TouchPoint;
|
||||
|
||||
pub(crate) use crate::seat::text_input::ZwpTextInputV3Ext;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WinitSeatState {
|
||||
/// The pointer bound on the seat.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use dpi::{LogicalPosition, LogicalSize};
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
@@ -8,8 +9,9 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_mana
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
|
||||
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
use tracing::warn;
|
||||
use winit_core::event::{Ime, WindowEvent};
|
||||
use winit_core::window::ImePurpose;
|
||||
use winit_core::window::{ImeCapabilities, ImePurpose, ImeRequestData};
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
@@ -69,10 +71,10 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
if window.ime_allowed() {
|
||||
text_input.enable();
|
||||
text_input.set_content_type_by_purpose(window.ime_purpose());
|
||||
text_input.commit();
|
||||
if let Some(text_input_state) = window.text_input_state() {
|
||||
text_input.set_state(Some(text_input_state), true);
|
||||
// The input method doesn't have to reply anything, so a synthetic event
|
||||
// carrying an empty state notifies the application about its presence.
|
||||
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
@@ -156,17 +158,40 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
}
|
||||
|
||||
pub trait ZwpTextInputV3Ext {
|
||||
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
|
||||
/// Applies the entire state atomically to the input method. It will skip the "enable" request
|
||||
/// if `already_enabled` is `true`.
|
||||
fn set_state(&self, state: Option<&ClientState>, send_enable: bool);
|
||||
}
|
||||
|
||||
impl ZwpTextInputV3Ext for ZwpTextInputV3 {
|
||||
fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
|
||||
let (hint, purpose) = match purpose {
|
||||
ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password),
|
||||
ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal),
|
||||
_ => (ContentHint::None, ContentPurpose::Normal),
|
||||
fn set_state(&self, state: Option<&ClientState>, send_enable: bool) {
|
||||
let state = match state {
|
||||
Some(state) => state,
|
||||
None => {
|
||||
self.disable();
|
||||
self.commit();
|
||||
return;
|
||||
},
|
||||
};
|
||||
self.set_content_type(hint, purpose);
|
||||
|
||||
if send_enable {
|
||||
self.enable();
|
||||
}
|
||||
|
||||
if let Some(content_type) = state.content_type() {
|
||||
self.set_content_type(content_type.hint, content_type.purpose);
|
||||
}
|
||||
|
||||
if let Some((position, size)) = state.cursor_area() {
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
let (width, height) = (size.width as i32, size.height as i32);
|
||||
// The same cursor can be applied on different seats.
|
||||
// It's the compositor's responsibility to make sure that any present popups don't
|
||||
// overlap.
|
||||
self.set_cursor_rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
self.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,11 +214,105 @@ pub struct TextInputDataInner {
|
||||
}
|
||||
|
||||
/// The state of the preedit.
|
||||
#[derive(Clone)]
|
||||
struct Preedit {
|
||||
text: String,
|
||||
cursor_begin: Option<usize>,
|
||||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
/// State change requested by the application.
|
||||
///
|
||||
/// This is a version that uses text_input abstractions translated from the ones used in
|
||||
/// winit::core::window::ImeStateChange.
|
||||
///
|
||||
/// Fields that are initially set to None are unsupported capabilities
|
||||
/// and trying to set them raises an error.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ClientState {
|
||||
capabilities: ImeCapabilities,
|
||||
|
||||
content_type: ContentType,
|
||||
|
||||
/// The IME cursor area which should not be covered by the input method popup.
|
||||
cursor_area: (LogicalPosition<u32>, LogicalSize<u32>),
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
pub fn new(
|
||||
capabilities: ImeCapabilities,
|
||||
request_data: ImeRequestData,
|
||||
scale_factor: f64,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
capabilities,
|
||||
content_type: Default::default(),
|
||||
cursor_area: Default::default(),
|
||||
};
|
||||
|
||||
this.update(request_data, scale_factor);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn capabilities(&self) -> ImeCapabilities {
|
||||
self.capabilities
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.content_type = purpose.into();
|
||||
} else {
|
||||
warn!("discarding ImePurpose update without capability enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((position, size)) = request_data.cursor_area {
|
||||
if self.capabilities.contains(ImeCapabilities::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);
|
||||
} else {
|
||||
warn!("discarding IME cursor area update without capability enabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_type(&self) -> Option<ContentType> {
|
||||
self.capabilities.contains(ImeCapabilities::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments to content_type
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ContentType {
|
||||
/// Text input purpose
|
||||
purpose: ContentPurpose,
|
||||
hint: ContentHint,
|
||||
}
|
||||
|
||||
impl From<ImePurpose> for ContentType {
|
||||
fn from(purpose: ImePurpose) -> Self {
|
||||
let (hint, purpose) = match purpose {
|
||||
ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password),
|
||||
ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal),
|
||||
_ => return Default::default(),
|
||||
};
|
||||
|
||||
Self { hint, purpose }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContentType {
|
||||
fn default() -> Self {
|
||||
ContentType { purpose: ContentPurpose::Normal, hint: ContentHint::None }
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);
|
||||
|
||||
Reference in New Issue
Block a user