Skip to content

Commit

Permalink
Merge pull request #2508 from iced-rs/feature/rich-text
Browse files Browse the repository at this point in the history
`rich_text` and `markdown` widgets
  • Loading branch information
hecrj authored Jul 18, 2024
2 parents 616689c + 06acb74 commit 23ad153
Show file tree
Hide file tree
Showing 22 changed files with 1,284 additions and 208 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ svg = ["iced_widget/svg"]
canvas = ["iced_widget/canvas"]
# Enables the `QRCode` widget
qr_code = ["iced_widget/qr_code"]
# Enables the `markdown` widget
markdown = ["iced_widget/markdown"]
# Enables lazy widgets
lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12)
Expand All @@ -51,7 +53,7 @@ web-colors = ["iced_renderer/web-colors"]
# Enables the WebGL backend, replacing WebGPU
webgl = ["iced_renderer/webgl"]
# Enables the syntax `highlighter` module
highlighter = ["iced_highlighter"]
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
# Enables the advanced module
Expand Down Expand Up @@ -155,6 +157,7 @@ num-traits = "0.2"
once_cell = "1.0"
ouroboros = "0.18"
palette = "0.7"
pulldown-cmark = "0.11"
qrcode = { version = "0.13", default-features = false }
raw-window-handle = "0.6"
resvg = "0.42"
Expand Down
7 changes: 6 additions & 1 deletion core/src/renderer/null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
163 changes: 161 additions & 2 deletions core/src/text.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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);
91 changes: 78 additions & 13 deletions core/src/text/paragraph.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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
}
}
Loading

0 comments on commit 23ad153

Please sign in to comment.