diff options
| author | 2023-09-10 01:14:39 +0200 | |
|---|---|---|
| committer | 2023-09-10 01:14:39 +0200 | |
| commit | 1af5ff41abdef243588199ef7988666655924a02 (patch) | |
| tree | 4dfd518dd93b0393e77355867062c60d75e7f358 /core/src/widget | |
| parent | a3489e4af960388e9f73988b88df361022a654a4 (diff) | |
| parent | 1cc5bf59d7c4f47ae47d9a4e22ebaab3ea4975c1 (diff) | |
| download | iced-1af5ff41abdef243588199ef7988666655924a02.tar.gz iced-1af5ff41abdef243588199ef7988666655924a02.tar.bz2 iced-1af5ff41abdef243588199ef7988666655924a02.zip | |
Merge pull request #2058 from iced-rs/explicit-text-caching
Explicit text caching
Diffstat (limited to 'core/src/widget')
| -rw-r--r-- | core/src/widget/text.rs | 135 | ||||
| -rw-r--r-- | core/src/widget/tree.rs | 82 | 
2 files changed, 169 insertions, 48 deletions
| diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 79df2b02..53ed463e 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -3,9 +3,9 @@ use crate::alignment;  use crate::layout;  use crate::mouse;  use crate::renderer; -use crate::text; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget}; +use crate::text::{self, Paragraph}; +use crate::widget::tree::{self, Tree}; +use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};  use std::borrow::Cow; @@ -19,7 +19,7 @@ where      Renderer::Theme: StyleSheet,  {      content: Cow<'a, str>, -    size: Option<f32>, +    size: Option<Pixels>,      line_height: LineHeight,      width: Length,      height: Length, @@ -53,7 +53,7 @@ where      /// Sets the size of the [`Text`].      pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = Some(size.into().0); +        self.size = Some(size.into());          self      } @@ -117,11 +117,23 @@ where      }  } +/// The internal state of a [`Text`] widget. +#[derive(Debug, Default)] +pub struct State<P: Paragraph>(P); +  impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>  where      Renderer: text::Renderer,      Renderer::Theme: StyleSheet,  { +    fn tag(&self) -> tree::Tag { +        tree::Tag::of::<State<Renderer::Paragraph>>() +    } + +    fn state(&self) -> tree::State { +        tree::State::new(State(Renderer::Paragraph::default())) +    } +      fn width(&self) -> Length {          self.width      } @@ -132,30 +144,29 @@ where      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        let size = self.size.unwrap_or_else(|| renderer.default_size()); - -        let bounds = renderer.measure( +        layout( +            tree.state.downcast_mut::<State<Renderer::Paragraph>>(), +            renderer, +            limits, +            self.width, +            self.height,              &self.content, -            size,              self.line_height, -            self.font.unwrap_or_else(|| renderer.default_font()), -            limits.max(), +            self.size, +            self.font, +            self.horizontal_alignment, +            self.vertical_alignment,              self.shaping, -        ); - -        let size = limits.resolve(bounds); - -        layout::Node::new(size) +        )      }      fn draw(          &self, -        _state: &Tree, +        tree: &Tree,          renderer: &mut Renderer,          theme: &Renderer::Theme,          style: &renderer::Style, @@ -163,22 +174,63 @@ where          _cursor_position: mouse::Cursor,          _viewport: &Rectangle,      ) { +        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); +          draw(              renderer,              style,              layout, -            &self.content, -            self.size, -            self.line_height, -            self.font, +            state,              theme.appearance(self.style.clone()), -            self.horizontal_alignment, -            self.vertical_alignment, -            self.shaping,          );      }  } +/// Produces the [`layout::Node`] of a [`Text`] widget. +pub fn layout<Renderer>( +    state: &mut State<Renderer::Paragraph>, +    renderer: &Renderer, +    limits: &layout::Limits, +    width: Length, +    height: Length, +    content: &str, +    line_height: LineHeight, +    size: Option<Pixels>, +    font: Option<Renderer::Font>, +    horizontal_alignment: alignment::Horizontal, +    vertical_alignment: alignment::Vertical, +    shaping: Shaping, +) -> layout::Node +where +    Renderer: text::Renderer, +{ +    let limits = limits.width(width).height(height); +    let bounds = limits.max(); + +    let size = size.unwrap_or_else(|| renderer.default_size()); +    let font = font.unwrap_or_else(|| renderer.default_font()); + +    let State(ref mut paragraph) = state; + +    renderer.update_paragraph( +        paragraph, +        text::Text { +            content, +            bounds, +            size, +            line_height, +            font, +            shaping, +            horizontal_alignment, +            vertical_alignment, +        }, +    ); + +    let size = limits.resolve(paragraph.min_bounds()); + +    layout::Node::new(size) +} +  /// Draws text using the same logic as the [`Text`] widget.  ///  /// Specifically: @@ -193,44 +245,31 @@ pub fn draw<Renderer>(      renderer: &mut Renderer,      style: &renderer::Style,      layout: Layout<'_>, -    content: &str, -    size: Option<f32>, -    line_height: LineHeight, -    font: Option<Renderer::Font>, +    state: &State<Renderer::Paragraph>,      appearance: Appearance, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -    shaping: Shaping,  ) where      Renderer: text::Renderer,  { +    let State(ref paragraph) = state;      let bounds = layout.bounds(); -    let x = match horizontal_alignment { +    let x = match paragraph.horizontal_alignment() {          alignment::Horizontal::Left => bounds.x,          alignment::Horizontal::Center => bounds.center_x(),          alignment::Horizontal::Right => bounds.x + bounds.width,      }; -    let y = match vertical_alignment { +    let y = match paragraph.vertical_alignment() {          alignment::Vertical::Top => bounds.y,          alignment::Vertical::Center => bounds.center_y(),          alignment::Vertical::Bottom => bounds.y + bounds.height,      }; -    let size = size.unwrap_or_else(|| renderer.default_size()); - -    renderer.fill_text(crate::Text { -        content, -        size, -        line_height, -        bounds: Rectangle { x, y, ..bounds }, -        color: appearance.color.unwrap_or(style.text_color), -        font: font.unwrap_or_else(|| renderer.default_font()), -        horizontal_alignment, -        vertical_alignment, -        shaping, -    }); +    renderer.fill_paragraph( +        paragraph, +        Point::new(x, y), +        appearance.color.unwrap_or(style.text_color), +    );  }  impl<'a, Message, Renderer> From<Text<'a, Renderer>> diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index 0af40c33..202cca9a 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -107,6 +107,88 @@ impl Tree {      }  } +/// Reconciliates the `current_children` with the provided list of widgets using +/// custom logic both for diffing and creating new widget state. +/// +/// The algorithm will try to minimize the impact of diffing by querying the +/// `maybe_changed` closure. +pub fn diff_children_custom_with_search<T>( +    current_children: &mut Vec<Tree>, +    new_children: &[T], +    diff: impl Fn(&mut Tree, &T), +    maybe_changed: impl Fn(usize) -> bool, +    new_state: impl Fn(&T) -> Tree, +) { +    if new_children.is_empty() { +        current_children.clear(); +        return; +    } + +    if current_children.is_empty() { +        current_children.extend(new_children.iter().map(new_state)); +        return; +    } + +    let first_maybe_changed = maybe_changed(0); +    let last_maybe_changed = maybe_changed(current_children.len() - 1); + +    if current_children.len() > new_children.len() { +        if !first_maybe_changed && last_maybe_changed { +            current_children.truncate(new_children.len()); +        } else { +            let difference_index = if first_maybe_changed { +                0 +            } else { +                (1..current_children.len()) +                    .find(|&i| maybe_changed(i)) +                    .unwrap_or(0) +            }; + +            let _ = current_children.splice( +                difference_index +                    ..difference_index +                        + (current_children.len() - new_children.len()), +                std::iter::empty(), +            ); +        } +    } + +    if current_children.len() < new_children.len() { +        let first_maybe_changed = maybe_changed(0); +        let last_maybe_changed = maybe_changed(current_children.len() - 1); + +        if !first_maybe_changed && last_maybe_changed { +            current_children.extend( +                new_children[current_children.len()..].iter().map(new_state), +            ); +        } else { +            let difference_index = if first_maybe_changed { +                0 +            } else { +                (1..current_children.len()) +                    .find(|&i| maybe_changed(i)) +                    .unwrap_or(0) +            }; + +            let _ = current_children.splice( +                difference_index..difference_index, +                new_children[difference_index +                    ..difference_index +                        + (new_children.len() - current_children.len())] +                    .iter() +                    .map(new_state), +            ); +        } +    } + +    // TODO: Merge loop with extend logic (?) +    for (child_state, new) in +        current_children.iter_mut().zip(new_children.iter()) +    { +        diff(child_state, new); +    } +} +  /// The identifier of some widget state.  #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]  pub struct Tag(any::TypeId); | 
