diff options
Diffstat (limited to '')
| -rw-r--r-- | widget/src/helpers.rs | 45 | ||||
| -rw-r--r-- | widget/src/lib.rs | 3 | ||||
| -rw-r--r-- | widget/src/markdown.rs | 246 | ||||
| -rw-r--r-- | widget/src/pick_list.rs | 9 | ||||
| -rw-r--r-- | widget/src/scrollable.rs | 18 | ||||
| -rw-r--r-- | widget/src/text.rs | 4 | ||||
| -rw-r--r-- | widget/src/text/rich.rs | 335 | ||||
| -rw-r--r-- | widget/src/text_editor.rs | 29 | ||||
| -rw-r--r-- | widget/src/text_input.rs | 32 | 
9 files changed, 692 insertions, 29 deletions
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 1f282f54..aa9394cb 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -24,7 +24,7 @@ use crate::tooltip::{self, Tooltip};  use crate::vertical_slider::{self, VerticalSlider};  use crate::{Column, MouseArea, Row, Space, Stack, Themer}; -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow};  use std::ops::RangeInclusive;  /// Creates a [`Column`] with the given children. @@ -112,6 +112,19 @@ macro_rules! text {      };  } +/// Creates some [`Rich`] text with the given spans. +/// +/// [`Rich`]: text::Rich +#[macro_export] +macro_rules! rich_text { +    () => ( +        $crate::Column::new() +    ); +    ($($x:expr),+ $(,)?) => ( +        $crate::text::Rich::with_spans([$($crate::text::Span::from($x)),+]) +    ); +} +  /// Creates a new [`Container`] with the provided content.  ///  /// [`Container`]: crate::Container @@ -646,8 +659,6 @@ where  }  /// Creates a new [`Text`] widget with the provided content. -/// -/// [`Text`]: core::widget::Text  pub fn text<'a, Theme, Renderer>(      text: impl text::IntoFragment<'a>,  ) -> Text<'a, Theme, Renderer> @@ -659,8 +670,6 @@ where  }  /// 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> @@ -671,6 +680,32 @@ where      Text::new(value.to_string())  } +/// Creates a new [`Rich`] text widget with the provided spans. +/// +/// [`Rich`]: text::Rich +pub fn rich_text<'a, Theme, Renderer>( +    spans: impl Into<Cow<'a, [text::Span<'a, Renderer::Font>]>>, +) -> text::Rich<'a, Theme, Renderer> +where +    Theme: text::Catalog + 'a, +    Renderer: core::text::Renderer, +{ +    text::Rich::with_spans(spans) +} + +/// Creates a new [`Span`] of text with the provided content. +/// +/// [`Span`]: text::Span +pub fn span<'a, Font>( +    text: impl text::IntoFragment<'a>, +) -> text::Span<'a, Font> { +    text::Span::new(text) +} + +#[cfg(feature = "markdown")] +#[doc(inline)] +pub use crate::markdown::view as markdown; +  /// Creates a new [`Checkbox`].  ///  /// [`Checkbox`]: crate::Checkbox diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 00e9aaa4..115a29e5 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -130,5 +130,8 @@ pub mod qr_code;  #[doc(no_inline)]  pub use qr_code::QRCode; +#[cfg(feature = "markdown")] +pub mod markdown; +  pub use crate::core::theme::{self, Theme};  pub use renderer::Renderer; diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs new file mode 100644 index 00000000..bbb5b463 --- /dev/null +++ b/widget/src/markdown.rs @@ -0,0 +1,246 @@ +//! Parse and display Markdown. +//! +//! You can enable the `highlighter` feature for syntax highligting +//! in code blocks. +//! +//! Only the variants of [`Item`] are currently supported. +use crate::core::font::{self, Font}; +use crate::core::padding; +use crate::core::theme::{self, Theme}; +use crate::core::{self, Element, Length}; +use crate::{column, container, rich_text, row, span, text}; + +/// A Markdown item. +#[derive(Debug, Clone)] +pub enum Item { +    /// A heading. +    Heading(Vec<text::Span<'static>>), +    /// A paragraph. +    Paragraph(Vec<text::Span<'static>>), +    /// A code block. +    /// +    /// You can enable the `highlighter` feature for syntax highligting. +    CodeBlock(Vec<text::Span<'static>>), +    /// A list. +    List { +        /// The first number of the list, if it is ordered. +        start: Option<u64>, +        /// The items of the list. +        items: Vec<Vec<text::Span<'static>>>, +    }, +} + +/// Parse the given Markdown content. +pub fn parse( +    markdown: &str, +    palette: theme::Palette, +) -> impl Iterator<Item = Item> + '_ { +    let mut spans = Vec::new(); +    let mut heading = None; +    let mut strong = false; +    let mut emphasis = false; +    let mut link = false; +    let mut list = Vec::new(); +    let mut list_start = None; + +    #[cfg(feature = "highlighter")] +    let mut highlighter = None; + +    let parser = pulldown_cmark::Parser::new(markdown); + +    // We want to keep the `spans` capacity +    #[allow(clippy::drain_collect)] +    parser.filter_map(move |event| match event { +        pulldown_cmark::Event::Start(tag) => match tag { +            pulldown_cmark::Tag::Heading { level, .. } => { +                heading = Some(level); +                None +            } +            pulldown_cmark::Tag::Strong => { +                strong = true; +                None +            } +            pulldown_cmark::Tag::Emphasis => { +                emphasis = true; +                None +            } +            pulldown_cmark::Tag::Link { .. } => { +                link = true; +                None +            } +            pulldown_cmark::Tag::List(first_item) => { +                list_start = first_item; +                None +            } +            pulldown_cmark::Tag::CodeBlock( +                pulldown_cmark::CodeBlockKind::Fenced(_language), +            ) => { +                #[cfg(feature = "highlighter")] +                { +                    use iced_highlighter::{self, Highlighter}; +                    use text::Highlighter as _; + +                    highlighter = +                        Some(Highlighter::new(&iced_highlighter::Settings { +                            theme: iced_highlighter::Theme::Base16Ocean, +                            token: _language.to_string(), +                        })); +                } + +                None +            } +            _ => None, +        }, +        pulldown_cmark::Event::End(tag) => match tag { +            pulldown_cmark::TagEnd::Heading(_) => { +                heading = None; +                Some(Item::Heading(spans.drain(..).collect())) +            } +            pulldown_cmark::TagEnd::Emphasis => { +                emphasis = false; +                None +            } +            pulldown_cmark::TagEnd::Strong => { +                strong = false; +                None +            } +            pulldown_cmark::TagEnd::Link => { +                link = false; +                None +            } +            pulldown_cmark::TagEnd::Paragraph => { +                Some(Item::Paragraph(spans.drain(..).collect())) +            } +            pulldown_cmark::TagEnd::List(_) => Some(Item::List { +                start: list_start, +                items: list.drain(..).collect(), +            }), +            pulldown_cmark::TagEnd::Item => { +                list.push(spans.drain(..).collect()); +                None +            } +            pulldown_cmark::TagEnd::CodeBlock => { +                #[cfg(feature = "highlighter")] +                { +                    highlighter = None; +                } + +                Some(Item::CodeBlock(spans.drain(..).collect())) +            } +            _ => None, +        }, +        pulldown_cmark::Event::Text(text) => { +            #[cfg(feature = "highlighter")] +            if let Some(highlighter) = &mut highlighter { +                use text::Highlighter as _; + +                for (range, highlight) in +                    highlighter.highlight_line(text.as_ref()) +                { +                    let span = span(text[range].to_owned()) +                        .color_maybe(highlight.color()) +                        .font_maybe(highlight.font()); + +                    spans.push(span); +                } + +                return None; +            } + +            let span = span(text.into_string()); + +            let span = match heading { +                None => span, +                Some(heading) => span.size(match heading { +                    pulldown_cmark::HeadingLevel::H1 => 32, +                    pulldown_cmark::HeadingLevel::H2 => 28, +                    pulldown_cmark::HeadingLevel::H3 => 24, +                    pulldown_cmark::HeadingLevel::H4 => 20, +                    pulldown_cmark::HeadingLevel::H5 => 16, +                    pulldown_cmark::HeadingLevel::H6 => 16, +                }), +            }; + +            let span = if strong || emphasis { +                span.font(Font { +                    weight: if strong { +                        font::Weight::Bold +                    } else { +                        font::Weight::Normal +                    }, +                    style: if emphasis { +                        font::Style::Italic +                    } else { +                        font::Style::Normal +                    }, +                    ..Font::default() +                }) +            } else { +                span +            }; + +            let span = span.color_maybe(link.then_some(palette.primary)); + +            spans.push(span); + +            None +        } +        pulldown_cmark::Event::Code(code) => { +            spans.push(span(code.into_string()).font(Font::MONOSPACE)); +            None +        } +        pulldown_cmark::Event::SoftBreak => { +            spans.push(span(" ")); +            None +        } +        pulldown_cmark::Event::HardBreak => { +            spans.push(span("\n")); +            None +        } +        _ => None, +    }) +} + +/// Display a bunch of Markdown items. +/// +/// You can obtain the items with [`parse`]. +pub fn view<'a, Message, Renderer>( +    items: impl IntoIterator<Item = &'a Item>, +) -> Element<'a, Message, Theme, Renderer> +where +    Message: 'a, +    Renderer: core::text::Renderer<Font = Font> + 'a, +{ +    let blocks = items.into_iter().enumerate().map(|(i, item)| match item { +        Item::Heading(heading) => container(rich_text(heading)) +            .padding(padding::top(if i > 0 { 8 } else { 0 })) +            .into(), +        Item::Paragraph(paragraph) => rich_text(paragraph).into(), +        Item::List { start: None, items } => column( +            items +                .iter() +                .map(|item| row!["•", rich_text(item)].spacing(10).into()), +        ) +        .spacing(10) +        .into(), +        Item::List { +            start: Some(start), +            items, +        } => column(items.iter().enumerate().map(|(i, item)| { +            row![text!("{}.", i as u64 + *start), rich_text(item)] +                .spacing(10) +                .into() +        })) +        .spacing(10) +        .into(), +        Item::CodeBlock(code) => { +            container(rich_text(code).font(Font::MONOSPACE).size(12)) +                .width(Length::Fill) +                .padding(10) +                .style(container::rounded_box) +                .into() +        } +    }); + +    Element::new(column(blocks).width(Length::Fill).spacing(16)) +} diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 97de5b48..f7f7b65b 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -6,7 +6,8 @@ use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer; -use crate::core::text::{self, Paragraph as _, Text}; +use crate::core::text::paragraph; +use crate::core::text::{self, Text};  use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ @@ -622,8 +623,8 @@ struct State<P: text::Paragraph> {      keyboard_modifiers: keyboard::Modifiers,      is_open: bool,      hovered_option: Option<usize>, -    options: Vec<P>, -    placeholder: P, +    options: Vec<paragraph::Plain<P>>, +    placeholder: paragraph::Plain<P>,  }  impl<P: text::Paragraph> State<P> { @@ -635,7 +636,7 @@ impl<P: text::Paragraph> State<P> {              is_open: bool::default(),              hovered_option: Option::default(),              options: Vec::new(), -            placeholder: P::default(), +            placeholder: paragraph::Plain::default(),          }      }  } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index b1082203..6dd593cb 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -62,19 +62,27 @@ where          .validate()      } -    fn validate(self) -> Self { +    fn validate(mut self) -> Self { +        let size_hint = self.content.as_widget().size_hint(); +          debug_assert!( -            self.direction.vertical().is_none() -                || !self.content.as_widget().size_hint().height.is_fill(), +            self.direction.vertical().is_none() || !size_hint.height.is_fill(),              "scrollable content must not fill its vertical scrolling axis"          );          debug_assert!( -            self.direction.horizontal().is_none() -                || !self.content.as_widget().size_hint().width.is_fill(), +            self.direction.horizontal().is_none() || !size_hint.width.is_fill(),              "scrollable content must not fill its horizontal scrolling axis"          ); +        if self.direction.horizontal().is_none() { +            self.width = self.width.enclose(size_hint.width); +        } + +        if self.direction.vertical().is_none() { +            self.height = self.height.enclose(size_hint.height); +        } +          self      } diff --git a/widget/src/text.rs b/widget/src/text.rs index 0d689295..9bf7fce4 100644 --- a/widget/src/text.rs +++ b/widget/src/text.rs @@ -1,5 +1,9 @@  //! Draw and interact with text. +mod rich; + +pub use crate::core::text::{Fragment, Highlighter, IntoFragment, Span};  pub use crate::core::widget::text::*; +pub use rich::Rich;  /// A paragraph.  pub type Text<'a, Theme = crate::Theme, Renderer = crate::Renderer> = diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs new file mode 100644 index 00000000..5c44ed9e --- /dev/null +++ b/widget/src/text/rich.rs @@ -0,0 +1,335 @@ +use crate::core::alignment; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text::{Paragraph, Span}; +use crate::core::widget::text::{ +    self, Catalog, LineHeight, Shaping, Style, StyleFn, +}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ +    self, Color, Element, Length, Pixels, Rectangle, Size, Widget, +}; + +use std::borrow::Cow; + +/// A bunch of [`Rich`] text. +#[derive(Debug)] +pub struct Rich<'a, Theme = crate::Theme, Renderer = crate::Renderer> +where +    Theme: Catalog, +    Renderer: core::text::Renderer, +{ +    spans: Cow<'a, [Span<'a, Renderer::Font>]>, +    size: Option<Pixels>, +    line_height: LineHeight, +    width: Length, +    height: Length, +    font: Option<Renderer::Font>, +    align_x: alignment::Horizontal, +    align_y: alignment::Vertical, +    class: Theme::Class<'a>, +} + +impl<'a, Theme, Renderer> Rich<'a, Theme, Renderer> +where +    Theme: Catalog, +    Renderer: core::text::Renderer, +{ +    /// Creates a new empty [`Rich`] text. +    pub fn new() -> Self { +        Self { +            spans: Cow::default(), +            size: None, +            line_height: LineHeight::default(), +            width: Length::Shrink, +            height: Length::Shrink, +            font: None, +            align_x: alignment::Horizontal::Left, +            align_y: alignment::Vertical::Top, +            class: Theme::default(), +        } +    } + +    /// Creates a new [`Rich`] text with the given text spans. +    pub fn with_spans( +        spans: impl Into<Cow<'a, [Span<'a, Renderer::Font>]>>, +    ) -> Self { +        Self { +            spans: spans.into(), +            ..Self::new() +        } +    } + +    /// Sets the default size of the [`Rich`] text. +    pub fn size(mut self, size: impl Into<Pixels>) -> Self { +        self.size = Some(size.into()); +        self +    } + +    /// Sets the defualt [`LineHeight`] of the [`Rich`] text. +    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self { +        self.line_height = line_height.into(); +        self +    } + +    /// Sets the default font of the [`Rich`] text. +    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { +        self.font = Some(font.into()); +        self +    } + +    /// Sets the width of the [`Rich`] text boundaries. +    pub fn width(mut self, width: impl Into<Length>) -> Self { +        self.width = width.into(); +        self +    } + +    /// Sets the height of the [`Rich`] text boundaries. +    pub fn height(mut self, height: impl Into<Length>) -> Self { +        self.height = height.into(); +        self +    } + +    /// Centers the [`Rich`] text, both horizontally and vertically. +    pub fn center(self) -> Self { +        self.align_x(alignment::Horizontal::Center) +            .align_y(alignment::Vertical::Center) +    } + +    /// Sets the [`alignment::Horizontal`] of the [`Rich`] text. +    pub fn align_x( +        mut self, +        alignment: impl Into<alignment::Horizontal>, +    ) -> Self { +        self.align_x = alignment.into(); +        self +    } + +    /// Sets the [`alignment::Vertical`] of the [`Rich`] text. +    pub fn align_y( +        mut self, +        alignment: impl Into<alignment::Vertical>, +    ) -> Self { +        self.align_y = alignment.into(); +        self +    } + +    /// Sets the default style of the [`Rich`] text. +    #[must_use] +    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self +    where +        Theme::Class<'a>: From<StyleFn<'a, Theme>>, +    { +        self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); +        self +    } + +    /// Sets the default [`Color`] of the [`Rich`] text. +    pub fn color(self, color: impl Into<Color>) -> Self +    where +        Theme::Class<'a>: From<StyleFn<'a, Theme>>, +    { +        self.color_maybe(Some(color)) +    } + +    /// Sets the default [`Color`] of the [`Rich`] text, if `Some`. +    pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self +    where +        Theme::Class<'a>: From<StyleFn<'a, Theme>>, +    { +        let color = color.map(Into::into); + +        self.style(move |_theme| Style { color }) +    } + +    /// Sets the default style class of the [`Rich`] text. +    #[cfg(feature = "advanced")] +    #[must_use] +    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self { +        self.class = class.into(); +        self +    } + +    /// Adds a new text [`Span`] to the [`Rich`] text. +    pub fn push(mut self, span: impl Into<Span<'a, Renderer::Font>>) -> Self { +        self.spans.to_mut().push(span.into()); +        self +    } +} + +impl<'a, Theme, Renderer> Default for Rich<'a, Theme, Renderer> +where +    Theme: Catalog, +    Renderer: core::text::Renderer, +{ +    fn default() -> Self { +        Self::new() +    } +} + +struct State<P: Paragraph> { +    spans: Vec<Span<'static, P::Font>>, +    paragraph: P, +} + +impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> +    for Rich<'a, Theme, Renderer> +where +    Theme: Catalog, +    Renderer: core::text::Renderer, +{ +    fn tag(&self) -> tree::Tag { +        tree::Tag::of::<State<Renderer::Paragraph>>() +    } + +    fn state(&self) -> tree::State { +        tree::State::new(State { +            spans: Vec::new(), +            paragraph: Renderer::Paragraph::default(), +        }) +    } + +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        } +    } + +    fn layout( +        &self, +        tree: &mut Tree, +        renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        layout( +            tree.state.downcast_mut::<State<Renderer::Paragraph>>(), +            renderer, +            limits, +            self.width, +            self.height, +            self.spans.as_ref(), +            self.line_height, +            self.size, +            self.font, +            self.align_x, +            self.align_y, +        ) +    } + +    fn draw( +        &self, +        tree: &Tree, +        renderer: &mut Renderer, +        theme: &Theme, +        defaults: &renderer::Style, +        layout: Layout<'_>, +        _cursor_position: mouse::Cursor, +        viewport: &Rectangle, +    ) { +        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); +        let style = theme.style(&self.class); + +        text::draw( +            renderer, +            defaults, +            layout, +            &state.paragraph, +            style, +            viewport, +        ); +    } +} + +fn layout<Renderer>( +    state: &mut State<Renderer::Paragraph>, +    renderer: &Renderer, +    limits: &layout::Limits, +    width: Length, +    height: Length, +    spans: &[Span<'_, Renderer::Font>], +    line_height: LineHeight, +    size: Option<Pixels>, +    font: Option<Renderer::Font>, +    horizontal_alignment: alignment::Horizontal, +    vertical_alignment: alignment::Vertical, +) -> layout::Node +where +    Renderer: core::text::Renderer, +{ +    layout::sized(limits, width, height, |limits| { +        let bounds = limits.max(); + +        let size = size.unwrap_or_else(|| renderer.default_size()); +        let font = font.unwrap_or_else(|| renderer.default_font()); + +        let text_with_spans = || core::Text { +            content: spans, +            bounds, +            size, +            line_height, +            font, +            horizontal_alignment, +            vertical_alignment, +            shaping: Shaping::Advanced, +        }; + +        if state.spans != spans { +            state.paragraph = +                Renderer::Paragraph::with_spans(text_with_spans()); +            state.spans = spans.iter().cloned().map(Span::to_static).collect(); +        } else { +            match state.paragraph.compare(core::Text { +                content: (), +                bounds, +                size, +                line_height, +                font, +                horizontal_alignment, +                vertical_alignment, +                shaping: Shaping::Advanced, +            }) { +                core::text::Difference::None => {} +                core::text::Difference::Bounds => { +                    state.paragraph.resize(bounds); +                } +                core::text::Difference::Shape => { +                    state.paragraph = +                        Renderer::Paragraph::with_spans(text_with_spans()); +                } +            } +        } + +        state.paragraph.min_bounds() +    }) +} + +impl<'a, Theme, Renderer> FromIterator<Span<'a, Renderer::Font>> +    for Rich<'a, Theme, Renderer> +where +    Theme: Catalog, +    Renderer: core::text::Renderer, +{ +    fn from_iter<T: IntoIterator<Item = Span<'a, Renderer::Font>>>( +        spans: T, +    ) -> Self { +        Self { +            spans: spans.into_iter().collect(), +            ..Self::new() +        } +    } +} + +impl<'a, Message, Theme, Renderer> From<Rich<'a, Theme, Renderer>> +    for Element<'a, Message, Theme, Renderer> +where +    Theme: Catalog + 'a, +    Renderer: core::text::Renderer + 'a, +{ +    fn from( +        text: Rich<'a, Theme, Renderer>, +    ) -> Element<'a, Message, Theme, Renderer> { +        Element::new(text) +    } +} diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 0156b960..e494a3b0 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -9,6 +9,7 @@ use crate::core::renderer;  use crate::core::text::editor::{Cursor, Editor as _};  use crate::core::text::highlighter::{self, Highlighter};  use crate::core::text::{self, LineHeight}; +use crate::core::widget::operation;  use crate::core::widget::{self, Widget};  use crate::core::{      Background, Border, Color, Element, Length, Padding, Pixels, Rectangle, @@ -338,6 +339,22 @@ impl<Highlighter: text::Highlighter> State<Highlighter> {      }  } +impl<Highlighter: text::Highlighter> operation::Focusable +    for State<Highlighter> +{ +    fn is_focused(&self) -> bool { +        self.is_focused +    } + +    fn focus(&mut self) { +        self.is_focused = true; +    } + +    fn unfocus(&mut self) { +        self.is_focused = false; +    } +} +  impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for TextEditor<'a, Highlighter, Message, Theme, Renderer>  where @@ -640,6 +657,18 @@ where              mouse::Interaction::default()          }      } + +    fn operate( +        &self, +        tree: &mut widget::Tree, +        _layout: Layout<'_>, +        _renderer: &Renderer, +        operation: &mut dyn widget::Operation<()>, +    ) { +        let state = tree.state.downcast_mut::<State<Highlighter>>(); + +        operation.focusable(state, None); +    }  }  impl<'a, Highlighter, Message, Theme, Renderer> diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ba2fbc13..a0fe14a0 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -19,7 +19,8 @@ use crate::core::keyboard::key;  use crate::core::layout;  use crate::core::mouse::{self, click};  use crate::core::renderer; -use crate::core::text::{self, Paragraph as _, Text}; +use crate::core::text::paragraph; +use crate::core::text::{self, Text};  use crate::core::time::{Duration, Instant};  use crate::core::touch;  use crate::core::widget; @@ -360,7 +361,7 @@ where              let icon_layout = children_layout.next().unwrap();              renderer.fill_paragraph( -                &state.icon, +                state.icon.raw(),                  icon_layout.bounds().center(),                  style.icon,                  *viewport, @@ -378,7 +379,7 @@ where                  cursor::State::Index(position) => {                      let (text_value_width, offset) =                          measure_cursor_and_scroll_offset( -                            &state.value, +                            state.value.raw(),                              text_bounds,                              position,                          ); @@ -415,14 +416,14 @@ where                      let (left_position, left_offset) =                          measure_cursor_and_scroll_offset( -                            &state.value, +                            state.value.raw(),                              text_bounds,                              left,                          );                      let (right_position, right_offset) =                          measure_cursor_and_scroll_offset( -                            &state.value, +                            state.value.raw(),                              text_bounds,                              right,                          ); @@ -469,9 +470,9 @@ where              renderer.fill_paragraph(                  if text.is_empty() { -                    &state.placeholder +                    state.placeholder.raw()                  } else { -                    &state.value +                    state.value.raw()                  },                  Point::new(text_bounds.x, text_bounds.center_y())                      - Vector::new(offset, 0.0), @@ -1178,9 +1179,9 @@ pub fn select_all<T>(id: Id) -> Task<T> {  /// The state of a [`TextInput`].  #[derive(Debug, Default, Clone)]  pub struct State<P: text::Paragraph> { -    value: P, -    placeholder: P, -    icon: P, +    value: paragraph::Plain<P>, +    placeholder: paragraph::Plain<P>, +    icon: paragraph::Plain<P>,      is_focused: Option<Focus>,      is_dragging: bool,      is_pasting: Option<Value>, @@ -1212,9 +1213,9 @@ impl<P: text::Paragraph> State<P> {      /// Creates a new [`State`], representing a focused [`TextInput`].      pub fn focused() -> Self {          Self { -            value: P::default(), -            placeholder: P::default(), -            icon: P::default(), +            value: paragraph::Plain::default(), +            placeholder: paragraph::Plain::default(), +            icon: paragraph::Plain::default(),              is_focused: None,              is_dragging: false,              is_pasting: None, @@ -1319,7 +1320,7 @@ fn offset<P: text::Paragraph>(          };          let (_, offset) = measure_cursor_and_scroll_offset( -            &state.value, +            state.value.raw(),              text_bounds,              focus_position,          ); @@ -1357,6 +1358,7 @@ fn find_cursor_position<P: text::Paragraph>(      let char_offset = state          .value +        .raw()          .hit_test(Point::new(x + offset, text_bounds.height / 2.0))          .map(text::Hit::cursor)?; @@ -1386,7 +1388,7 @@ fn replace_paragraph<Renderer>(      let mut children_layout = layout.children();      let text_bounds = children_layout.next().unwrap().bounds(); -    state.value = Renderer::Paragraph::with_text(Text { +    state.value = paragraph::Plain::new(Text {          font,          line_height,          content: &value.to_string(),  | 
