summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector@hecrj.dev>2024-07-18 22:39:49 +0200
committerLibravatar GitHub <noreply@github.com>2024-07-18 22:39:49 +0200
commit23ad15391c88f562c90f4344d3949f76b6f9caf9 (patch)
tree883f5752e3cfe516ee22048015e9255b502bb04e /core
parent616689ca54942a13aac3615e571ae995ad4571b6 (diff)
parent06acb740fba1889c6a9fb48dfa3ae3aaac1df3ab (diff)
downloadiced-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.rs7
-rw-r--r--core/src/text.rs163
-rw-r--r--core/src/text/paragraph.rs91
-rw-r--r--core/src/widget/text.rs98
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);