diff options
author | 2024-07-18 22:39:49 +0200 | |
---|---|---|
committer | 2024-07-18 22:39:49 +0200 | |
commit | 23ad15391c88f562c90f4344d3949f76b6f9caf9 (patch) | |
tree | 883f5752e3cfe516ee22048015e9255b502bb04e /core | |
parent | 616689ca54942a13aac3615e571ae995ad4571b6 (diff) | |
parent | 06acb740fba1889c6a9fb48dfa3ae3aaac1df3ab (diff) | |
download | iced-23ad15391c88f562c90f4344d3949f76b6f9caf9.tar.gz iced-23ad15391c88f562c90f4344d3949f76b6f9caf9.tar.bz2 iced-23ad15391c88f562c90f4344d3949f76b6f9caf9.zip |
Merge pull request #2508 from iced-rs/feature/rich-text
`rich_text` and `markdown` widgets
Diffstat (limited to 'core')
-rw-r--r-- | core/src/renderer/null.rs | 7 | ||||
-rw-r--r-- | core/src/text.rs | 163 | ||||
-rw-r--r-- | core/src/text/paragraph.rs | 91 | ||||
-rw-r--r-- | core/src/widget/text.rs | 98 |
4 files changed, 255 insertions, 104 deletions
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index e8709dbc..f9d1a5b0 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -77,9 +77,14 @@ impl text::Paragraph for () { fn with_text(_text: Text<&str>) -> Self {} + fn with_spans( + _text: Text<&[text::Span<'_, Self::Font>], Self::Font>, + ) -> Self { + } + fn resize(&mut self, _new_bounds: Size) {} - fn compare(&self, _text: Text<&str>) -> text::Difference { + fn compare(&self, _text: Text<()>) -> text::Difference { text::Difference::None } diff --git a/core/src/text.rs b/core/src/text.rs index b30feae0..22cfce13 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -1,8 +1,7 @@ //! Draw and interact with text. -mod paragraph; - pub mod editor; pub mod highlighter; +pub mod paragraph; pub use editor::Editor; pub use highlighter::Highlighter; @@ -11,6 +10,7 @@ pub use paragraph::Paragraph; use crate::alignment; use crate::{Color, Pixels, Point, Rectangle, Size}; +use std::borrow::Cow; use std::hash::{Hash, Hasher}; /// A paragraph. @@ -221,3 +221,162 @@ pub trait Renderer: crate::Renderer { clip_bounds: Rectangle, ); } + +/// A span of text. +#[derive(Debug, Clone, PartialEq)] +pub struct Span<'a, Font = crate::Font> { + /// The [`Fragment`] of text. + pub text: Fragment<'a>, + /// The size of the [`Span`] in [`Pixels`]. + pub size: Option<Pixels>, + /// The [`LineHeight`] of the [`Span`]. + pub line_height: Option<LineHeight>, + /// The font of the [`Span`]. + pub font: Option<Font>, + /// The [`Color`] of the [`Span`]. + pub color: Option<Color>, +} + +impl<'a, Font> Span<'a, Font> { + /// Creates a new [`Span`] of text with the given text fragment. + pub fn new(fragment: impl IntoFragment<'a>) -> Self { + Self { + text: fragment.into_fragment(), + size: None, + line_height: None, + font: None, + color: None, + } + } + + /// Sets the size of the [`Span`]. + pub fn size(mut self, size: impl Into<Pixels>) -> Self { + self.size = Some(size.into()); + self + } + + /// Sets the [`LineHeight`] of the [`Span`]. + pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self { + self.line_height = Some(line_height.into()); + self + } + + /// Sets the font of the [`Span`]. + pub fn font(mut self, font: impl Into<Font>) -> Self { + self.font = Some(font.into()); + self + } + + /// Sets the font of the [`Span`], if any. + pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self { + self.font = font.map(Into::into); + self + } + + /// Sets the [`Color`] of the [`Span`]. + pub fn color(mut self, color: impl Into<Color>) -> Self { + self.color = Some(color.into()); + self + } + + /// Sets the [`Color`] of the [`Span`], if any. + pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self { + self.color = color.map(Into::into); + self + } + + /// Turns the [`Span`] into a static one. + pub fn to_static(self) -> Span<'static, Font> { + Span { + text: Cow::Owned(self.text.into_owned()), + size: self.size, + line_height: self.line_height, + font: self.font, + color: self.color, + } + } +} + +impl<'a, Font> From<&'a str> for Span<'a, Font> { + fn from(value: &'a str) -> Self { + Span::new(value) + } +} + +/// A fragment of [`Text`]. +/// +/// This is just an alias to a string that may be either +/// borrowed or owned. +pub type Fragment<'a> = Cow<'a, str>; + +/// 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> IntoFragment<'a> for Fragment<'a> { + fn into_fragment(self) -> Fragment<'a> { + self + } +} + +impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self) + } +} + +impl<'a> IntoFragment<'a> for &'a str { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self) + } +} + +impl<'a> IntoFragment<'a> for &'a String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Borrowed(self.as_str()) + } +} + +impl<'a> IntoFragment<'a> for String { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self) + } +} + +macro_rules! into_fragment { + ($type:ty) => { + impl<'a> IntoFragment<'a> for $type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) + } + } + + impl<'a> IntoFragment<'a> for &$type { + fn into_fragment(self) -> Fragment<'a> { + Fragment::Owned(self.to_string()) + } + } + }; +} + +into_fragment!(char); +into_fragment!(bool); + +into_fragment!(u8); +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); diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 8ff04015..4ee83798 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -1,5 +1,6 @@ +//! Draw paragraphs. use crate::alignment; -use crate::text::{Difference, Hit, Text}; +use crate::text::{Difference, Hit, Span, Text}; use crate::{Point, Size}; /// A text paragraph. @@ -10,12 +11,15 @@ pub trait Paragraph: Sized + Default { /// Creates a new [`Paragraph`] laid out with the given [`Text`]. fn with_text(text: Text<&str, Self::Font>) -> Self; + /// Creates a new [`Paragraph`] laid out with the given [`Text`]. + fn with_spans(text: Text<&[Span<'_, Self::Font>], Self::Font>) -> Self; + /// Lays out the [`Paragraph`] with some new boundaries. fn resize(&mut self, new_bounds: Size); /// Compares the [`Paragraph`] with some desired [`Text`] and returns the /// [`Difference`]. - fn compare(&self, text: Text<&str, Self::Font>) -> Difference; + fn compare(&self, text: Text<(), Self::Font>) -> Difference; /// Returns the horizontal alignment of the [`Paragraph`]. fn horizontal_alignment(&self) -> alignment::Horizontal; @@ -34,26 +38,87 @@ pub trait Paragraph: Sized + Default { /// Returns the distance to the given grapheme index in the [`Paragraph`]. fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>; - /// Updates the [`Paragraph`] to match the given [`Text`], if needed. - fn update(&mut self, text: Text<&str, Self::Font>) { - match self.compare(text) { + /// Returns the minimum width that can fit the contents of the [`Paragraph`]. + fn min_width(&self) -> f32 { + self.min_bounds().width + } + + /// Returns the minimum height that can fit the contents of the [`Paragraph`]. + fn min_height(&self) -> f32 { + self.min_bounds().height + } +} + +/// A [`Paragraph`] of plain text. +#[derive(Debug, Clone, Default)] +pub struct Plain<P: Paragraph> { + raw: P, + content: String, +} + +impl<P: Paragraph> Plain<P> { + /// Creates a new [`Plain`] paragraph. + pub fn new(text: Text<&str, P::Font>) -> Self { + let content = text.content.to_owned(); + + Self { + raw: P::with_text(text), + content, + } + } + + /// Updates the plain [`Paragraph`] to match the given [`Text`], if needed. + pub fn update(&mut self, text: Text<&str, P::Font>) { + if self.content != text.content { + text.content.clone_into(&mut self.content); + self.raw = P::with_text(text); + return; + } + + match self.raw.compare(Text { + content: (), + bounds: text.bounds, + size: text.size, + line_height: text.line_height, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + }) { Difference::None => {} Difference::Bounds => { - self.resize(text.bounds); + self.raw.resize(text.bounds); } Difference::Shape => { - *self = Self::with_text(text); + self.raw = P::with_text(text); } } } - /// Returns the minimum width that can fit the contents of the [`Paragraph`]. - fn min_width(&self) -> f32 { - self.min_bounds().width + /// Returns the horizontal alignment of the [`Paragraph`]. + pub fn horizontal_alignment(&self) -> alignment::Horizontal { + self.raw.horizontal_alignment() } - /// Returns the minimum height that can fit the contents of the [`Paragraph`]. - fn min_height(&self) -> f32 { - self.min_bounds().height + /// Returns the vertical alignment of the [`Paragraph`]. + pub fn vertical_alignment(&self) -> alignment::Vertical { + self.raw.vertical_alignment() + } + + /// Returns the minimum boundaries that can fit the contents of the + /// [`Paragraph`]. + pub fn min_bounds(&self) -> Size { + self.raw.min_bounds() + } + + /// Returns the minimum width that can fit the contents of the + /// [`Paragraph`]. + pub fn min_width(&self) -> f32 { + self.raw.min_width() + } + + /// Returns the cached [`Paragraph`]. + pub fn raw(&self) -> &P { + &self.raw } } diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 91c9893d..d0ecd27b 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -3,15 +3,14 @@ use crate::alignment; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text::{self, Paragraph}; +use crate::text; +use crate::text::paragraph::{self, Paragraph}; use crate::widget::tree::{self, Tree}; use crate::{ Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme, Widget, }; -use std::borrow::Cow; - pub use text::{LineHeight, Shaping}; /// A paragraph of text. @@ -21,7 +20,7 @@ where Theme: Catalog, Renderer: text::Renderer, { - fragment: Fragment<'a>, + fragment: text::Fragment<'a>, size: Option<Pixels>, line_height: LineHeight, width: Length, @@ -39,7 +38,7 @@ where Renderer: text::Renderer, { /// Create a new fragment of [`Text`] with the given contents. - pub fn new(fragment: impl IntoFragment<'a>) -> Self { + pub fn new(fragment: impl text::IntoFragment<'a>) -> Self { Text { fragment: fragment.into_fragment(), size: None, @@ -155,7 +154,7 @@ where /// The internal state of a [`Text`] widget. #[derive(Debug, Default)] -pub struct State<P: Paragraph>(P); +pub struct State<P: Paragraph>(paragraph::Plain<P>); impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'a, Theme, Renderer> @@ -168,7 +167,9 @@ where } fn state(&self) -> tree::State { - tree::State::new(State(Renderer::Paragraph::default())) + tree::State::new(State::<Renderer::Paragraph>( + paragraph::Plain::default(), + )) } fn size(&self) -> Size<Length> { @@ -213,7 +214,7 @@ where let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); let style = theme.style(&self.class); - draw(renderer, defaults, layout, state, style, viewport); + draw(renderer, defaults, layout, state.0.raw(), style, viewport); } } @@ -272,13 +273,12 @@ pub fn draw<Renderer>( renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, - state: &State<Renderer::Paragraph>, + paragraph: &Renderer::Paragraph, appearance: Style, viewport: &Rectangle, ) where Renderer: text::Renderer, { - let State(ref paragraph) = state; let bounds = layout.bounds(); let x = match paragraph.horizontal_alignment() { @@ -412,81 +412,3 @@ pub fn danger(theme: &Theme) -> Style { color: Some(theme.palette().danger), } } - -/// A fragment of [`Text`]. -/// -/// This is just an alias to a string that may be either -/// borrowed or owned. -pub type Fragment<'a> = Cow<'a, str>; - -/// 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> IntoFragment<'a> for Fragment<'a> { - fn into_fragment(self) -> Fragment<'a> { - self - } -} - -impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Borrowed(self) - } -} - -impl<'a> IntoFragment<'a> for &'a str { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Borrowed(self) - } -} - -impl<'a> IntoFragment<'a> for &'a String { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Borrowed(self.as_str()) - } -} - -impl<'a> IntoFragment<'a> for String { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Owned(self) - } -} - -macro_rules! into_fragment { - ($type:ty) => { - impl<'a> IntoFragment<'a> for $type { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Owned(self.to_string()) - } - } - - impl<'a> IntoFragment<'a> for &$type { - fn into_fragment(self) -> Fragment<'a> { - Fragment::Owned(self.to_string()) - } - } - }; -} - -into_fragment!(char); -into_fragment!(bool); - -into_fragment!(u8); -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); |