From 1d83e59e8ab51d403baee0daa878644c6a37be3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 1 Apr 2024 21:36:08 +0200 Subject: [PATCH 1/5] Specialize `widget::text` helper with custom `IntoContent` trait --- core/src/widget/text.rs | 70 ++++++++++++++++++++++++++++++++-- examples/lazy/src/main.rs | 2 +- examples/tour/src/main.rs | 6 +-- examples/websocket/src/main.rs | 6 ++- widget/src/helpers.rs | 4 +- 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 12f6956a39..e05eb0d98a 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -21,7 +21,7 @@ where Theme: Catalog, Renderer: text::Renderer, { - content: Cow<'a, str>, + content: Content<'a>, size: Option, line_height: LineHeight, width: Length, @@ -39,9 +39,9 @@ where Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new(content: impl Into>) -> Self { + pub fn new(content: impl IntoContent<'a>) -> Self { Text { - content: content.into(), + content: content.into_content(), size: None, line_height: LineHeight::default(), font: None, @@ -366,3 +366,67 @@ impl Catalog for Theme { class(self) } } + +/// The content of a [`Text`] widget. +/// +/// This is just an alias to a string that may be either +/// borrowed or owned. +pub type Content<'a> = Cow<'a, str>; + +/// A trait for converting a value to some text [`Content`]. +pub trait IntoContent<'a> { + /// Converts the value to some text [`Content`]. + fn into_content(self) -> Content<'a>; +} + +impl<'a> IntoContent<'a> for &'a str { + fn into_content(self) -> Content<'a> { + Content::Borrowed(self) + } +} + +impl<'a> IntoContent<'a> for &'a String { + fn into_content(self) -> Content<'a> { + Content::Borrowed(self.as_str()) + } +} + +impl<'a> IntoContent<'a> for String { + fn into_content(self) -> Content<'a> { + Content::Owned(self) + } +} + +macro_rules! into_content { + ($type:ty) => { + impl<'a> IntoContent<'a> for $type { + fn into_content(self) -> Content<'a> { + Content::Owned(self.to_string()) + } + } + + impl<'a> IntoContent<'a> for &$type { + fn into_content(self) -> Content<'a> { + Content::Owned(self.to_string()) + } + } + }; +} + +into_content!(char); +into_content!(bool); + +into_content!(u8); +into_content!(u16); +into_content!(u32); +into_content!(u64); +into_content!(u128); + +into_content!(i8); +into_content!(i16); +into_content!(i32); +into_content!(i64); +into_content!(i128); + +into_content!(f32); +into_content!(f64); diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 2d53df9335..c3f6b8de68 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -173,7 +173,7 @@ impl App { .style(button::danger); row![ - text(&item.name).color(item.color), + text(item.name.clone()).color(item.color), horizontal_space(), pick_list(Color::ALL, Some(item.color), move |color| { Message::ItemColorChanged(item.clone(), color) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index a88c0dba0f..e3a2ca8796 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -357,7 +357,7 @@ impl<'a> Step { .into() } - fn container(title: &str) -> Column<'a, StepMessage> { + fn container(title: &str) -> Column<'_, StepMessage> { column![text(title).size(50)].spacing(20) } @@ -589,7 +589,7 @@ impl<'a> Step { value: &str, is_secure: bool, is_showing_icon: bool, - ) -> Column<'a, StepMessage> { + ) -> Column<'_, StepMessage> { let mut text_input = text_input("Type something to continue...", value) .on_input(StepMessage::InputChanged) .padding(10) @@ -674,7 +674,7 @@ fn ferris<'a>( .center_x() } -fn padded_button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { +fn padded_button(label: &str) -> Button<'_, Message> { button(text(label)).padding([12, 24]) } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 460d9a0839..ef4505243a 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -97,7 +97,11 @@ impl WebSocket { } else { scrollable( column( - self.messages.iter().cloned().map(text).map(Element::from), + self.messages + .iter() + .map(ToString::to_string) + .map(text) + .map(Element::from), ) .spacing(10), ) diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 77b308821b..deff026106 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -145,13 +145,13 @@ where /// /// [`Text`]: core::widget::Text pub fn text<'a, Theme, Renderer>( - text: impl ToString, + text: impl text::IntoContent<'a>, ) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: core::text::Renderer, { - Text::new(text.to_string()) + Text::new(text) } /// Creates a new [`Checkbox`]. From b8d5df28172a9f50c8b48123df05a336801f2bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 1 Apr 2024 21:36:52 +0200 Subject: [PATCH 2/5] Reintroduce old `text` helper as `value` helper --- examples/websocket/src/main.rs | 12 +++--------- widget/src/helpers.rs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ef4505243a..369ae820a5 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -2,7 +2,7 @@ mod echo; use iced::alignment::{self, Alignment}; use iced::widget::{ - button, column, container, row, scrollable, text, text_input, + button, column, container, row, scrollable, text, text_input, value, }; use iced::{color, Command, Element, Length, Subscription}; use once_cell::sync::Lazy; @@ -96,14 +96,8 @@ impl WebSocket { .into() } else { scrollable( - column( - self.messages - .iter() - .map(ToString::to_string) - .map(text) - .map(Element::from), - ) - .spacing(10), + column(self.messages.iter().map(value).map(Element::from)) + .spacing(10), ) .id(MESSAGE_LOG.clone()) .height(Length::Fill) diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index deff026106..5bfb590fa2 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -154,6 +154,19 @@ where Text::new(text) } +/// Creates a new [`Text`] widget that displays the provided value. +/// +/// [`Text`]: core::widget::Text +pub fn value<'a, Theme, Renderer>( + value: impl ToString, +) -> Text<'a, Theme, Renderer> +where + Theme: text::Catalog + 'a, + Renderer: core::text::Renderer, +{ + Text::new(value.to_string()) +} + /// Creates a new [`Checkbox`]. /// /// [`Checkbox`]: crate::Checkbox From 34f799aa3dbb58c49708c1f1d9ee436922db636d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 1 Apr 2024 21:47:55 +0200 Subject: [PATCH 3/5] Rename `text::IntoContent` to `IntoFragment` --- core/src/widget/text.rs | 80 ++++++++++++++++++++--------------------- widget/src/helpers.rs | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index e05eb0d98a..123c6a69d0 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -21,7 +21,7 @@ where Theme: Catalog, Renderer: text::Renderer, { - content: Content<'a>, + fragment: Fragment<'a>, size: Option, line_height: LineHeight, width: Length, @@ -39,9 +39,9 @@ where Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new(content: impl IntoContent<'a>) -> Self { + pub fn new(fragment: impl IntoFragment<'a>) -> Self { Text { - content: content.into_content(), + fragment: fragment.into_fragment(), size: None, line_height: LineHeight::default(), font: None, @@ -184,7 +184,7 @@ where limits, self.width, self.height, - &self.content, + &self.fragment, self.line_height, self.size, self.font, @@ -367,66 +367,66 @@ impl Catalog for Theme { } } -/// The content of a [`Text`] widget. +/// A fragment of [`Text`]. /// /// This is just an alias to a string that may be either /// borrowed or owned. -pub type Content<'a> = Cow<'a, str>; +pub type Fragment<'a> = Cow<'a, str>; -/// A trait for converting a value to some text [`Content`]. -pub trait IntoContent<'a> { - /// Converts the value to some text [`Content`]. - fn into_content(self) -> Content<'a>; +/// A trait for converting a value to some text [`Fragment`]. +pub trait IntoFragment<'a> { + /// Converts the value to some text [`Fragment`]. + fn into_fragment(self) -> Fragment<'a>; } -impl<'a> IntoContent<'a> for &'a str { - fn into_content(self) -> Content<'a> { - Content::Borrowed(self) +impl<'a> IntoFragment<'a> for &'a str { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self) } } -impl<'a> IntoContent<'a> for &'a String { - fn into_content(self) -> Content<'a> { - Content::Borrowed(self.as_str()) +impl<'a> IntoFragment<'a> for &'a String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self.as_str()) } } -impl<'a> IntoContent<'a> for String { - fn into_content(self) -> Content<'a> { - Content::Owned(self) +impl<'a> IntoFragment<'a> for String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self) } } -macro_rules! into_content { +macro_rules! into_fragment { ($type:ty) => { - impl<'a> IntoContent<'a> for $type { - fn into_content(self) -> Content<'a> { - Content::Owned(self.to_string()) + impl<'a> IntoFragment<'a> for $type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) } } - impl<'a> IntoContent<'a> for &$type { - fn into_content(self) -> Content<'a> { - Content::Owned(self.to_string()) + impl<'a> IntoFragment<'a> for &$type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) } } }; } -into_content!(char); -into_content!(bool); +into_fragment!(char); +into_fragment!(bool); -into_content!(u8); -into_content!(u16); -into_content!(u32); -into_content!(u64); -into_content!(u128); +into_fragment!(u8); +into_fragment!(u16); +into_fragment!(u32); +into_fragment!(u64); +into_fragment!(u128); -into_content!(i8); -into_content!(i16); -into_content!(i32); -into_content!(i64); -into_content!(i128); +into_fragment!(i8); +into_fragment!(i16); +into_fragment!(i32); +into_fragment!(i64); +into_fragment!(i128); -into_content!(f32); -into_content!(f64); +into_fragment!(f32); +into_fragment!(f64); diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 5bfb590fa2..61789c193b 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -145,7 +145,7 @@ where /// /// [`Text`]: core::widget::Text pub fn text<'a, Theme, Renderer>( - text: impl text::IntoContent<'a>, + text: impl text::IntoFragment<'a>, ) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, From dee43d5f66c97dddffebbc02c4a87674fb2c13b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 2 Apr 2024 09:24:22 +0200 Subject: [PATCH 4/5] Implement `IntoFragment` for `usize` and `isize` --- core/src/widget/text.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 123c6a69d0..53591e4191 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -421,12 +421,14 @@ into_fragment!(u16); into_fragment!(u32); into_fragment!(u64); into_fragment!(u128); +into_fragment!(usize); into_fragment!(i8); into_fragment!(i16); into_fragment!(i32); into_fragment!(i64); into_fragment!(i128); +into_fragment!(isize); into_fragment!(f32); into_fragment!(f64); From 488cac714896002791f3c7b9a99181310c1d1b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 2 Apr 2024 09:29:16 +0200 Subject: [PATCH 5/5] Avoid extra text allocations in `websocket` example --- examples/websocket/src/echo.rs | 23 ++++++++++++++++------- examples/websocket/src/main.rs | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index 281ed4bdcd..cd32cb6605 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -2,6 +2,7 @@ pub mod server; use iced::futures; use iced::subscription::{self, Subscription}; +use iced::widget::text; use futures::channel::mpsc; use futures::sink::SinkExt; @@ -136,16 +137,24 @@ impl Message { pub fn disconnected() -> Self { Message::Disconnected } + + pub fn as_str(&self) -> &str { + match self { + Message::Connected => "Connected successfully!", + Message::Disconnected => "Connection lost... Retrying...", + Message::User(message) => message.as_str(), + } + } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Message::Connected => write!(f, "Connected successfully!"), - Message::Disconnected => { - write!(f, "Connection lost... Retrying...") - } - Message::User(message) => write!(f, "{message}"), - } + f.write_str(self.as_str()) + } +} + +impl<'a> text::IntoFragment<'a> for &'a Message { + fn into_fragment(self) -> text::Fragment<'a> { + text::Fragment::Borrowed(self.as_str()) } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 369ae820a5..b479fe8911 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -2,7 +2,7 @@ mod echo; use iced::alignment::{self, Alignment}; use iced::widget::{ - button, column, container, row, scrollable, text, text_input, value, + button, column, container, row, scrollable, text, text_input, }; use iced::{color, Command, Element, Length, Subscription}; use once_cell::sync::Lazy; @@ -96,7 +96,7 @@ impl WebSocket { .into() } else { scrollable( - column(self.messages.iter().map(value).map(Element::from)) + column(self.messages.iter().map(text).map(Element::from)) .spacing(10), ) .id(MESSAGE_LOG.clone())