Refactor and simplify input_method API
This commit is contained in:
parent
d5ee9c2795
commit
ae10adda74
19 changed files with 540 additions and 472 deletions
|
|
@ -1,17 +1,112 @@
|
|||
//! Listen to input method events.
|
||||
use crate::Point;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
/// The input method strategy of a widget.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InputMethod<T = String> {
|
||||
/// No input method is allowed.
|
||||
Disabled,
|
||||
/// Input methods are allowed, but not open yet.
|
||||
Allowed,
|
||||
/// Input method is open.
|
||||
Open {
|
||||
/// The position at which the input method dialog should be placed.
|
||||
position: Point,
|
||||
/// The [`Purpose`] of the input method.
|
||||
purpose: Purpose,
|
||||
/// The preedit to overlay on top of the input method dialog, if needed.
|
||||
///
|
||||
/// Ideally, your widget will show pre-edits on-the-spot; but, since that can
|
||||
/// be tricky, you can instead provide the current pre-edit here and the
|
||||
/// runtime will display it as an overlay (i.e. "Over-the-spot IME").
|
||||
preedit: Option<T>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The purpose of an [`InputMethod`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Purpose {
|
||||
/// No special hints for the IME (default).
|
||||
#[default]
|
||||
Normal,
|
||||
/// The IME is used for secure input (e.g. passwords).
|
||||
Secure,
|
||||
/// The IME is used to input into a terminal.
|
||||
///
|
||||
/// For example, that could alter OSK on Wayland to show extra buttons.
|
||||
Terminal,
|
||||
}
|
||||
|
||||
impl InputMethod {
|
||||
/// Merges two [`InputMethod`] strategies, prioritizing the second one when both ready:
|
||||
/// ```
|
||||
/// # use iced_core::input_method::{InputMethod, Purpose};
|
||||
/// # use iced_core::Point;
|
||||
///
|
||||
/// let open = InputMethod::Open {
|
||||
/// position: Point::ORIGIN,
|
||||
/// purpose: Purpose::Normal,
|
||||
/// preedit: None,
|
||||
/// };
|
||||
///
|
||||
/// let open_2 = InputMethod::Open {
|
||||
/// position: Point::ORIGIN,
|
||||
/// purpose: Purpose::Secure,
|
||||
/// preedit: None,
|
||||
/// };
|
||||
///
|
||||
/// 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);
|
||||
///
|
||||
/// ime.merge(&open_2);
|
||||
/// assert_eq!(ime, open_2);
|
||||
/// ```
|
||||
pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) {
|
||||
match other {
|
||||
InputMethod::Disabled => {}
|
||||
InputMethod::Open {
|
||||
position,
|
||||
purpose,
|
||||
preedit,
|
||||
} => {
|
||||
*self = Self::Open {
|
||||
position: *position,
|
||||
purpose: *purpose,
|
||||
preedit: preedit
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
.map(str::to_owned),
|
||||
};
|
||||
}
|
||||
InputMethod::Allowed if matches!(self, Self::Disabled) => {
|
||||
*self = Self::Allowed;
|
||||
}
|
||||
InputMethod::Allowed => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
|
||||
///
|
||||
/// This is also called a "composition event".
|
||||
///
|
||||
/// Most keypresses using a latin-like keyboard layout simply generate a
|
||||
/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
|
||||
/// unicode character that the user might want to type
|
||||
/// - so the solution operating systems employ is to allow the user to type these using _a sequence
|
||||
/// of keypresses_ instead.
|
||||
/// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed).
|
||||
/// However, one couldn't possibly have a key for every single
|
||||
/// unicode character that the user might want to type. The solution operating systems employ is
|
||||
/// to allow the user to type these using _a sequence of keypresses_ instead.
|
||||
///
|
||||
/// A prominent example of this is accents - many keyboard layouts allow you to first click the
|
||||
/// A prominent example of this is accents—many keyboard layouts allow you to first click the
|
||||
/// "accent key", and then the character you want to apply the accent to. In this case, some
|
||||
/// platforms will generate the following event sequence:
|
||||
///
|
||||
|
|
@ -19,13 +114,13 @@ use std::ops::Range;
|
|||
/// // Press "`" key
|
||||
/// Ime::Preedit("`", Some((0, 0)))
|
||||
/// // Press "E" key
|
||||
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
|
||||
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
|
||||
/// Ime::Commit("é")
|
||||
/// ```
|
||||
///
|
||||
/// 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_cursor_area`].)
|
||||
/// [`Shell::request_input_method`](crate::Shell::request_input_method).)
|
||||
///
|
||||
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
|
||||
/// following event sequence could be obtained:
|
||||
|
|
@ -40,17 +135,19 @@ use std::ops::Range;
|
|||
/// // Press space key
|
||||
/// Ime::Preedit("啊b", Some((3, 3)))
|
||||
/// // Press space key
|
||||
/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
|
||||
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
|
||||
/// Ime::Commit("啊不")
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Event {
|
||||
/// Notifies when the IME was enabled.
|
||||
/// Notifies when the IME was opened.
|
||||
///
|
||||
/// 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_cursor_area`].
|
||||
Enabled,
|
||||
/// like [`Shell::request_input_method`].
|
||||
///
|
||||
/// [`Shell::request_input_method`]: crate::Shell::request_input_method
|
||||
Opened,
|
||||
|
||||
/// Notifies when a new composing text should be set at the cursor position.
|
||||
///
|
||||
|
|
@ -63,14 +160,16 @@ pub enum Event {
|
|||
|
||||
/// Notifies when text should be inserted into the editor widget.
|
||||
///
|
||||
/// Right before this event winit will send empty [`Self::Preedit`] event.
|
||||
/// Right before this event, an empty [`Self::Preedit`] event will be issued.
|
||||
Commit(String),
|
||||
|
||||
/// Notifies when the IME was disabled.
|
||||
///
|
||||
/// 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_cursor_area`] and clear
|
||||
/// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should
|
||||
/// also stop issuing IME related requests like [`Shell::request_input_method`] and clear
|
||||
/// pending preedit text.
|
||||
Disabled,
|
||||
///
|
||||
/// [`Shell::request_input_method`]: crate::Shell::request_input_method
|
||||
Closed,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub use event::Event;
|
|||
pub use font::Font;
|
||||
pub use gradient::Gradient;
|
||||
pub use image::Image;
|
||||
pub use input_method::InputMethod;
|
||||
pub use layout::Layout;
|
||||
pub use length::Length;
|
||||
pub use overlay::Overlay;
|
||||
|
|
@ -73,7 +74,6 @@ pub use renderer::Renderer;
|
|||
pub use rotation::Rotation;
|
||||
pub use settings::Settings;
|
||||
pub use shadow::Shadow;
|
||||
pub use shell::CaretInfo;
|
||||
pub use shell::Shell;
|
||||
pub use size::Size;
|
||||
pub use svg::Svg;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
use crate::time::Instant;
|
||||
use crate::event;
|
||||
use crate::window;
|
||||
use crate::{event, Point};
|
||||
|
||||
/// TODO
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CaretInfo {
|
||||
/// TODO
|
||||
pub position: Point,
|
||||
/// TODO
|
||||
pub input_method_allowed: bool,
|
||||
}
|
||||
use crate::InputMethod;
|
||||
|
||||
/// A connection to the state of a shell.
|
||||
///
|
||||
|
|
@ -21,10 +12,10 @@ pub struct CaretInfo {
|
|||
pub struct Shell<'a, Message> {
|
||||
messages: &'a mut Vec<Message>,
|
||||
event_status: event::Status,
|
||||
redraw_request: Option<window::RedrawRequest>,
|
||||
redraw_request: window::RedrawRequest,
|
||||
input_method: InputMethod,
|
||||
is_layout_invalid: bool,
|
||||
are_widgets_invalid: bool,
|
||||
caret_info: Option<CaretInfo>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Shell<'a, Message> {
|
||||
|
|
@ -33,10 +24,10 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
Self {
|
||||
messages,
|
||||
event_status: event::Status::Ignored,
|
||||
redraw_request: None,
|
||||
redraw_request: window::RedrawRequest::Wait,
|
||||
is_layout_invalid: false,
|
||||
are_widgets_invalid: false,
|
||||
caret_info: None,
|
||||
input_method: InputMethod::Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,35 +61,38 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
|
||||
/// Requests a new frame to be drawn as soon as possible.
|
||||
pub fn request_redraw(&mut self) {
|
||||
self.redraw_request = Some(window::RedrawRequest::NextFrame);
|
||||
self.redraw_request = window::RedrawRequest::NextFrame;
|
||||
}
|
||||
|
||||
/// Requests a new frame to be drawn at the given [`Instant`].
|
||||
pub fn request_redraw_at(&mut self, at: Instant) {
|
||||
match self.redraw_request {
|
||||
None => {
|
||||
self.redraw_request = Some(window::RedrawRequest::At(at));
|
||||
}
|
||||
Some(window::RedrawRequest::At(current)) if at < current => {
|
||||
self.redraw_request = Some(window::RedrawRequest::At(at));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
/// Requests a new frame to be drawn at the given [`window::RedrawRequest`].
|
||||
pub fn request_redraw_at(
|
||||
&mut self,
|
||||
redraw_request: impl Into<window::RedrawRequest>,
|
||||
) {
|
||||
self.redraw_request = self.redraw_request.min(redraw_request.into());
|
||||
}
|
||||
|
||||
/// Returns the request a redraw should happen, if any.
|
||||
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
|
||||
pub fn redraw_request(&self) -> window::RedrawRequest {
|
||||
self.redraw_request
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub fn update_caret_info(&mut self, caret_info: Option<CaretInfo>) {
|
||||
self.caret_info = caret_info.or(self.caret_info);
|
||||
/// Requests the current [`InputMethod`] strategy.
|
||||
pub fn request_input_method<T: AsRef<str>>(
|
||||
&mut self,
|
||||
ime: &InputMethod<T>,
|
||||
) {
|
||||
self.input_method.merge(ime);
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub fn caret_info(&self) -> Option<CaretInfo> {
|
||||
self.caret_info
|
||||
/// Returns the current [`InputMethod`] strategy.
|
||||
pub fn input_method(&self) -> &InputMethod {
|
||||
&self.input_method
|
||||
}
|
||||
|
||||
/// Returns the current [`InputMethod`] strategy.
|
||||
pub fn input_method_mut(&mut self) -> &mut InputMethod {
|
||||
&mut self.input_method
|
||||
}
|
||||
|
||||
/// Returns whether the current layout is invalid or not.
|
||||
|
|
@ -143,22 +137,14 @@ impl<'a, Message> Shell<'a, Message> {
|
|||
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
||||
self.messages.extend(other.messages.drain(..).map(f));
|
||||
|
||||
if let Some(new) = other.redraw_request {
|
||||
self.redraw_request = Some(
|
||||
self.redraw_request
|
||||
.map(|current| if current < new { current } else { new })
|
||||
.unwrap_or(new),
|
||||
);
|
||||
}
|
||||
|
||||
self.update_caret_info(other.caret_info());
|
||||
|
||||
self.is_layout_invalid =
|
||||
self.is_layout_invalid || other.is_layout_invalid;
|
||||
|
||||
self.are_widgets_invalid =
|
||||
self.are_widgets_invalid || other.are_widgets_invalid;
|
||||
|
||||
self.redraw_request = self.redraw_request.min(other.redraw_request);
|
||||
self.event_status = self.event_status.merge(other.event_status);
|
||||
self.input_method.merge(&other.input_method);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,12 @@ impl<P: Paragraph> Plain<P> {
|
|||
self.raw.min_width()
|
||||
}
|
||||
|
||||
/// Returns the minimum height that can fit the contents of the
|
||||
/// [`Paragraph`].
|
||||
pub fn min_height(&self) -> f32 {
|
||||
self.raw.min_height()
|
||||
}
|
||||
|
||||
/// Returns the cached [`Paragraph`].
|
||||
pub fn raw(&self) -> &P {
|
||||
&self.raw
|
||||
|
|
|
|||
|
|
@ -8,6 +8,15 @@ pub enum RedrawRequest {
|
|||
|
||||
/// Redraw at the given time.
|
||||
At(Instant),
|
||||
|
||||
/// No redraw is needed.
|
||||
Wait,
|
||||
}
|
||||
|
||||
impl From<Instant> for RedrawRequest {
|
||||
fn from(time: Instant) -> Self {
|
||||
Self::At(time)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -34,5 +43,8 @@ mod tests {
|
|||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
|
||||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
|
||||
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
|
||||
|
||||
assert!(RedrawRequest::Wait > RedrawRequest::NextFrame);
|
||||
assert!(RedrawRequest::Wait > RedrawRequest::At(later));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue