diff options
Diffstat (limited to '')
| -rw-r--r-- | core/src/event.rs | 4 | ||||
| -rw-r--r-- | core/src/input_method.rs | 235 | ||||
| -rw-r--r-- | core/src/lib.rs | 2 | ||||
| -rw-r--r-- | core/src/shell.rs | 66 | ||||
| -rw-r--r-- | core/src/text.rs | 27 | ||||
| -rw-r--r-- | core/src/text/paragraph.rs | 6 | ||||
| -rw-r--r-- | core/src/window/redraw_request.rs | 12 | 
7 files changed, 316 insertions, 36 deletions
| diff --git a/core/src/event.rs b/core/src/event.rs index b6cf321e..7f0ab914 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,4 +1,5 @@  //! Handle events of a user interface. +use crate::input_method;  use crate::keyboard;  use crate::mouse;  use crate::touch; @@ -23,6 +24,9 @@ pub enum Event {      /// A touch event      Touch(touch::Event), + +    /// An input method event +    InputMethod(input_method::Event),  }  /// The status of an [`Event`] after being processed. diff --git a/core/src/input_method.rs b/core/src/input_method.rs new file mode 100644 index 00000000..4e8c383b --- /dev/null +++ b/core/src/input_method.rs @@ -0,0 +1,235 @@ +//! 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 strategy has been specified. +    None, +    /// 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<Preedit<T>>, +    }, +} + +/// The pre-edit of an [`InputMethod`]. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Preedit<T = String> { +    /// The current content. +    pub content: T, +    /// The selected range of the content. +    pub selection: Option<Range<usize>>, +} + +impl<T> Preedit<T> { +    /// Creates a new empty [`Preedit`]. +    pub fn new() -> Self +    where +        T: Default, +    { +        Self::default() +    } + +    /// Turns a [`Preedit`] into its owned version. +    pub fn to_owned(&self) -> Preedit +    where +        T: AsRef<str>, +    { +        Preedit { +            content: self.content.as_ref().to_owned(), +            selection: self.selection.clone(), +        } +    } +} + +impl Preedit { +    /// Borrows the contents of a [`Preedit`]. +    pub fn as_ref(&self) -> Preedit<&str> { +        Preedit { +            content: &self.content, +            selection: self.selection.clone(), +        } +    } +} + +/// 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 first one when both open: +    /// ``` +    /// # use iced_core::input_method::{InputMethod, Purpose, Preedit}; +    /// # use iced_core::Point; +    /// +    /// let open = InputMethod::Open { +    ///     position: Point::ORIGIN, +    ///     purpose: Purpose::Normal, +    ///     preedit: Some(Preedit { content: "1".to_owned(), selection: None }), +    /// }; +    /// +    /// let open_2 = InputMethod::Open { +    ///     position: Point::ORIGIN, +    ///     purpose: Purpose::Secure, +    ///     preedit: Some(Preedit { content: "2".to_owned(), selection: 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); +    /// ``` +    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) => {} +            _ => { +                *self = other.to_owned(); +            } +        } +    } + +    /// Returns true if the [`InputMethod`] is open. +    pub fn is_open(&self) -> bool { +        matches!(self, Self::Open { .. }) +    } +} + +impl<T> InputMethod<T> { +    /// Turns an [`InputMethod`] into its owned version. +    pub fn to_owned(&self) -> InputMethod +    where +        T: AsRef<str>, +    { +        match self { +            Self::None => InputMethod::None, +            Self::Disabled => InputMethod::Disabled, +            Self::Allowed => InputMethod::Allowed, +            Self::Open { +                position, +                purpose, +                preedit, +            } => InputMethod::Open { +                position: *position, +                purpose: *purpose, +                preedit: preedit.as_ref().map(Preedit::to_owned), +            }, +        } +    } +} + +/// 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 +/// [`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 +/// "accent key", and then the character you want to apply the accent to. In this case, some +/// platforms will generate the following event sequence: +/// +/// ```ignore +/// // Press "`" key +/// Ime::Preedit("`", Some((0, 0))) +/// // Press "E" key +/// 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 +/// [`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: +/// +/// ```ignore +/// // Press "A" key +/// Ime::Preedit("a", Some((1, 1))) +/// // Press "B" key +/// Ime::Preedit("a b", Some((3, 3))) +/// // Press left arrow key +/// Ime::Preedit("a b", Some((1, 1))) +/// // Press space key +/// Ime::Preedit("啊b", Some((3, 3))) +/// // Press space key +/// 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 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 [`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. +    /// +    /// The value represents a pair of the preedit string and the cursor begin position and end +    /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string +    /// this indicates that preedit was cleared. +    /// +    /// The cursor range is byte-wise indexed. +    Preedit(String, Option<Range<usize>>), + +    /// Notifies when text should be inserted into the editor widget. +    /// +    /// 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 [`Opened`][Self::Opened] event. You should +    /// also stop issuing IME related requests like [`Shell::request_input_method`] and clear +    /// pending preedit text. +    /// +    /// [`Shell::request_input_method`]: crate::Shell::request_input_method +    Closed, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 16b3aa0f..c31a8da7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,6 +17,7 @@ pub mod event;  pub mod font;  pub mod gradient;  pub mod image; +pub mod input_method;  pub mod keyboard;  pub mod layout;  pub mod mouse; @@ -61,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; diff --git a/core/src/shell.rs b/core/src/shell.rs index c2275f71..509e3822 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,6 +1,6 @@  use crate::event; -use crate::time::Instant;  use crate::window; +use crate::InputMethod;  /// A connection to the state of a shell.  /// @@ -12,7 +12,8 @@ use crate::window;  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,  } @@ -23,9 +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, +            input_method: InputMethod::None,          }      } @@ -59,24 +61,19 @@ 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); -    } - -    /// 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)); -            } -            _ => {} -        } +        self.redraw_request = window::RedrawRequest::NextFrame; +    } + +    /// 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      } @@ -87,11 +84,32 @@ impl<'a, Message> Shell<'a, Message> {      /// method.      pub fn replace_redraw_request(          shell: &mut Self, -        redraw_request: Option<window::RedrawRequest>, +        redraw_request: window::RedrawRequest,      ) {          shell.redraw_request = redraw_request;      } +    /// Requests the current [`InputMethod`] strategy. +    /// +    /// __Important__: This request will only be honored by the +    /// [`Shell`] only during a [`window::Event::RedrawRequested`]. +    pub fn request_input_method<T: AsRef<str>>( +        &mut self, +        ime: &InputMethod<T>, +    ) { +        self.input_method.merge(ime); +    } + +    /// 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.      pub fn is_layout_invalid(&self) -> bool {          self.is_layout_invalid @@ -134,20 +152,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.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);      }  } diff --git a/core/src/text.rs b/core/src/text.rs index c144fd24..a7e1f281 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -284,15 +284,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {      pub fn new(fragment: impl IntoFragment<'a>) -> Self {          Self {              text: fragment.into_fragment(), -            size: None, -            line_height: None, -            font: None, -            color: None, -            highlight: None, -            link: None, -            padding: Padding::ZERO, -            underline: false, -            strikethrough: false, +            ..Self::default()          }      } @@ -440,6 +432,23 @@ impl<'a, Link, Font> Span<'a, Link, Font> {      }  } +impl<Link, Font> Default for Span<'_, Link, Font> { +    fn default() -> Self { +        Self { +            text: Cow::default(), +            size: None, +            line_height: None, +            font: None, +            color: None, +            link: None, +            highlight: None, +            padding: Padding::default(), +            underline: false, +            strikethrough: false, +        } +    } +} +  impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {      fn from(value: &'a str) -> Self {          Span::new(value) diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 924276c3..700c2c75 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -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 diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index b0c000d6..0ae4face 100644 --- a/core/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs @@ -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));      }  } | 
