Simplify InputMethod API with only two states

Co-authored-by: rhysd <lin90162@yahoo.co.jp>
Co-authored-by: KENZ <KENZ.gelsoft@gmail.com>
This commit is contained in:
Héctor Ramón Jiménez 2025-02-12 08:46:35 +01:00
parent 97f1db3783
commit 7979125ed7
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
8 changed files with 113 additions and 100 deletions

View file

@ -6,14 +6,10 @@ use std::ops::Range;
/// The input method strategy of a widget.
#[derive(Debug, Clone, PartialEq)]
pub enum InputMethod<T = String> {
/// No input method strategy has been specified.
None,
/// No input method is allowed.
/// Input method is disabled.
Disabled,
/// Input methods are allowed, but not open yet.
Allowed,
/// Input method is open.
Open {
/// Input method is enabled.
Enabled {
/// The position at which the input method dialog should be placed.
position: Point,
/// The [`Purpose`] of the input method.
@ -91,13 +87,13 @@ impl InputMethod {
/// # use iced_core::input_method::{InputMethod, Purpose, Preedit};
/// # use iced_core::Point;
///
/// let open = InputMethod::Open {
/// let open = InputMethod::Enabled {
/// position: Point::ORIGIN,
/// purpose: Purpose::Normal,
/// preedit: Some(Preedit { content: "1".to_owned(), selection: None, text_size: None }),
/// };
///
/// let open_2 = InputMethod::Open {
/// let open_2 = InputMethod::Enabled {
/// position: Point::ORIGIN,
/// purpose: Purpose::Secure,
/// preedit: Some(Preedit { content: "2".to_owned(), selection: None, text_size: None }),
@ -105,12 +101,6 @@ impl InputMethod {
///
/// let mut ime = InputMethod::Disabled;
///
/// ime.merge(&InputMethod::<String>::Allowed);
/// assert_eq!(ime, InputMethod::Allowed);
///
/// ime.merge(&InputMethod::<String>::Disabled);
/// assert_eq!(ime, InputMethod::Allowed);
///
/// ime.merge(&open);
/// assert_eq!(ime, open);
///
@ -118,22 +108,16 @@ impl InputMethod {
/// assert_eq!(ime, open);
/// ```
pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) {
match (&self, other) {
(InputMethod::Open { .. }, _)
| (
InputMethod::Allowed,
InputMethod::None | InputMethod::Disabled,
)
| (InputMethod::Disabled, InputMethod::None) => {}
_ => {
if let InputMethod::Enabled { .. } = self {
return;
}
*self = other.to_owned();
}
}
}
/// Returns true if the [`InputMethod`] is open.
pub fn is_open(&self) -> bool {
matches!(self, Self::Open { .. })
pub fn is_enabled(&self) -> bool {
matches!(self, Self::Enabled { .. })
}
}
@ -144,14 +128,12 @@ impl<T> InputMethod<T> {
T: AsRef<str>,
{
match self {
Self::None => InputMethod::None,
Self::Disabled => InputMethod::Disabled,
Self::Allowed => InputMethod::Allowed,
Self::Open {
Self::Enabled {
position,
purpose,
preedit,
} => InputMethod::Open {
} => InputMethod::Enabled {
position: *position,
purpose: *purpose,
preedit: preedit.as_ref().map(Preedit::to_owned),

View file

@ -27,7 +27,7 @@ impl<'a, Message> Shell<'a, Message> {
redraw_request: window::RedrawRequest::Wait,
is_layout_invalid: false,
are_widgets_invalid: false,
input_method: InputMethod::None,
input_method: InputMethod::Disabled,
}
}

View file

@ -11,7 +11,7 @@ use cosmic_text::Edit as _;
use std::borrow::Cow;
use std::fmt;
use std::sync::{self, Arc};
use std::sync::{self, Arc, RwLock};
/// A multi-line text editor.
#[derive(Debug, PartialEq)]
@ -19,6 +19,7 @@ pub struct Editor(Option<Arc<Internal>>);
struct Internal {
editor: cosmic_text::Editor<'static>,
cursor: RwLock<Option<Cursor>>,
font: Font,
bounds: Size,
topmost_line_changed: Option<usize>,
@ -114,10 +115,14 @@ impl editor::Editor for Editor {
fn cursor(&self) -> editor::Cursor {
let internal = self.internal();
if let Ok(Some(cursor)) = internal.cursor.read().as_deref() {
return cursor.clone();
}
let cursor = internal.editor.cursor();
let buffer = buffer_from_editor(&internal.editor);
match internal.editor.selection_bounds() {
let cursor = match internal.editor.selection_bounds() {
Some((start, end)) => {
let line_height = buffer.metrics().line_height;
let selected_lines = end.line - start.line + 1;
@ -237,7 +242,12 @@ impl editor::Editor for Editor {
- buffer.scroll().vertical,
))
}
}
};
*internal.cursor.write().expect("Write to cursor cache") =
Some(cursor.clone());
cursor
}
fn cursor_position(&self) -> (usize, usize) {
@ -259,6 +269,13 @@ impl editor::Editor for Editor {
let editor = &mut internal.editor;
// Clear cursor cache
let _ = internal
.cursor
.write()
.expect("Write to cursor cache")
.take();
match action {
// Motion events
Action::Move(motion) => {
@ -527,6 +544,13 @@ impl editor::Editor for Editor {
internal.editor.shape_as_needed(font_system.raw(), false);
// Clear cursor cache
let _ = internal
.cursor
.write()
.expect("Write to cursor cache")
.take();
self.0 = Some(Arc::new(internal));
}
@ -635,6 +659,7 @@ impl Default for Internal {
line_height: 1.0,
},
)),
cursor: RwLock::new(None),
font: Font::default(),
bounds: Size::ZERO,
topmost_line_changed: None,

View file

@ -189,7 +189,7 @@ where
let mut outdated = false;
let mut redraw_request = window::RedrawRequest::Wait;
let mut input_method = InputMethod::None;
let mut input_method = InputMethod::Disabled;
let mut manual_overlay = ManuallyDrop::new(
self.root

View file

@ -729,7 +729,7 @@ where
_ => mouse::Cursor::Unavailable,
};
let had_input_method = shell.input_method().is_open();
let had_input_method = shell.input_method().is_enabled();
let translation =
state.translation(self.direction, bounds, content_bounds);
@ -750,7 +750,7 @@ where
);
if !had_input_method {
if let InputMethod::Open { position, .. } =
if let InputMethod::Enabled { position, .. } =
shell.input_method_mut()
{
*position = *position - translation;

View file

@ -339,10 +339,6 @@ where
return InputMethod::Disabled;
};
let Some(preedit) = &state.preedit else {
return InputMethod::Allowed;
};
let bounds = layout.bounds();
let internal = self.content.0.borrow_mut();
@ -363,10 +359,10 @@ where
let position =
cursor + translation + Vector::new(0.0, f32::from(line_height));
InputMethod::Open {
InputMethod::Enabled {
position,
purpose: input_method::Purpose::Normal,
preedit: Some(preedit.as_ref()),
preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
}
}
}

View file

@ -406,10 +406,6 @@ where
return InputMethod::Disabled;
};
let Some(preedit) = &state.is_ime_open else {
return InputMethod::Allowed;
};
let secure_value = self.is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(value);
@ -433,14 +429,14 @@ where
let x = (text_bounds.x + cursor_x).floor() - scroll_offset
+ alignment_offset;
InputMethod::Open {
InputMethod::Enabled {
position: Point::new(x, text_bounds.y + text_bounds.height),
purpose: if self.is_secure {
input_method::Purpose::Secure
} else {
input_method::Purpose::Normal
},
preedit: Some(preedit.as_ref()),
preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
}
}
@ -584,7 +580,7 @@ where
let draw = |renderer: &mut Renderer, viewport| {
let paragraph = if text.is_empty()
&& state
.is_ime_open
.preedit
.as_ref()
.map(|preedit| preedit.content.is_empty())
.unwrap_or(true)
@ -1260,7 +1256,7 @@ where
input_method::Event::Opened | input_method::Event::Closed => {
let state = state::<Renderer>(tree);
state.is_ime_open =
state.preedit =
matches!(event, input_method::Event::Opened)
.then(input_method::Preedit::new);
@ -1270,7 +1266,7 @@ where
let state = state::<Renderer>(tree);
if state.is_focused.is_some() {
state.is_ime_open = Some(input_method::Preedit {
state.preedit = Some(input_method::Preedit {
content: content.to_owned(),
selection: selection.clone(),
text_size: self.size,
@ -1340,6 +1336,12 @@ where
millis_until_redraw as u64,
),
);
shell.request_input_method(&self.input_method(
state,
layout,
&self.value,
));
}
}
}
@ -1363,12 +1365,6 @@ where
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.last_status = Some(status);
shell.request_input_method(&self.input_method(
state,
layout,
&self.value,
));
} else if self
.last_status
.is_some_and(|last_status| status != last_status)
@ -1528,9 +1524,9 @@ pub struct State<P: text::Paragraph> {
placeholder: paragraph::Plain<P>,
icon: paragraph::Plain<P>,
is_focused: Option<Focus>,
is_ime_open: Option<input_method::Preedit>,
is_dragging: bool,
is_pasting: Option<Value>,
preedit: Option<input_method::Preedit>,
last_click: Option<mouse::Click>,
cursor: Cursor,
keyboard_modifiers: keyboard::Modifiers,

View file

@ -75,6 +75,7 @@ where
mouse_interaction: mouse::Interaction::None,
redraw_at: None,
preedit: None,
ime_state: None,
},
);
@ -166,6 +167,7 @@ where
pub renderer: P::Renderer,
pub redraw_at: Option<Instant>,
preedit: Option<Preedit<P::Renderer>>,
ime_state: Option<(Point, input_method::Purpose)>,
}
impl<P, C> Window<P, C>
@ -206,27 +208,15 @@ where
pub fn request_input_method(&mut self, input_method: InputMethod) {
match input_method {
InputMethod::None => {}
InputMethod::Disabled => {
self.raw.set_ime_allowed(false);
self.disable_ime();
}
InputMethod::Allowed | InputMethod::Open { .. } => {
self.raw.set_ime_allowed(true);
}
}
if let InputMethod::Open {
InputMethod::Enabled {
position,
purpose,
preedit,
} = input_method
{
self.raw.set_ime_cursor_area(
LogicalPosition::new(position.x, position.y),
LogicalSize::new(10, 10), // TODO?
);
self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
} => {
self.enable_ime(position, purpose);
if let Some(preedit) = preedit {
if preedit.content.is_empty() {
@ -250,8 +240,7 @@ where
self.preedit = Some(overlay);
}
}
} else {
self.preedit = None;
}
}
}
@ -268,6 +257,31 @@ where
);
}
}
fn enable_ime(&mut self, position: Point, purpose: input_method::Purpose) {
if self.ime_state.is_none() {
self.raw.set_ime_allowed(true);
}
if self.ime_state != Some((position, purpose)) {
self.raw.set_ime_cursor_area(
LogicalPosition::new(position.x, position.y),
LogicalSize::new(10, 10), // TODO?
);
self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
self.ime_state = Some((position, purpose));
}
}
fn disable_ime(&mut self) {
if self.ime_state.is_some() {
self.raw.set_ime_allowed(false);
self.ime_state = None;
}
self.preedit = None;
}
}
struct Preedit<Renderer>