summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/animation.rs12
-rw-r--r--core/src/element.rs9
-rw-r--r--core/src/event.rs4
-rw-r--r--core/src/input_method.rs221
-rw-r--r--core/src/length.rs6
-rw-r--r--core/src/lib.rs68
-rw-r--r--core/src/mouse/cursor.rs39
-rw-r--r--core/src/overlay.rs2
-rw-r--r--core/src/overlay/element.rs4
-rw-r--r--core/src/overlay/group.rs11
-rw-r--r--core/src/padding.rs6
-rw-r--r--core/src/pixels.rs14
-rw-r--r--core/src/shell.rs76
-rw-r--r--core/src/text.rs27
-rw-r--r--core/src/text/paragraph.rs6
-rw-r--r--core/src/widget.rs2
-rw-r--r--core/src/widget/operation/focusable.rs27
-rw-r--r--core/src/window/redraw_request.rs12
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));
}
}