diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/src/animation.rs | 12 | ||||
-rw-r--r-- | core/src/element.rs | 9 | ||||
-rw-r--r-- | core/src/event.rs | 4 | ||||
-rw-r--r-- | core/src/input_method.rs | 221 | ||||
-rw-r--r-- | core/src/length.rs | 6 | ||||
-rw-r--r-- | core/src/lib.rs | 68 | ||||
-rw-r--r-- | core/src/mouse/cursor.rs | 39 | ||||
-rw-r--r-- | core/src/overlay.rs | 2 | ||||
-rw-r--r-- | core/src/overlay/element.rs | 4 | ||||
-rw-r--r-- | core/src/overlay/group.rs | 11 | ||||
-rw-r--r-- | core/src/padding.rs | 6 | ||||
-rw-r--r-- | core/src/pixels.rs | 14 | ||||
-rw-r--r-- | core/src/shell.rs | 76 | ||||
-rw-r--r-- | core/src/text.rs | 27 | ||||
-rw-r--r-- | core/src/text/paragraph.rs | 6 | ||||
-rw-r--r-- | core/src/widget.rs | 2 | ||||
-rw-r--r-- | core/src/widget/operation/focusable.rs | 27 | ||||
-rw-r--r-- | core/src/window/redraw_request.rs | 12 |
18 files changed, 481 insertions, 65 deletions
diff --git a/core/src/animation.rs b/core/src/animation.rs index 258fd084..14cbb5c3 100644 --- a/core/src/animation.rs +++ b/core/src/animation.rs @@ -13,6 +13,7 @@ where T: Clone + Copy + PartialEq + Float, { raw: lilt::Animated<T, Instant>, + duration: Duration, // TODO: Expose duration getter in `lilt` } impl<T> Animation<T> @@ -23,6 +24,7 @@ where pub fn new(state: T) -> Self { Self { raw: lilt::Animated::new(state), + duration: Duration::from_millis(100), } } @@ -58,6 +60,7 @@ where /// Sets the duration of the [`Animation`] to the given value. pub fn duration(mut self, duration: Duration) -> Self { self.raw = self.raw.duration(duration.as_secs_f32() * 1_000.0); + self.duration = duration; self } @@ -133,4 +136,13 @@ impl Animation<bool> { { self.raw.animate_bool(start, end, at) } + + /// Returns the remaining [`Duration`] of the [`Animation`]. + pub fn remaining(&self, at: Instant) -> Duration { + Duration::from_secs_f32(self.interpolate( + self.duration.as_secs_f32(), + 0.0, + at, + )) + } } diff --git a/core/src/element.rs b/core/src/element.rs index 82ba753b..b7d51aeb 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -93,6 +93,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// /// ```no_run /// # mod iced { + /// # pub use iced_core::Function; /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # pub mod widget { @@ -119,7 +120,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// use counter::Counter; /// /// use iced::widget::row; - /// use iced::Element; + /// use iced::{Element, Function}; /// /// struct ManyCounters { /// counters: Vec<Counter>, @@ -142,7 +143,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// // Here we turn our `Element<counter::Message>` into /// // an `Element<Message>` by combining the `index` and the /// // message of the `element`. - /// counter.map(move |message| Message::Counter(index, message)) + /// counter.map(Message::Counter.with(index)) /// }), /// ) /// .into() @@ -311,7 +312,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -447,7 +448,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 b6cf321e..7f0ab914 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,4 +1,5 @@ //! Handle events of a user interface. +use crate::input_method; use crate::keyboard; use crate::mouse; use crate::touch; @@ -23,6 +24,9 @@ pub enum Event { /// A touch event Touch(touch::Event), + + /// An input method event + InputMethod(input_method::Event), } /// The status of an [`Event`] after being processed. diff --git a/core/src/input_method.rs b/core/src/input_method.rs new file mode 100644 index 00000000..cd8d459d --- /dev/null +++ b/core/src/input_method.rs @@ -0,0 +1,221 @@ +//! Listen to input method events. +use crate::{Pixels, Point}; + +use std::ops::Range; + +/// The input method strategy of a widget. +#[derive(Debug, Clone, PartialEq)] +pub enum InputMethod<T = String> { + /// Input method is disabled. + Disabled, + /// Input method is enabled. + Enabled { + /// The position at which the input method dialog should be placed. + position: Point, + /// The [`Purpose`] of the input method. + purpose: Purpose, + /// The preedit to overlay on top of the input method dialog, if needed. + /// + /// Ideally, your widget will show pre-edits on-the-spot; but, since that can + /// be tricky, you can instead provide the current pre-edit here and the + /// runtime will display it as an overlay (i.e. "Over-the-spot IME"). + preedit: Option<Preedit<T>>, + }, +} + +/// The pre-edit of an [`InputMethod`]. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Preedit<T = String> { + /// The current content. + pub content: T, + /// The selected range of the content. + pub selection: Option<Range<usize>>, + /// The text size of the content. + pub text_size: Option<Pixels>, +} + +impl<T> Preedit<T> { + /// Creates a new empty [`Preedit`]. + pub fn new() -> Self + where + T: Default, + { + Self::default() + } + + /// Turns a [`Preedit`] into its owned version. + pub fn to_owned(&self) -> Preedit + where + T: AsRef<str>, + { + Preedit { + content: self.content.as_ref().to_owned(), + selection: self.selection.clone(), + text_size: self.text_size, + } + } +} + +impl Preedit { + /// Borrows the contents of a [`Preedit`]. + pub fn as_ref(&self) -> Preedit<&str> { + Preedit { + content: &self.content, + selection: self.selection.clone(), + text_size: self.text_size, + } + } +} + +/// 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::Enabled { + /// position: Point::ORIGIN, + /// purpose: Purpose::Normal, + /// preedit: Some(Preedit { content: "1".to_owned(), selection: None, text_size: None }), + /// }; + /// + /// let open_2 = InputMethod::Enabled { + /// position: Point::ORIGIN, + /// purpose: Purpose::Secure, + /// preedit: Some(Preedit { content: "2".to_owned(), selection: None, text_size: None }), + /// }; + /// + /// let mut ime = InputMethod::Disabled; + /// + /// ime.merge(&open); + /// assert_eq!(ime, open); + /// + /// ime.merge(&open_2); + /// assert_eq!(ime, open); + /// ``` + pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) { + if let InputMethod::Enabled { .. } = self { + return; + } + + *self = other.to_owned(); + } + + /// Returns true if the [`InputMethod`] is open. + pub fn is_enabled(&self) -> bool { + matches!(self, Self::Enabled { .. }) + } +} + +impl<T> InputMethod<T> { + /// Turns an [`InputMethod`] into its owned version. + pub fn to_owned(&self) -> InputMethod + where + T: AsRef<str>, + { + match self { + Self::Disabled => InputMethod::Disabled, + Self::Enabled { + position, + purpose, + preedit, + } => InputMethod::Enabled { + position: *position, + purpose: *purpose, + preedit: preedit.as_ref().map(Preedit::to_owned), + }, + } + } +} + +/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. +/// +/// This is also called a "composition event". +/// +/// Most keypresses using a latin-like keyboard layout simply generate a +/// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed). +/// However, one couldn't possibly have a key for every single +/// unicode character that the user might want to type. The solution operating systems employ is +/// to allow the user to type these using _a sequence of keypresses_ instead. +/// +/// A prominent example of this is accents—many keyboard layouts allow you to first click the +/// "accent key", and then the character you want to apply the accent to. In this case, some +/// platforms will generate the following event sequence: +/// +/// ```ignore +/// // Press "`" key +/// Ime::Preedit("`", Some((0, 0))) +/// // Press "E" key +/// Ime::Preedit("", None) // Synthetic event generated to clear preedit. +/// Ime::Commit("é") +/// ``` +/// +/// Additionally, certain input devices are configured to display a candidate box that allow the +/// user to select the desired character interactively. (To properly position this box, you must use +/// [`Shell::request_input_method`](crate::Shell::request_input_method).) +/// +/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the +/// following event sequence could be obtained: +/// +/// ```ignore +/// // Press "A" key +/// Ime::Preedit("a", Some((1, 1))) +/// // Press "B" key +/// Ime::Preedit("a b", Some((3, 3))) +/// // Press left arrow key +/// Ime::Preedit("a b", Some((1, 1))) +/// // Press space key +/// Ime::Preedit("啊b", Some((3, 3))) +/// // Press space key +/// Ime::Preedit("", None) // Synthetic event generated to clear preedit. +/// Ime::Commit("啊不") +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Event { + /// Notifies when the IME was opened. + /// + /// After getting this event you could receive [`Preedit`][Self::Preedit] and + /// [`Commit`][Self::Commit] events. You should also start performing IME related requests + /// like [`Shell::request_input_method`]. + /// + /// [`Shell::request_input_method`]: crate::Shell::request_input_method + Opened, + + /// Notifies when a new composing text should be set at the cursor position. + /// + /// The value represents a pair of the preedit string and the cursor begin position and end + /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string + /// this indicates that preedit was cleared. + /// + /// The cursor range is byte-wise indexed. + Preedit(String, Option<Range<usize>>), + + /// Notifies when text should be inserted into the editor widget. + /// + /// Right before this event, an empty [`Self::Preedit`] event will be issued. + Commit(String), + + /// Notifies when the IME was disabled. + /// + /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or + /// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should + /// also stop issuing IME related requests like [`Shell::request_input_method`] and clear + /// pending preedit text. + /// + /// [`Shell::request_input_method`]: crate::Shell::request_input_method + Closed, +} diff --git a/core/src/length.rs b/core/src/length.rs index 5f24169f..363833c4 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -77,8 +77,8 @@ impl From<f32> for Length { } } -impl From<u16> for Length { - fn from(units: u16) -> Self { - Length::Fixed(f32::from(units)) +impl From<u32> for Length { + fn from(units: u32) -> Self { + Length::Fixed(units as f32) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 16b3aa0f..03cc0632 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; @@ -82,3 +84,69 @@ pub use vector::Vector; pub use widget::Widget; pub use smol_str::SmolStr; + +/// A function that can _never_ be called. +/// +/// This is useful to turn generic types into anything +/// you want by coercing them into a type with no possible +/// values. +pub fn never<T>(never: std::convert::Infallible) -> T { + match never {} +} + +/// A trait extension for binary functions (`Fn(A, B) -> O`). +/// +/// It enables you to use a bunch of nifty functional programming paradigms +/// that work well with iced. +pub trait Function<A, B, O> { + /// Applies the given first argument to a binary function and returns + /// a new function that takes the other argument. + /// + /// This lets you partially "apply" a function—equivalent to currying, + /// but it only works with binary functions. If you want to apply an + /// arbitrary number of arguments, create a little struct for them. + /// + /// # When is this useful? + /// Sometimes you will want to identify the source or target + /// of some message in your user interface. This can be achieved through + /// normal means by defining a closure and moving the identifier + /// inside: + /// + /// ```rust + /// # let element: Option<()> = Some(()); + /// # enum Message { ButtonPressed(u32, ()) } + /// let id = 123; + /// + /// # let _ = { + /// element.map(move |result| Message::ButtonPressed(id, result)) + /// # }; + /// ``` + /// + /// That's quite a mouthful. [`with`](Self::with) lets you write: + /// + /// ```rust + /// # use iced_core::Function; + /// # let element: Option<()> = Some(()); + /// # enum Message { ButtonPressed(u32, ()) } + /// let id = 123; + /// + /// # let _ = { + /// element.map(Message::ButtonPressed.with(id)) + /// # }; + /// ``` + /// + /// Effectively creating the same closure that partially applies + /// the `id` to the message—but much more concise! + fn with(self, prefix: A) -> impl Fn(B) -> O; +} + +impl<F, A, B, O> Function<A, B, O> for F +where + F: Fn(A, B) -> O, + Self: Sized, + A: Copy, +{ + fn with(self, prefix: A) -> impl Fn(B) -> O { + move |result| self(prefix, result) + } +} diff --git a/core/src/mouse/cursor.rs b/core/src/mouse/cursor.rs index 616cd315..9388a578 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<Point> { 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<Transformation> for Cursor { +impl std::ops::Mul<Transformation> 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 383663af..94239152 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 7a179663..de6e73fd 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 e07744e3..970c1b0e 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/padding.rs b/core/src/padding.rs index e26cdd9b..9ec02e6d 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -202,3 +202,9 @@ impl From<Padding> for Size { Self::new(padding.horizontal(), padding.vertical()) } } + +impl From<Pixels> for Padding { + fn from(pixels: Pixels) -> Self { + Self::from(pixels.0) + } +} diff --git a/core/src/pixels.rs b/core/src/pixels.rs index a1ea0f15..c87e2b31 100644 --- a/core/src/pixels.rs +++ b/core/src/pixels.rs @@ -20,9 +20,9 @@ impl From<f32> for Pixels { } } -impl From<u16> for Pixels { - fn from(amount: u16) -> Self { - Self(f32::from(amount)) +impl From<u32> for Pixels { + fn from(amount: u32) -> Self { + Self(amount as f32) } } @@ -79,3 +79,11 @@ impl std::ops::Div<f32> for Pixels { Pixels(self.0 / rhs) } } + +impl std::ops::Div<u32> for Pixels { + type Output = Pixels; + + fn div(self, rhs: u32) -> Self { + Pixels(self.0 / rhs as f32) + } +} diff --git a/core/src/shell.rs b/core/src/shell.rs index 12ebbaa8..56250e2e 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,6 +1,6 @@ use crate::event; -use crate::time::Instant; use crate::window; +use crate::InputMethod; /// A connection to the state of a shell. /// @@ -12,7 +12,8 @@ use crate::window; pub struct Shell<'a, Message> { messages: &'a mut Vec<Message>, event_status: event::Status, - redraw_request: Option<window::RedrawRequest>, + redraw_request: window::RedrawRequest, + input_method: InputMethod, is_layout_invalid: bool, are_widgets_invalid: bool, } @@ -23,9 +24,10 @@ impl<'a, Message> Shell<'a, Message> { Self { messages, event_status: event::Status::Ignored, - redraw_request: None, + redraw_request: window::RedrawRequest::Wait, is_layout_invalid: false, are_widgets_invalid: false, + input_method: InputMethod::Disabled, } } @@ -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<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 } + /// 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<T: AsRef<str>>( + &mut self, + ime: &InputMethod<T>, + ) { + self.input_method.merge(ime); + } + + /// Returns the current [`InputMethod`] strategy. + pub fn input_method(&self) -> &InputMethod { + &self.input_method + } + + /// Returns the current [`InputMethod`] strategy. + pub fn input_method_mut(&mut self) -> &mut InputMethod { + &mut self.input_method + } + /// Returns whether the current layout is invalid or not. pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid @@ -122,20 +152,14 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(new) = other.redraw_request { - self.redraw_request = Some( - self.redraw_request - .map(|current| if current < new { current } else { new }) - .unwrap_or(new), - ); - } - self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; + self.redraw_request = self.redraw_request.min(other.redraw_request); self.event_status = self.event_status.merge(other.event_status); + self.input_method.merge(&other.input_method); } } diff --git a/core/src/text.rs b/core/src/text.rs index c144fd24..a7e1f281 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -284,15 +284,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> { pub fn new(fragment: impl IntoFragment<'a>) -> Self { Self { text: fragment.into_fragment(), - size: None, - line_height: None, - font: None, - color: None, - highlight: None, - link: None, - padding: Padding::ZERO, - underline: false, - strikethrough: false, + ..Self::default() } } @@ -440,6 +432,23 @@ impl<'a, Link, Font> Span<'a, Link, Font> { } } +impl<Link, Font> Default for Span<'_, Link, Font> { + fn default() -> Self { + Self { + text: Cow::default(), + size: None, + line_height: None, + font: None, + color: None, + link: None, + highlight: None, + padding: Padding::default(), + underline: false, + strikethrough: false, + } + } +} + impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> { fn from(value: &'a str) -> Self { Span::new(value) diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 924276c3..700c2c75 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -129,6 +129,12 @@ impl<P: Paragraph> Plain<P> { self.raw.min_width() } + /// Returns the minimum height that can fit the contents of the + /// [`Paragraph`]. + pub fn min_height(&self) -> f32 { + self.raw.min_height() + } + /// Returns the cached [`Paragraph`]. pub fn raw(&self) -> &P { &self.raw diff --git a/core/src/widget.rs b/core/src/widget.rs index 2a40f823..3c9c50ab 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/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 8f66e575..a1327bc1 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -61,6 +61,33 @@ pub fn focus<T>(target: Id) -> impl Operation<T> { Focus { target } } +/// Produces an [`Operation`] that unfocuses the focused widget. +pub fn unfocus<T>() -> impl Operation<T> { + struct Unfocus; + + impl<T> Operation<T> for Unfocus { + fn focusable( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + state: &mut dyn Focusable, + ) { + state.unfocus(); + } + + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self); + } + } + + Unfocus +} + /// Produces an [`Operation`] that generates a [`Count`] and chains it with the /// provided function to build a new [`Operation`]. pub fn count() -> impl Operation<Count> { 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)); } } |