diff --git a/Cargo.lock b/Cargo.lock index c71d6d8df0..c9c7ddc5f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -716,9 +716,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bytesize" @@ -3957,9 +3957,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.69" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -3989,9 +3989,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -4235,18 +4235,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", @@ -5920,7 +5920,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.0", + "winnow 0.7.1", ] [[package]] @@ -7166,9 +7166,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] diff --git a/core/src/element.rs b/core/src/element.rs index 82ba753b48..ede9e16cfe 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -311,7 +311,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -447,7 +447,7 @@ where fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/core/src/event.rs b/core/src/event.rs index b6cf321ec3..7f0ab91438 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 0000000000..4e8c383b7d --- /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 { + /// 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>, + }, +} + +/// The pre-edit of an [`InputMethod`]. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Preedit { + /// The current content. + pub content: T, + /// The selected range of the content. + pub selection: Option>, +} + +impl Preedit { + /// 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, + { + 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::::Allowed); + /// assert_eq!(ime, InputMethod::Allowed); + /// + /// ime.merge(&InputMethod::::Disabled); + /// assert_eq!(ime, InputMethod::Allowed); + /// + /// ime.merge(&open); + /// assert_eq!(ime, open); + /// + /// ime.merge(&open_2); + /// assert_eq!(ime, open); + /// ``` + pub fn merge>(&mut self, other: &InputMethod) { + 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 InputMethod { + /// Turns an [`InputMethod`] into its owned version. + pub fn to_owned(&self) -> InputMethod + where + T: AsRef, + { + 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>), + + /// 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 16b3aa0f27..c31a8da749 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/mouse/cursor.rs b/core/src/mouse/cursor.rs index 616cd31514..9388a5785f 100644 --- a/core/src/mouse/cursor.rs +++ b/core/src/mouse/cursor.rs @@ -1,13 +1,14 @@ use crate::{Point, Rectangle, Transformation, Vector}; -use std::ops::Mul; - /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Cursor { /// The cursor has a defined position. Available(Point), + /// The cursor has a defined position, but it's levitating over a layer above. + Levitating(Point), + /// The cursor is currently unavailable (i.e. out of bounds or busy). #[default] Unavailable, @@ -18,7 +19,7 @@ impl Cursor { pub fn position(self) -> Option { match self { Cursor::Available(position) => Some(position), - Cursor::Unavailable => None, + Cursor::Levitating(_) | Cursor::Unavailable => None, } } @@ -51,17 +52,41 @@ impl Cursor { pub fn is_over(self, bounds: Rectangle) -> bool { self.position_over(bounds).is_some() } + + /// Returns true if the [`Cursor`] is levitating over a layer above. + pub fn is_levitating(self) -> bool { + matches!(self, Self::Levitating(_)) + } + + /// Makes the [`Cursor`] levitate over a layer above. + pub fn levitate(self) -> Self { + match self { + Self::Available(position) => Self::Levitating(position), + _ => self, + } + } + + /// Brings the [`Cursor`] back to the current layer. + pub fn land(self) -> Self { + match self { + Cursor::Levitating(position) => Cursor::Available(position), + _ => self, + } + } } -impl Mul for Cursor { +impl std::ops::Mul for Cursor { type Output = Self; fn mul(self, transformation: Transformation) -> Self { match self { - Cursor::Unavailable => Cursor::Unavailable, - Cursor::Available(point) => { - Cursor::Available(point * transformation) + Self::Available(position) => { + Self::Available(position * transformation) + } + Self::Levitating(position) => { + Self::Levitating(position * transformation) } + Self::Unavailable => Self::Unavailable, } } } diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 383663af37..94239152b1 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -58,7 +58,7 @@ where /// By default, it does nothing. fn update( &mut self, - _event: Event, + _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 7a1796634a..de6e73fdad 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -51,7 +51,7 @@ where /// Processes a runtime [`Event`]. pub fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -150,7 +150,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index e07744e352..970c1b0e89 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -74,7 +74,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -82,14 +82,7 @@ where shell: &mut Shell<'_, Message>, ) { for (child, layout) in self.children.iter_mut().zip(layout.children()) { - child.update( - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - ); + child.update(event, layout, cursor, renderer, clipboard, shell); } } diff --git a/core/src/shell.rs b/core/src/shell.rs index 12ebbaa89a..509e3822fc 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, event_status: event::Status, - redraw_request: Option, + 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,27 +61,55 @@ 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, + ) { + 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 { + pub fn redraw_request(&self) -> window::RedrawRequest { self.redraw_request } + /// Replaces the redraw request of the [`Shell`]; without conflict resolution. + /// + /// This is useful if you want to overwrite the redraw request to a previous value. + /// Since it's a fairly advanced use case and should rarely be used, it is a static + /// method. + pub fn replace_redraw_request( + shell: &mut Self, + 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>( + &mut self, + ime: &InputMethod, + ) { + 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 @@ -122,20 +152,14 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge(&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 c144fd24b7..a7e1f28163 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 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 924276c35c..700c2c7595 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -129,6 +129,12 @@ impl Plain

{ 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/widget.rs b/core/src/widget.rs index 2a40f8234a..3c9c50ab28 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -114,7 +114,7 @@ where fn update( &mut self, _state: &mut Tree, - _event: Event, + _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index b0c000d6e9..0ae4facec6 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 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/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 4d438bd9e2..95ad299dbc 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -93,7 +93,7 @@ mod bezier { fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 1008e477d2..dec3df7f83 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -380,7 +380,7 @@ mod grid { fn update( &self, interaction: &mut Interaction, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { @@ -471,7 +471,7 @@ mod grid { _ => action.and_capture(), }) } - mouse::Event::WheelScrolled { delta } => match delta { + mouse::Event::WheelScrolled { delta } => match *delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { if y < 0.0 && self.scaling > Self::MIN_SCALING diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 33232fac7e..24293138bc 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -264,7 +264,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, @@ -278,7 +278,7 @@ where state.animation = state.animation.timed_transition( self.cycle_duration, self.rotation_duration, - now, + *now, ); state.cache.clear(); diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index a10b64f03e..a6713c7a7a 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -178,7 +178,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, @@ -189,7 +189,7 @@ where let state = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { - *state = state.timed_transition(self.cycle_duration, now); + *state = state.timed_transition(self.cycle_duration, *now); shell.request_redraw(); } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 5f4a5c90ec..bda3b8f759 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -55,11 +55,11 @@ impl canvas::Program for Multitouch { fn update( &self, _state: &mut Self::State, - event: Event, + event: &Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { - let message = match event { + let message = match event.clone() { Event::Touch( touch::Event::FingerPressed { id, position } | touch::Event::FingerMoved { id, position }, diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index d4d483f5d4..a4a89455b3 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -76,7 +76,7 @@ impl canvas::Program for SierpinskiGraph { fn update( &self, _state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 2ae9bfe23e..dc314df85f 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -361,7 +361,7 @@ mod toast { fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -491,7 +491,7 @@ mod toast { fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -530,7 +530,7 @@ mod toast { child.as_widget_mut().update( state, - event.clone(), + event, layout, cursor, renderer, diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 342ad70c69..38054d7b4a 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -160,7 +160,7 @@ where /// Processes a runtime [`Event`]. pub fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -170,7 +170,7 @@ where fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, - event: Event, + event: &Event, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, @@ -188,7 +188,7 @@ where recurse( &mut nested, nested_layout, - event.clone(), + event, cursor, renderer, clipboard, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index b2826f7114..cb4416788f 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,7 +5,9 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; -use crate::core::{Clipboard, Element, Layout, Rectangle, Shell, Size, Vector}; +use crate::core::{ + Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, +}; use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -186,7 +188,8 @@ where use std::mem::ManuallyDrop; let mut outdated = false; - let mut redraw_request = None; + let mut redraw_request = window::RedrawRequest::Wait; + let mut input_method = InputMethod::None; let mut manual_overlay = ManuallyDrop::new( self.root @@ -207,7 +210,7 @@ where let mut layout = overlay.layout(renderer, bounds); let mut event_statuses = Vec::new(); - for event in events.iter().cloned() { + for event in events { let mut shell = Shell::new(messages); overlay.update( @@ -220,16 +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); - } - _ => {} - } + 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); @@ -299,7 +294,6 @@ where let event_statuses = events .iter() - .cloned() .zip(overlay_statuses) .map(|(event, overlay_status)| { if matches!(overlay_status, event::Status::Captured) { @@ -323,15 +317,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); - } - _ => {} - } + redraw_request = redraw_request.min(shell.redraw_request()); + input_method.merge(shell.input_method()); shell.revalidate_layout(|| { self.base = self.root.as_widget().layout( @@ -355,7 +342,10 @@ where if outdated { State::Outdated } else { - State::Updated { redraw_request } + State::Updated { + redraw_request, + input_method, + } }, event_statuses, ) @@ -636,7 +626,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, @@ -644,7 +634,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, + /// 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/wgpu/src/lib.rs b/wgpu/src/lib.rs index 2283cf711e..9c9fcb31d4 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -557,6 +557,16 @@ impl core::svg::Renderer for Renderer { impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, mesh: graphics::Mesh) { + debug_assert!( + !mesh.indices().is_empty(), + "Mesh must not have empty indices" + ); + + debug_assert!( + mesh.indices().len() % 3 == 0, + "Mesh indices length must be a multiple of 3" + ); + let (layer, transformation) = self.layers.current_mut(); layer.draw_mesh(mesh, transformation); } diff --git a/widget/src/action.rs b/widget/src/action.rs index 1dd3a787a5..cc31e76a6e 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_to_publish: Option, - redraw_request: Option, + redraw_request: window::RedrawRequest, event_status: event::Status, } @@ -14,7 +14,7 @@ impl Action { 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 Action { /// 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 Action { /// 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 Action { /// widget implementations. pub fn into_inner( self, - ) -> ( - Option, - Option, - event::Status, - ) { + ) -> (Option, window::RedrawRequest, event::Status) { ( self.message_to_publish, self.redraw_request, diff --git a/widget/src/button.rs b/widget/src/button.rs index 11839d5eb2..0e24328f10 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -275,7 +275,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -285,7 +285,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout.children().next().unwrap(), cursor, renderer, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 23cc3f2b36..046abddfbb 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -218,7 +218,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -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/canvas/program.rs b/widget/src/canvas/program.rs index c68b28308a..43446b6424 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -32,7 +32,7 @@ where fn update( &self, _state: &mut Self::State, - _event: Event, + _event: &Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { @@ -82,7 +82,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 663bfad16f..6ed3e080aa 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -305,7 +305,7 @@ where fn update( &mut self, _tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/column.rs b/widget/src/column.rs index c729cbdbae..7200690b82 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -260,7 +260,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -275,13 +275,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 500d2bec56..f71e4a6e40 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, @@ -513,7 +512,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -541,7 +540,7 @@ where // Provide it to the widget self.text_input.update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -554,16 +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.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 { @@ -742,18 +733,21 @@ where published_message_to_shell = true; // Unfocus the input + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); self.text_input.update( &mut tree.children[0], - Event::Mouse(mouse::Event::ButtonPressed( + &Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, )), layout, mouse::Cursor::Unavailable, renderer, clipboard, - &mut Shell::new(&mut vec![]), + &mut local_shell, viewport, ); + shell.request_input_method(local_shell.input_method()); } }); diff --git a/widget/src/container.rs b/widget/src/container.rs index 852481f1d7..82dc314134 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -300,7 +300,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 17cf94cc0d..4cba197d06 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -633,7 +633,7 @@ where fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -836,7 +836,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -871,26 +871,28 @@ where shell.request_redraw(); } + let is_visible = + is_hovered || self.is_top_focused || self.is_top_overlay_active; + if matches!( event, Event::Mouse( mouse::Event::CursorMoved { .. } | mouse::Event::ButtonReleased(_) ) - ) || is_hovered - || self.is_top_focused - || self.is_top_overlay_active + ) || is_visible { + let redraw_request = shell.redraw_request(); + self.top.as_widget_mut().update( - top_tree, - event.clone(), - top_layout, - cursor, - renderer, - clipboard, - shell, - viewport, + top_tree, event, top_layout, cursor, renderer, clipboard, + shell, viewport, ); + + // Ignore redraw requests of invisible content + if !is_visible { + Shell::replace_redraw_request(shell, redraw_request); + } }; if shell.is_event_captured() { @@ -899,7 +901,7 @@ where self.base.as_widget_mut().update( base_tree, - event.clone(), + event, base_layout, cursor, renderer, diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 20a7955f3a..06652ff731 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -151,7 +151,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -167,7 +167,7 @@ where return; }; - match delta { + match *delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { let state = tree.state.downcast_mut::(); @@ -256,7 +256,7 @@ where .max(0.0) .round(); - let delta = position - origin; + let delta = *position - origin; let x = if bounds.width < scaled_size.width { (state.starting_offset.x - delta.x) diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index ab0b0bde5a..313b728ab8 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -300,7 +300,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -315,13 +315,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index c6710e300a..6df026de34 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -198,7 +198,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -386,7 +386,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 15b8b62ed3..30822b7d52 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, @@ -314,7 +313,7 @@ where fn update( &mut self, tree: &mut Tree, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -344,17 +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.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(); @@ -603,7 +593,7 @@ where fn update( &mut self, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -629,17 +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.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/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 8129336e09..e7c937afc6 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -188,7 +188,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -416,7 +416,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 6e3d599860..95f8aeb248 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -288,7 +288,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -298,7 +298,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -396,7 +396,7 @@ where fn update( widget: &mut MouseArea<'_, Message, Theme, Renderer>, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, @@ -507,7 +507,7 @@ fn update( } Event::Mouse(mouse::Event::WheelScrolled { delta }) => { if let Some(on_scroll) = widget.on_scroll.as_ref() { - shell.publish(on_scroll(delta)); + shell.publish(on_scroll(*delta)); shell.capture_event(); } } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 611476ce36..9d0539ffc6 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -263,7 +263,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -388,7 +388,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5c3b343c80..3ae1dfc7e6 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -474,7 +474,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -509,15 +509,8 @@ where let is_picked = picked_pane == Some(pane); content.update( - tree, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - is_picked, + tree, event, layout, cursor, renderer, clipboard, shell, + viewport, is_picked, ); } @@ -687,7 +680,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/pane_grid/content.rs b/widget/src/pane_grid/content.rs index be5e506609..4d63dd1843 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -242,7 +242,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -256,7 +256,7 @@ where title_bar.update( &mut tree.children[1], - event.clone(), + event, children.next().unwrap(), cursor, renderer, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 4bd2c2f6da..611c3d6703 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -430,7 +430,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -456,7 +456,7 @@ where compact.as_widget_mut().update( &mut tree.children[2], - event.clone(), + event, compact_layout, cursor, renderer, @@ -469,7 +469,7 @@ where controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, @@ -481,7 +481,7 @@ where } else { controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 6708e7cd14..b751fcc34e 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -430,7 +430,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -489,13 +489,13 @@ where let options = self.options.borrow(); let selected = self.selected.as_ref().map(Borrow::borrow); - let next_option = if y < 0.0 { + let next_option = if *y < 0.0 { if let Some(selected) = selected { find_next(selected, options.iter()) } else { options.first() } - } else if y > 0.0 { + } else if *y > 0.0 { if let Some(selected) = selected { find_next(selected, options.iter().rev()) } else { @@ -513,7 +513,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} }; diff --git a/widget/src/pin.rs b/widget/src/pin.rs index 7c1aca612f..afa2939840 100644 --- a/widget/src/pin.rs +++ b/widget/src/pin.rs @@ -177,7 +177,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pop.rs b/widget/src/pop.rs index 146cfb2b26..6e9df4be6a 100644 --- a/widget/src/pop.rs +++ b/widget/src/pop.rs @@ -108,7 +108,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 15c983df68..0df4d7157d 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -326,7 +326,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/row.rs b/widget/src/row.rs index 3b605f0754..5ffeab498c 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -256,7 +256,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -271,13 +271,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -495,7 +489,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 312aee2968..8adf51362f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - self, Background, Clipboard, Color, Element, Event, Layout, Length, - Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + self, Background, Clipboard, Color, Element, Event, InputMethod, Layout, + Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, + Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -516,7 +517,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -563,7 +564,8 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(scrollbar) = scrollbars.y { - let Some(cursor_position) = cursor.position() + let Some(cursor_position) = + cursor.land().position() else { return; }; @@ -635,7 +637,8 @@ where match event { Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - let Some(cursor_position) = cursor.position() else { + let Some(cursor_position) = cursor.land().position() + else { return; }; @@ -726,12 +729,14 @@ where _ => mouse::Cursor::Unavailable, }; + let had_input_method = shell.input_method().is_open(); + let translation = state.translation(self.direction, bounds, content_bounds); self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, content, cursor, renderer, @@ -743,6 +748,14 @@ where ..bounds }, ); + + if !had_input_method { + if let InputMethod::Open { position, .. } = + shell.input_method_mut() + { + *position = *position + translation; + } + } }; if matches!( @@ -768,7 +781,7 @@ where modifiers, )) = event { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; return; } @@ -779,7 +792,7 @@ where return; } - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x, y } => { let is_shift_pressed = state.keyboard_modifiers.shift(); diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8ec57482c7..06254a1c1d 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; @@ -89,7 +88,7 @@ where fn update( &mut self, tree: &mut Tree, - event: crate::core::Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -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(); } @@ -184,7 +174,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 0fc110afeb..81ecc9b122 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -26,7 +26,7 @@ pub trait Program { fn update( &self, _state: &mut Self::State, - _event: shader::Event, + _event: &shader::Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 5250085473..1908abc994 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -245,7 +245,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 12ed941dbb..df9f616288 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -207,7 +207,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, mut cursor: mouse::Cursor, renderer: &Renderer, @@ -216,43 +216,34 @@ where viewport: &Rectangle, ) { let is_over = cursor.is_over(layout.bounds()); - let is_mouse_movement = - matches!(event, Event::Mouse(mouse::Event::CursorMoved { .. })); + let end = self.children.len() - 1; - for ((child, state), layout) in self + for (i, ((child, state), layout)) in self .children .iter_mut() .rev() .zip(tree.children.iter_mut().rev()) .zip(layout.children().rev()) + .enumerate() { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); - if is_over - && !is_mouse_movement - && cursor != mouse::Cursor::Unavailable - { + if shell.is_event_captured() { + return; + } + + if i < end && is_over && !cursor.is_levitating() { let interaction = child.as_widget().mouse_interaction( state, layout, cursor, viewport, renderer, ); if interaction != mouse::Interaction::None { - cursor = mouse::Cursor::Unavailable; + cursor = cursor.levitate(); } } - - if shell.is_event_captured() { - return; - } } } diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 0b499ec6c0..a9e544d14e 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -358,7 +358,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index f1ec589b04..e685256bff 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,6 +33,7 @@ //! ``` use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -46,14 +47,15 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, SmolStr, Theme, Vector, + Background, Border, Color, Element, Event, InputMethod, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; use std::borrow::Cow; use std::cell::RefCell; use std::fmt; use std::ops::DerefMut; +use std::ops::Range; use std::sync::Arc; pub use text::editor::{Action, Edit, Line, LineEnding, Motion}; @@ -322,6 +324,51 @@ where self.class = class.into(); self } + + fn input_method<'b>( + &self, + state: &'b State, + renderer: &Renderer, + layout: Layout<'_>, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.focus + else { + return InputMethod::Disabled; + }; + + let Some(preedit) = &state.preedit else { + return InputMethod::Allowed; + }; + + let bounds = layout.bounds(); + let internal = self.content.0.borrow_mut(); + + let text_bounds = bounds.shrink(self.padding); + let translation = text_bounds.position() - Point::ORIGIN; + + 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.as_ref()), + } + } } /// The content of a [`TextEditor`]. @@ -450,6 +497,7 @@ where #[derive(Debug)] pub struct State { focus: Option, + preedit: Option, last_click: Option, drag_click: Option, partial_scroll: f32, @@ -458,7 +506,7 @@ pub struct State { highlighter_format_address: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, @@ -524,6 +572,7 @@ where fn state(&self) -> widget::tree::State { widget::tree::State::new(State { focus: None, + preedit: None, last_click: None, drag_click: None, partial_scroll: 0.0, @@ -602,10 +651,10 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, - _renderer: &Renderer, + renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, @@ -637,17 +686,18 @@ where Event::Window(window::Event::RedrawRequested(now)) => { if let Some(focus) = &mut state.focus { if focus.is_window_focused { - focus.now = now; + focus.now = *now; let millis_until_redraw = Focus::CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() + - (focus.now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; shell.request_redraw_at( - now + Duration::from_millis( - millis_until_redraw as u64, - ), + focus.now + + Duration::from_millis( + millis_until_redraw as u64, + ), ); } } @@ -701,6 +751,25 @@ where })); shell.capture_event(); } + Update::InputMethod(update) => match update { + Ime::Toggle(is_open) => { + state.preedit = + is_open.then(input_method::Preedit::new); + + shell.request_redraw(); + } + Ime::Preedit { content, selection } => { + state.preedit = + Some(input_method::Preedit { content, selection }); + + shell.request_redraw(); + } + Ime::Commit(text) => { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(text), + )))); + } + }, Update::Binding(binding) => { fn apply_binding< H: text::Highlighter, @@ -827,6 +896,10 @@ where 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) @@ -1129,12 +1202,22 @@ enum Update { Drag(Point), Release, Scroll(f32), + InputMethod(Ime), Binding(Binding), } +enum Ime { + Toggle(bool), + Preedit { + content: String, + selection: Option>, + }, + Commit(String), +} + impl Update { fn from_event( - event: Event, + event: &Event, state: &State, bounds: Rectangle, padding: Padding, @@ -1191,6 +1274,28 @@ impl Update { } _ => None, }, + 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, selection) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Preedit { + content: content.clone(), + selection: selection.clone(), + })) + } + input_method::Event::Commit(content) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Commit(content.clone()))) + } + _ => None, + }, Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, @@ -1206,9 +1311,9 @@ impl Update { }; let key_press = KeyPress { - key, - modifiers, - text, + key: key.clone(), + modifiers: *modifiers, + text: text.clone(), status, }; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 57ebe46a12..215ecbd6a3 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,6 +42,7 @@ use editor::Editor; use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout; @@ -56,8 +57,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, Color, Element, Event, InputMethod, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -391,6 +392,58 @@ where } } + fn input_method<'b>( + &self, + state: &'b State, + layout: Layout<'_>, + value: &Value, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.is_focused + else { + 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); + + let text_bounds = layout.children().next().unwrap().bounds(); + + 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 (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 x = (text_bounds.x + cursor_x).floor() - scroll_offset + + alignment_offset; + + InputMethod::Open { + 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()), + } + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -639,7 +692,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -1197,6 +1250,51 @@ where state.keyboard_modifiers = *modifiers; } + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + let state = state::(tree); + + state.is_ime_open = + matches!(event, input_method::Event::Opened) + .then(input_method::Preedit::new); + + shell.request_redraw(); + } + input_method::Event::Preedit(content, selection) => { + let state = state::(tree); + + if state.is_focused.is_some() { + state.is_ime_open = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + }); + + shell.request_redraw(); + } + } + input_method::Event::Commit(text) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return; + }; + + 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; + + 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::(tree); @@ -1258,6 +1356,12 @@ 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) @@ -1417,6 +1521,7 @@ pub struct State { placeholder: paragraph::Plain

, icon: paragraph::Plain

, is_focused: Option, + is_ime_open: Option, is_dragging: bool, is_pasting: Option, last_click: Option, @@ -1431,7 +1536,7 @@ fn state( tree.state.downcast_mut::>() } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 769cc4ca6a..4e583882af 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -113,7 +113,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -220,7 +220,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 56c2be1fcd..b711432e10 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -309,7 +309,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index a0ffe39206..5bebeeace2 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -192,7 +192,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 2ed9419ab8..6f878fde37 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -249,7 +249,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -379,7 +379,7 @@ where if state.keyboard_modifiers.control() => { if cursor.is_over(layout.bounds()) { - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x: _, y } => y, mouse::ScrollDelta::Pixels { x: _, y } => y, }; @@ -411,7 +411,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 462be65b03..ab84afff27 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,6 +2,7 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime +use crate::core::input_method; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -140,6 +141,7 @@ pub fn window_event( scale_factor: f64, modifiers: winit::keyboard::ModifiersState, ) -> Option { + use winit::event::Ime; use winit::event::WindowEvent; match event { @@ -283,6 +285,15 @@ pub fn window_event( self::modifiers(new_modifiers.state()), ))) } + 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 { @@ -1160,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 { @@ -1169,6 +1180,17 @@ pub fn icon(icon: window::Icon) -> Option { 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 d84362121b..7ead4c3b53 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -873,20 +873,16 @@ async fn run_instance( }); if let user_interface::State::Updated { - redraw_request: Some(redraw_request), + redraw_request, + input_method, } = ui_state { - match redraw_request { - window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); - window.redraw_at = None; - } - window::RedrawRequest::At(at) => { - window.redraw_at = Some(at); - } - } + window.request_redraw(redraw_request); + window.request_input_method(input_method); } + window.draw_preedit(); + debug.render_started(); match compositor.present( &mut window.renderer, @@ -1029,27 +1025,23 @@ async fn run_instance( ); #[cfg(feature = "unconditional-rendering")] - window.raw.request_redraw(); + window.request_redraw( + window::RedrawRequest::NextFrame, + ); match ui_state { - #[cfg(not( - feature = "unconditional-rendering" - ))] user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); - window.redraw_at = None; - } - window::RedrawRequest::At(at) => { - window.redraw_at = Some(at); - } - }, + redraw_request: _redraw_request, + .. + } => { + #[cfg(not( + feature = "unconditional-rendering" + ))] + window.request_redraw(_redraw_request); + } user_interface::State::Outdated => { uis_stale = true; } - user_interface::State::Updated { .. } => {} } for (event, status) in window_events diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index a3c991df0d..ae214e7cfd 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,14 +1,23 @@ +use crate::conversion; +use crate::core::alignment; +use crate::core::input_method; 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 winit::dpi::{LogicalPosition, LogicalSize}; +use winit::monitor::MonitorHandle; + use std::collections::BTreeMap; use std::sync::Arc; -use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] pub struct WindowManager @@ -65,6 +74,7 @@ where renderer, mouse_interaction: mouse::Interaction::None, redraw_at: None, + preedit: None, }, ); @@ -155,6 +165,7 @@ where pub surface: C::Surface, pub renderer: P::Renderer, pub redraw_at: Option, + preedit: Option>, } impl Window @@ -179,4 +190,216 @@ 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) { + match input_method { + InputMethod::None => {} + InputMethod::Disabled => { + self.raw.set_ime_allowed(false); + } + InputMethod::Allowed | InputMethod::Open { .. } => { + self.raw.set_ime_allowed(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), // TODO? + ); + + self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); + + if let Some(preedit) = preedit { + if preedit.content.is_empty() { + self.preedit = None; + } else if let Some(overlay) = &mut self.preedit { + overlay.update( + position, + &preedit, + self.state.background_color(), + &self.renderer, + ); + } else { + let mut overlay = Preedit::new(); + overlay.update( + position, + &preedit, + self.state.background_color(), + &self.renderer, + ); + + self.preedit = Some(overlay); + } + } + } 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(), + &Rectangle::new( + Point::ORIGIN, + self.state.viewport().logical_size(), + ), + ); + } + } +} + +struct Preedit +where + Renderer: text::Renderer, +{ + position: Point, + content: Renderer::Paragraph, + spans: Vec>, +} + +impl Preedit +where + Renderer: text::Renderer, +{ + fn new() -> Self { + Self { + position: Point::ORIGIN, + spans: Vec::new(), + content: Renderer::Paragraph::default(), + } + } + + fn update( + &mut self, + position: Point, + preedit: &input_method::Preedit, + background: Color, + renderer: &Renderer, + ) { + self.position = position; + + let spans = match &preedit.selection { + Some(selection) => { + vec![ + text::Span::new(&preedit.content[..selection.start]), + text::Span::new(if selection.start == selection.end { + "\u{200A}" + } else { + &preedit.content[selection.start..selection.end] + }) + .color(background), + text::Span::new(&preedit.content[selection.end..]), + ] + } + _ => vec![text::Span::new(&preedit.content)], + }; + + if spans != self.spans.as_slice() { + use text::Paragraph as _; + + self.content = Renderer::Paragraph::with_spans(Text { + content: &spans, + 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, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::None, + }); + } + } + + fn draw( + &self, + renderer: &mut Renderer, + color: Color, + background: Color, + viewport: &Rectangle, + ) { + use text::Paragraph as _; + + if self.content.min_width() < 1.0 { + return; + } + + let mut bounds = Rectangle::new( + self.position - Vector::new(0.0, self.content.min_height()), + self.content.min_bounds(), + ); + + bounds.x = bounds + .x + .max(viewport.x) + .min(viewport.x + viewport.width - bounds.width); + + bounds.y = bounds + .y + .max(viewport.y) + .min(viewport.y + viewport.height - bounds.height); + + renderer.with_layer(bounds, |renderer| { + renderer.fill_quad( + renderer::Quad { + bounds, + ..Default::default() + }, + background, + ); + + renderer.fill_paragraph( + &self.content, + bounds.position(), + 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, + ); + + for span_bounds in self.content.span_bounds(1) { + renderer.fill_quad( + renderer::Quad { + bounds: span_bounds + + (bounds.position() - Point::ORIGIN), + ..Default::default() + }, + color, + ); + } + }); + } }