diff options
| -rw-r--r-- | core/src/input_method.rs | 129 | ||||
| -rw-r--r-- | core/src/lib.rs | 2 | ||||
| -rw-r--r-- | core/src/shell.rs | 78 | ||||
| -rw-r--r-- | core/src/text/paragraph.rs | 6 | ||||
| -rw-r--r-- | core/src/window/redraw_request.rs | 12 | ||||
| -rw-r--r-- | runtime/src/user_interface.rs | 43 | ||||
| -rw-r--r-- | widget/src/action.rs | 14 | ||||
| -rw-r--r-- | widget/src/canvas.rs | 15 | ||||
| -rw-r--r-- | widget/src/combo_box.rs | 16 | ||||
| -rw-r--r-- | widget/src/lazy/component.rs | 29 | ||||
| -rw-r--r-- | widget/src/pane_grid.rs | 2 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 18 | ||||
| -rw-r--r-- | widget/src/shader.rs | 14 | ||||
| -rw-r--r-- | widget/src/text_editor.rs | 133 | ||||
| -rw-r--r-- | widget/src/text_input.rs | 148 | ||||
| -rw-r--r-- | winit/src/conversion.rs | 36 | ||||
| -rw-r--r-- | winit/src/program.rs | 156 | ||||
| -rw-r--r-- | winit/src/program/state.rs | 12 | ||||
| -rw-r--r-- | winit/src/program/window_manager.rs | 145 | 
19 files changed, 538 insertions, 470 deletions
| diff --git a/core/src/input_method.rs b/core/src/input_method.rs index d282d404..c293582d 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -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,  } diff --git a/core/src/lib.rs b/core/src/lib.rs index c7c38044..c31a8da7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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; diff --git a/core/src/shell.rs b/core/src/shell.rs index d2c1b9ec..e87d1696 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -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); -    } - -    /// 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      } -    /// 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);      }  } 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));      }  } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 6d3360d0..59e497c5 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -6,7 +6,7 @@ use crate::core::renderer;  use crate::core::widget;  use crate::core::window;  use crate::core::{ -    CaretInfo, Clipboard, Element, Layout, Rectangle, Shell, Size, Vector, +    Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector,  };  use crate::overlay; @@ -188,8 +188,8 @@ where          use std::mem::ManuallyDrop;          let mut outdated = false; -        let mut redraw_request = None; -        let mut caret_info = None; +        let mut redraw_request = window::RedrawRequest::Wait; +        let mut input_method = InputMethod::Disabled;          let mut manual_overlay = ManuallyDrop::new(              self.root @@ -223,17 +223,8 @@ where                  );                  event_statuses.push(shell.event_status()); - -                match (redraw_request, shell.redraw_request()) { -                    (None, Some(at)) => { -                        redraw_request = Some(at); -                    } -                    (Some(current), Some(new)) if new < current => { -                        redraw_request = Some(new); -                    } -                    _ => {} -                } -                caret_info = caret_info.or(shell.caret_info()); +                redraw_request = redraw_request.min(shell.redraw_request()); +                input_method.merge(shell.input_method());                  if shell.is_layout_invalid() {                      let _ = ManuallyDrop::into_inner(manual_overlay); @@ -327,16 +318,8 @@ where                      self.overlay = None;                  } -                match (redraw_request, shell.redraw_request()) { -                    (None, Some(at)) => { -                        redraw_request = Some(at); -                    } -                    (Some(current), Some(new)) if new < current => { -                        redraw_request = Some(new); -                    } -                    _ => {} -                } -                caret_info = caret_info.or(shell.caret_info()); +                redraw_request = redraw_request.min(shell.redraw_request()); +                input_method.merge(shell.input_method());                  shell.revalidate_layout(|| {                      self.base = self.root.as_widget().layout( @@ -362,7 +345,7 @@ where              } else {                  State::Updated {                      redraw_request, -                    caret_info, +                    input_method,                  }              },              event_statuses, @@ -644,7 +627,7 @@ impl Default for Cache {  }  /// The resulting state after updating a [`UserInterface`]. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  pub enum State {      /// The [`UserInterface`] is outdated and needs to be rebuilt.      Outdated, @@ -652,9 +635,9 @@ pub enum State {      /// The [`UserInterface`] is up-to-date and can be reused without      /// rebuilding.      Updated { -        /// The [`window::RedrawRequest`] when a redraw should be performed. -        redraw_request: Option<window::RedrawRequest>, -        /// TODO -        caret_info: Option<CaretInfo>, +        /// The [`window::RedrawRequest`] describing when a redraw should be performed. +        redraw_request: window::RedrawRequest, +        /// The current [`InputMethod`] strategy of the user interface. +        input_method: InputMethod,      },  } diff --git a/widget/src/action.rs b/widget/src/action.rs index 1dd3a787..cc31e76a 100644 --- a/widget/src/action.rs +++ b/widget/src/action.rs @@ -6,7 +6,7 @@ use crate::core::window;  #[derive(Debug, Clone)]  pub struct Action<Message> {      message_to_publish: Option<Message>, -    redraw_request: Option<window::RedrawRequest>, +    redraw_request: window::RedrawRequest,      event_status: event::Status,  } @@ -14,7 +14,7 @@ impl<Message> Action<Message> {      fn new() -> Self {          Self {              message_to_publish: None, -            redraw_request: None, +            redraw_request: window::RedrawRequest::Wait,              event_status: event::Status::Ignored,          }      } @@ -46,7 +46,7 @@ impl<Message> Action<Message> {      /// soon as possible; without publishing any `Message`.      pub fn request_redraw() -> Self {          Self { -            redraw_request: Some(window::RedrawRequest::NextFrame), +            redraw_request: window::RedrawRequest::NextFrame,              ..Self::new()          }      } @@ -58,7 +58,7 @@ impl<Message> Action<Message> {      /// blinking caret on a text input.      pub fn request_redraw_at(at: Instant) -> Self {          Self { -            redraw_request: Some(window::RedrawRequest::At(at)), +            redraw_request: window::RedrawRequest::At(at),              ..Self::new()          }      } @@ -75,11 +75,7 @@ impl<Message> Action<Message> {      /// widget implementations.      pub fn into_inner(          self, -    ) -> ( -        Option<Message>, -        Option<window::RedrawRequest>, -        event::Status, -    ) { +    ) -> (Option<Message>, window::RedrawRequest, event::Status) {          (              self.message_to_publish,              self.redraw_request, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 23cc3f2b..d10771f0 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -238,27 +238,18 @@ where          {              let (message, redraw_request, event_status) = action.into_inner(); +            shell.request_redraw_at(redraw_request); +              if let Some(message) = message {                  shell.publish(message);              } -            if let Some(redraw_request) = redraw_request { -                match redraw_request { -                    window::RedrawRequest::NextFrame => { -                        shell.request_redraw(); -                    } -                    window::RedrawRequest::At(at) => { -                        shell.request_redraw_at(at); -                    } -                } -            } -              if event_status == event::Status::Captured {                  shell.capture_event();              }          } -        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +        if shell.redraw_request() != window::RedrawRequest::NextFrame {              let mouse_interaction = self                  .mouse_interaction(tree, layout, cursor, viewport, renderer); diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index d7c7c922..05793155 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -63,7 +63,6 @@ use crate::core::renderer;  use crate::core::text;  use crate::core::time::Instant;  use crate::core::widget::{self, Widget}; -use crate::core::window;  use crate::core::{      Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,      Vector, @@ -554,17 +553,8 @@ where              shell.capture_event();          } -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          // Then finally react to them here          for message in local_messages { @@ -757,7 +747,7 @@ where                      &mut local_shell,                      viewport,                  ); -                shell.update_caret_info(local_shell.caret_info()); +                shell.request_input_method(local_shell.input_method());              }          }); diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index b9fbde58..c93b7c42 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -6,7 +6,6 @@ use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; -use crate::core::window;  use crate::core::{      self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,      Widget, @@ -344,18 +343,8 @@ where          }          local_shell.revalidate_layout(|| shell.invalidate_layout()); - -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          if !local_messages.is_empty() {              let mut heads = self.state.take().unwrap().into_heads(); @@ -630,18 +619,8 @@ where          }          local_shell.revalidate_layout(|| shell.invalidate_layout()); - -        if let Some(redraw_request) = local_shell.redraw_request() { -            match redraw_request { -                window::RedrawRequest::NextFrame => { -                    shell.request_redraw(); -                } -                window::RedrawRequest::At(at) => { -                    shell.request_redraw_at(at); -                } -            } -        } -        shell.update_caret_info(local_shell.caret_info()); +        shell.request_redraw_at(local_shell.redraw_request()); +        shell.request_input_method(local_shell.input_method());          if !local_messages.is_empty() {              let mut inner = diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5c3b343c..e972b983 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -687,7 +687,7 @@ where              _ => {}          } -        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +        if shell.redraw_request() != window::RedrawRequest::NextFrame {              let interaction = self                  .grid_interaction(action, layout, cursor)                  .or_else(|| { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 7df7a0e5..0a93584e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,7 +33,7 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout, +    self, Background, Clipboard, Color, Element, Event, InputMethod, Layout,      Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,      Widget,  }; @@ -730,7 +730,6 @@ where                  let translation =                      state.translation(self.direction, bounds, content_bounds); -                let children_may_have_caret = shell.caret_info().is_none();                  self.content.as_widget_mut().update(                      &mut tree.children[0],                      event.clone(), @@ -746,17 +745,10 @@ where                      },                  ); -                if children_may_have_caret { -                    if let Some(caret_info) = shell.caret_info() { -                        shell.update_caret_info(Some(CaretInfo { -                            position: Point::new( -                                caret_info.position.x - translation.x, -                                caret_info.position.y - translation.y, -                            ), -                            input_method_allowed: caret_info -                                .input_method_allowed, -                        })); -                    } +                if let InputMethod::Open { position, .. } = +                    shell.input_method_mut() +                { +                    *position = *position + translation;                  }              }; diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8ec57482..48c96321 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -9,7 +9,6 @@ use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::{self, Widget}; -use crate::core::window;  use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};  use crate::renderer::wgpu::primitive; @@ -105,21 +104,12 @@ where          {              let (message, redraw_request, event_status) = action.into_inner(); +            shell.request_redraw_at(redraw_request); +              if let Some(message) = message {                  shell.publish(message);              } -            if let Some(redraw_request) = redraw_request { -                match redraw_request { -                    window::RedrawRequest::NextFrame => { -                        shell.request_redraw(); -                    } -                    window::RedrawRequest::At(at) => { -                        shell.request_redraw_at(at); -                    } -                } -            } -              if event_status == event::Status::Captured {                  shell.capture_event();              } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 529c8b90..4f985f28 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -47,7 +47,7 @@ use crate::core::widget::operation;  use crate::core::widget::{self, Widget};  use crate::core::window;  use crate::core::{ -    Background, Border, CaretInfo, Color, Element, Event, Length, Padding, +    Background, Border, Color, Element, Event, InputMethod, Length, Padding,      Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,  }; @@ -324,43 +324,49 @@ where          self      } -    fn caret_rect( +    fn input_method<'b>(          &self, -        tree: &widget::Tree, +        state: &'b State<Highlighter>,          renderer: &Renderer,          layout: Layout<'_>, -    ) -> Option<Rectangle> { -        let bounds = layout.bounds(); +    ) -> InputMethod<&'b str> { +        let Some(Focus { +            is_window_focused: true, +            is_ime_open, +            .. +        }) = &state.focus +        else { +            return InputMethod::Disabled; +        }; + +        let Some(preedit) = &is_ime_open else { +            return InputMethod::Allowed; +        }; +        let bounds = layout.bounds();          let internal = self.content.0.borrow_mut(); -        let state = tree.state.downcast_ref::<State<Highlighter>>();          let text_bounds = bounds.shrink(self.padding);          let translation = text_bounds.position() - Point::ORIGIN; -        if state.focus.is_some() { -            let position = match internal.editor.cursor() { -                Cursor::Caret(position) => position, -                Cursor::Selection(ranges) => ranges -                    .first() -                    .cloned() -                    .unwrap_or(Rectangle::default()) -                    .position(), -            }; -            Some(Rectangle::new( -                position + translation, -                Size::new( -                    1.0, -                    self.line_height -                        .to_absolute( -                            self.text_size -                                .unwrap_or_else(|| renderer.default_size()), -                        ) -                        .into(), -                ), -            )) -        } else { -            None +        let cursor = match internal.editor.cursor() { +            Cursor::Caret(position) => position, +            Cursor::Selection(ranges) => { +                ranges.first().cloned().unwrap_or_default().position() +            } +        }; + +        let line_height = self.line_height.to_absolute( +            self.text_size.unwrap_or_else(|| renderer.default_size()), +        ); + +        let position = +            cursor + translation + Vector::new(0.0, f32::from(line_height)); + +        InputMethod::Open { +            position, +            purpose: input_method::Purpose::Normal, +            preedit: Some(preedit),          }      }  } @@ -499,11 +505,12 @@ pub struct State<Highlighter: text::Highlighter> {      highlighter_format_address: usize,  } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  struct Focus {      updated_at: Instant,      now: Instant,      is_window_focused: bool, +    is_ime_open: Option<String>,  }  impl Focus { @@ -516,6 +523,7 @@ impl Focus {              updated_at: now,              now,              is_window_focused: true, +            is_ime_open: None,          }      } @@ -742,11 +750,23 @@ where                      }));                      shell.capture_event();                  } -                Update::Commit(text) => { -                    shell.publish(on_edit(Action::Edit(Edit::Paste( -                        Arc::new(text), -                    )))); -                } +                Update::InputMethod(update) => match update { +                    Ime::Toggle(is_open) => { +                        if let Some(focus) = &mut state.focus { +                            focus.is_ime_open = is_open.then(String::new); +                        } +                    } +                    Ime::Preedit(text) => { +                        if let Some(focus) = &mut state.focus { +                            focus.is_ime_open = Some(text); +                        } +                    } +                    Ime::Commit(text) => { +                        shell.publish(on_edit(Action::Edit(Edit::Paste( +                            Arc::new(text), +                        )))); +                    } +                },                  Update::Binding(binding) => {                      fn apply_binding<                          H: text::Highlighter, @@ -871,22 +891,12 @@ where              }          }; -        shell.update_caret_info(if state.is_focused() { -            let rect = -                self.caret_rect(tree, renderer, layout).unwrap_or_default(); - -            let bottom_left = Point::new(rect.x, rect.y + rect.height); - -            Some(CaretInfo { -                position: bottom_left, -                input_method_allowed: true, -            }) -        } else { -            None -        }); -          if is_redraw {              self.last_status = Some(status); + +            shell.request_input_method( +                &self.input_method(state, renderer, layout), +            );          } else if self              .last_status              .is_some_and(|last_status| status != last_status) @@ -1189,10 +1199,16 @@ enum Update<Message> {      Drag(Point),      Release,      Scroll(f32), -    Commit(String), +    InputMethod(Ime),      Binding(Binding<Message>),  } +enum Ime { +    Toggle(bool), +    Preedit(String), +    Commit(String), +} +  impl<Message> Update<Message> {      fn from_event<H: Highlighter>(          event: Event, @@ -1252,9 +1268,20 @@ impl<Message> Update<Message> {                  }                  _ => None,              }, -            Event::InputMethod(input_method::Event::Commit(text)) => { -                Some(Update::Commit(text)) -            } +            Event::InputMethod(event) => match event { +                input_method::Event::Opened | input_method::Event::Closed => { +                    Some(Update::InputMethod(Ime::Toggle(matches!( +                        event, +                        input_method::Event::Opened +                    )))) +                } +                input_method::Event::Preedit(content, _range) => { +                    Some(Update::InputMethod(Ime::Preedit(content))) +                } +                input_method::Event::Commit(content) => { +                    Some(Update::InputMethod(Ime::Commit(content))) +                } +            },              Event::Keyboard(keyboard::Event::KeyPressed {                  key,                  modifiers, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ba5d1843..d0e93927 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -57,7 +57,7 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    Background, Border, CaretInfo, Color, Element, Event, Layout, Length, +    Background, Border, Color, Element, Event, InputMethod, Layout, Length,      Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::runtime::task::{self, Task}; @@ -392,14 +392,24 @@ where          }      } -    fn caret_rect( +    fn input_method<'b>(          &self, -        tree: &Tree, +        state: &'b State<Renderer::Paragraph>,          layout: Layout<'_>, -        value: Option<&Value>, -    ) -> Option<Rectangle> { -        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); -        let value = value.unwrap_or(&self.value); +        value: &Value, +    ) -> InputMethod<&'b str> { +        let Some(Focus { +            is_window_focused: true, +            is_ime_open, +            .. +        }) = &state.is_focused +        else { +            return InputMethod::Disabled; +        }; + +        let Some(preedit) = 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); @@ -407,38 +417,32 @@ where          let mut children_layout = layout.children();          let text_bounds = children_layout.next().unwrap().bounds(); -        if state -            .is_focused -            .is_some_and(|focus| focus.is_window_focused) -        { -            let caret_index = match state.cursor.state(value) { -                cursor::State::Index(position) => position, -                cursor::State::Selection { start, end } => start.min(end), -            }; +        let caret_index = match state.cursor.state(value) { +            cursor::State::Index(position) => position, +            cursor::State::Selection { start, end } => start.min(end), +        }; -            let text = state.value.raw(); -            let (caret_x, offset) = measure_cursor_and_scroll_offset( -                text, -                text_bounds, -                caret_index, -            ); +        let text = state.value.raw(); +        let (cursor_x, scroll_offset) = +            measure_cursor_and_scroll_offset(text, text_bounds, caret_index); -            let alignment_offset = alignment_offset( -                text_bounds.width, -                text.min_width(), -                self.alignment, -            ); +        let alignment_offset = alignment_offset( +            text_bounds.width, +            text.min_width(), +            self.alignment, +        ); -            let x = (text_bounds.x + caret_x).floor(); +        let x = (text_bounds.x + cursor_x).floor() - scroll_offset +            + alignment_offset; -            Some(Rectangle { -                x: (alignment_offset - offset) + x, -                y: text_bounds.y, -                width: 1.0, -                height: text_bounds.height, -            }) -        } else { -            None +        InputMethod::Open { +            position: Point::new(x, text_bounds.y), +            purpose: if self.is_secure { +                input_method::Purpose::Secure +            } else { +                input_method::Purpose::Normal +            }, +            preedit: Some(preedit),          }      } @@ -725,6 +729,7 @@ where                          updated_at: now,                          now,                          is_window_focused: true, +                        is_ime_open: None,                      })                  } else {                      None @@ -1248,28 +1253,46 @@ where                  state.keyboard_modifiers = *modifiers;              } -            Event::InputMethod(input_method::Event::Commit(text)) => { -                let state = state::<Renderer>(tree); +            Event::InputMethod(event) => match event { +                input_method::Event::Opened | input_method::Event::Closed => { +                    let state = state::<Renderer>(tree); -                if let Some(focus) = &mut state.is_focused { -                    let Some(on_input) = &self.on_input else { -                        return; -                    }; +                    if let Some(focus) = &mut state.is_focused { +                        focus.is_ime_open = +                            matches!(event, input_method::Event::Opened) +                                .then(String::new); +                    } +                } +                input_method::Event::Preedit(content, _range) => { +                    let state = state::<Renderer>(tree); -                    let mut editor = -                        Editor::new(&mut self.value, &mut state.cursor); -                    editor.paste(Value::new(text)); +                    if let Some(focus) = &mut state.is_focused { +                        focus.is_ime_open = Some(content.to_owned()); +                    } +                } +                input_method::Event::Commit(text) => { +                    let state = state::<Renderer>(tree); -                    focus.updated_at = Instant::now(); -                    state.is_pasting = None; +                    if let Some(focus) = &mut state.is_focused { +                        let Some(on_input) = &self.on_input else { +                            return; +                        }; -                    let message = (on_input)(editor.contents()); -                    shell.publish(message); -                    shell.capture_event(); +                        let mut editor = +                            Editor::new(&mut self.value, &mut state.cursor); +                        editor.paste(Value::new(text)); + +                        focus.updated_at = Instant::now(); +                        state.is_pasting = None; -                    update_cache(state, &self.value); +                        let message = (on_input)(editor.contents()); +                        shell.publish(message); +                        shell.capture_event(); + +                        update_cache(state, &self.value); +                    }                  } -            } +            },              Event::Window(window::Event::Unfocused) => {                  let state = state::<Renderer>(tree); @@ -1329,21 +1352,14 @@ where              Status::Active          }; -        shell.update_caret_info(if state.is_focused() { -            let rect = self -                .caret_rect(tree, layout, Some(&self.value)) -                .unwrap_or(Rectangle::with_size(Size::<f32>::default())); -            let bottom_left = Point::new(rect.x, rect.y + rect.height); -            Some(CaretInfo { -                position: bottom_left, -                input_method_allowed: true, -            }) -        } else { -            None -        }); -          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) @@ -1517,11 +1533,12 @@ fn state<Renderer: text::Renderer>(      tree.state.downcast_mut::<State<Renderer::Paragraph>>()  } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  struct Focus {      updated_at: Instant,      now: Instant,      is_window_focused: bool, +    is_ime_open: Option<String>,  }  impl<P: text::Paragraph> State<P> { @@ -1548,6 +1565,7 @@ impl<P: text::Paragraph> State<P> {              updated_at: now,              now,              is_window_focused: true, +            is_ime_open: None,          });          self.move_cursor_to_end(); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index c7f9aaaf..ab84afff 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -141,6 +141,7 @@ pub fn window_event(      scale_factor: f64,      modifiers: winit::keyboard::ModifiersState,  ) -> Option<Event> { +    use winit::event::Ime;      use winit::event::WindowEvent;      match event { @@ -284,19 +285,15 @@ pub fn window_event(                  self::modifiers(new_modifiers.state()),              )))          } -        WindowEvent::Ime(ime) => { -            use winit::event::Ime; -            println!("ime event: {:?}", ime); -            Some(Event::InputMethod(match ime { -                Ime::Enabled => input_method::Event::Enabled, -                Ime::Preedit(s, size) => input_method::Event::Preedit( -                    s, -                    size.map(|(start, end)| (start..end)), -                ), -                Ime::Commit(s) => input_method::Event::Commit(s), -                Ime::Disabled => input_method::Event::Disabled, -            })) -        } +        WindowEvent::Ime(event) => Some(Event::InputMethod(match event { +            Ime::Enabled => input_method::Event::Opened, +            Ime::Preedit(content, size) => input_method::Event::Preedit( +                content, +                size.map(|(start, end)| (start..end)), +            ), +            Ime::Commit(content) => input_method::Event::Commit(content), +            Ime::Disabled => input_method::Event::Closed, +        })),          WindowEvent::Focused(focused) => Some(Event::Window(if focused {              window::Event::Focused          } else { @@ -1174,7 +1171,7 @@ pub fn resize_direction(      }  } -/// Converts some [`window::Icon`] into it's `winit` counterpart. +/// Converts some [`window::Icon`] into its `winit` counterpart.  ///  /// Returns `None` if there is an error during the conversion.  pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> { @@ -1183,6 +1180,17 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {      winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()  } +/// Convertions some [`input_method::Purpose`] to its `winit` counterpart. +pub fn ime_purpose( +    purpose: input_method::Purpose, +) -> winit::window::ImePurpose { +    match purpose { +        input_method::Purpose::Normal => winit::window::ImePurpose::Normal, +        input_method::Purpose::Secure => winit::window::ImePurpose::Password, +        input_method::Purpose::Terminal => winit::window::ImePurpose::Terminal, +    } +} +  // See: https://en.wikipedia.org/wiki/Private_Use_Areas  fn is_private_use(c: char) -> bool {      ('\u{E000}'..='\u{F8FF}').contains(&c) diff --git a/winit/src/program.rs b/winit/src/program.rs index 688d6731..302bc6c3 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -3,8 +3,6 @@ mod state;  mod window_manager;  pub use state::State; -use winit::dpi::LogicalPosition; -use winit::dpi::LogicalSize;  use crate::conversion;  use crate::core; @@ -581,8 +579,6 @@ async fn run_instance<P, C>(      let mut clipboard = Clipboard::unconnected();      let mut compositor_receiver: Option<oneshot::Receiver<_>> = None; -    let mut preedit = Preedit::<P>::new(); -      debug.startup_finished();      loop { @@ -878,29 +874,15 @@ async fn run_instance<P, C>(                          if let user_interface::State::Updated {                              redraw_request, -                            caret_info, +                            input_method,                          } = ui_state                          { -                            match redraw_request { -                                Some(window::RedrawRequest::NextFrame) => { -                                    window.raw.request_redraw(); -                                    window.redraw_at = None; -                                } -                                Some(window::RedrawRequest::At(at)) => { -                                    window.redraw_at = Some(at); -                                } -                                None => {} -                            } - -                            if let Some(caret_info) = caret_info { -                                update_input_method( -                                    window, -                                    &mut preedit, -                                    &caret_info, -                                ); -                            } +                            window.request_redraw(redraw_request); +                            window.request_input_method(input_method);                          } +                        window.draw_preedit(); +                          debug.render_started();                          match compositor.present(                              &mut window.renderer, @@ -1048,31 +1030,14 @@ async fn run_instance<P, C>(                              match ui_state {                                  user_interface::State::Updated {                                      redraw_request: _redraw_request, -                                    caret_info, +                                    input_method,                                  } => {                                      #[cfg(not(                                          feature = "unconditional-rendering"                                      ))] -                                    match _redraw_request { -                                        Some( -                                            window::RedrawRequest::NextFrame, -                                        ) => { -                                            window.raw.request_redraw(); -                                            window.redraw_at = None; -                                        } -                                        Some(window::RedrawRequest::At(at)) => { -                                            window.redraw_at = Some(at); -                                        } -                                        None => {} -                                    } +                                    window.request_redraw(_redraw_request); -                                    if let Some(caret_info) = caret_info { -                                        update_input_method( -                                            window, -                                            &mut preedit, -                                            &caret_info, -                                        ); -                                    } +                                    window.request_input_method(input_method);                                  }                                  user_interface::State::Outdated => {                                      uis_stale = true; @@ -1165,111 +1130,6 @@ async fn run_instance<P, C>(      let _ = ManuallyDrop::into_inner(user_interfaces);  } -fn update_input_method<P, C>( -    window: &mut crate::program::window_manager::Window<P, C>, -    preedit: &mut Preedit<P>, -    caret_info: &crate::core::CaretInfo, -) where -    P: Program, -    C: Compositor<Renderer = P::Renderer> + 'static, -{ -    window.raw.set_ime_allowed(caret_info.input_method_allowed); -    window.raw.set_ime_cursor_area( -        LogicalPosition::new(caret_info.position.x, caret_info.position.y), -        LogicalSize::new(10, 10), -    ); - -    let text = window.state.preedit(); -    if !text.is_empty() { -        preedit.update(text.as_str(), &window.renderer); -        preedit.fill( -            &mut window.renderer, -            window.state.text_color(), -            window.state.background_color(), -            caret_info.position, -        ); -    } -} - -struct Preedit<P: Program> { -    content: Option<<P::Renderer as core::text::Renderer>::Paragraph>, -} - -impl<P: Program> Preedit<P> { -    fn new() -> Self { -        Self { content: None } -    } - -    fn update(&mut self, text: &str, renderer: &P::Renderer) { -        use core::text::Paragraph as _; -        use core::text::Renderer as _; - -        self.content = Some( -            <P::Renderer as core::text::Renderer>::Paragraph::with_text( -                core::Text::<&str, <P::Renderer as core::text::Renderer>::Font> { -                    content: text, -                    bounds: Size::INFINITY, -                    size: renderer.default_size(), -                    line_height: core::text::LineHeight::default(), -                    font: renderer.default_font(), -                    horizontal_alignment: core::alignment::Horizontal::Left, -                    vertical_alignment: core::alignment::Vertical::Top, //Bottom, -                    shaping: core::text::Shaping::Advanced, -                    wrapping: core::text::Wrapping::None, -                }, -            ), -        ); -    } - -    fn fill( -        &self, -        renderer: &mut P::Renderer, -        fore_color: core::Color, -        bg_color: core::Color, -        caret_position: Point, -    ) { -        use core::text::Paragraph as _; -        use core::text::Renderer as _; -        use core::Renderer as _; - -        let Some(ref content) = self.content else { -            return; -        }; -        if content.min_width() < 1.0 { -            return; -        } - -        let top_left = Point::new( -            caret_position.x, -            caret_position.y - content.min_height(), -        ); -        let bounds = core::Rectangle::new(top_left, content.min_bounds()); -        renderer.with_layer(bounds, |renderer| { -            renderer.fill_quad( -                core::renderer::Quad { -                    bounds, -                    ..Default::default() -                }, -                core::Background::Color(bg_color), -            ); - -            let underline = 2.; -            renderer.fill_quad( -                core::renderer::Quad { -                    bounds: bounds.shrink(core::Padding { -                        top: bounds.height - underline, -                        ..Default::default() -                    }), -                    ..Default::default() -                }, -                core::Background::Color(fore_color), -            ); - -            renderer.fill_paragraph(content, top_left, fore_color, bounds); -        }); -    } -} -  /// Builds a window's [`UserInterface`] for the [`Program`].  fn build_user_interface<'a, P: Program>(      program: &'a P, diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index 6361d1ba..e883d04a 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -4,7 +4,7 @@ use crate::core::{Color, Size};  use crate::graphics::Viewport;  use crate::program::Program; -use winit::event::{Ime, Touch, WindowEvent}; +use winit::event::{Touch, WindowEvent};  use winit::window::Window;  use std::fmt::{Debug, Formatter}; @@ -22,7 +22,6 @@ where      modifiers: winit::keyboard::ModifiersState,      theme: P::Theme,      style: theme::Style, -    preedit: String,  }  impl<P: Program> Debug for State<P> @@ -74,7 +73,6 @@ where              modifiers: winit::keyboard::ModifiersState::default(),              theme,              style, -            preedit: String::default(),          }      } @@ -138,11 +136,6 @@ where          self.style.text_color      } -    /// TODO -    pub fn preedit(&self) -> String { -        self.preedit.clone() -    } -      /// Processes the provided window event and updates the [`State`] accordingly.      pub fn update(          &mut self, @@ -186,9 +179,6 @@ where              WindowEvent::ModifiersChanged(new_modifiers) => {                  self.modifiers = new_modifiers.state();              } -            WindowEvent::Ime(Ime::Preedit(text, _)) => { -                self.preedit = text.clone(); -            }              #[cfg(feature = "debug")]              WindowEvent::KeyboardInput {                  event: diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index a3c991df..cd49a8b4 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,13 +1,20 @@ +use crate::conversion; +use crate::core::alignment;  use crate::core::mouse; +use crate::core::renderer; +use crate::core::text;  use crate::core::theme;  use crate::core::time::Instant; -use crate::core::window::Id; -use crate::core::{Point, Size}; +use crate::core::window::{Id, RedrawRequest}; +use crate::core::{ +    Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector, +};  use crate::graphics::Compositor;  use crate::program::{Program, State};  use std::collections::BTreeMap;  use std::sync::Arc; +use winit::dpi::{LogicalPosition, LogicalSize};  use winit::monitor::MonitorHandle;  #[allow(missing_debug_implementations)] @@ -65,6 +72,7 @@ where                  renderer,                  mouse_interaction: mouse::Interaction::None,                  redraw_at: None, +                preedit: None,              },          ); @@ -155,6 +163,7 @@ where      pub surface: C::Surface,      pub renderer: P::Renderer,      pub redraw_at: Option<Instant>, +    preedit: Option<Preedit<P::Renderer>>,  }  impl<P, C> Window<P, C> @@ -179,4 +188,136 @@ where          Size::new(size.width, size.height)      } + +    pub fn request_redraw(&mut self, redraw_request: RedrawRequest) { +        match redraw_request { +            RedrawRequest::NextFrame => { +                self.raw.request_redraw(); +                self.redraw_at = None; +            } +            RedrawRequest::At(at) => { +                self.redraw_at = Some(at); +            } +            RedrawRequest::Wait => {} +        } +    } + +    pub fn request_input_method(&mut self, input_method: InputMethod) { +        self.raw.set_ime_allowed(match input_method { +            InputMethod::Disabled => false, +            InputMethod::Allowed | InputMethod::Open { .. } => true, +        }); + +        if let InputMethod::Open { +            position, +            purpose, +            preedit, +        } = input_method +        { +            self.raw.set_ime_cursor_area( +                LogicalPosition::new(position.x, position.y), +                LogicalSize::new(10, 10), +            ); + +            self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); + +            if let Some(content) = preedit { +                if let Some(preedit) = &mut self.preedit { +                    preedit.update(&content, &self.renderer); +                } else { +                    let mut preedit = Preedit::new(); +                    preedit.update(&content, &self.renderer); + +                    self.preedit = Some(preedit); +                } +            } +        } else { +            self.preedit = None; +        } +    } + +    pub fn draw_preedit(&mut self) { +        if let Some(preedit) = &self.preedit { +            preedit.draw( +                &mut self.renderer, +                self.state.text_color(), +                self.state.background_color(), +            ); +        } +    } +} + +struct Preedit<Renderer> +where +    Renderer: text::Renderer, +{ +    position: Point, +    content: text::paragraph::Plain<Renderer::Paragraph>, +} + +impl<Renderer> Preedit<Renderer> +where +    Renderer: text::Renderer, +{ +    fn new() -> Self { +        Self { +            position: Point::ORIGIN, +            content: text::paragraph::Plain::default(), +        } +    } + +    fn update(&mut self, text: &str, renderer: &Renderer) { +        self.content.update(Text { +            content: text, +            bounds: Size::INFINITY, +            size: renderer.default_size(), +            line_height: text::LineHeight::default(), +            font: renderer.default_font(), +            horizontal_alignment: alignment::Horizontal::Left, +            vertical_alignment: alignment::Vertical::Top, //Bottom, +            shaping: text::Shaping::Advanced, +            wrapping: text::Wrapping::None, +        }); +    } + +    fn draw(&self, renderer: &mut Renderer, color: Color, background: Color) { +        if self.content.min_width() < 1.0 { +            return; +        } + +        let top_left = +            self.position - Vector::new(0.0, self.content.min_height()); + +        let bounds = Rectangle::new(top_left, self.content.min_bounds()); + +        renderer.with_layer(bounds, |renderer| { +            renderer.fill_quad( +                renderer::Quad { +                    bounds, +                    ..Default::default() +                }, +                background, +            ); + +            renderer.fill_paragraph( +                self.content.raw(), +                top_left, +                color, +                bounds, +            ); + +            const UNDERLINE: f32 = 2.0; + +            renderer.fill_quad( +                renderer::Quad { +                    bounds: bounds.shrink(Padding { +                        top: bounds.height - UNDERLINE, +                        ..Default::default() +                    }), +                    ..Default::default() +                }, +                color, +            ); +        }); +    }  } | 
