diff options
author | 2023-08-30 04:31:21 +0200 | |
---|---|---|
committer | 2023-08-30 04:31:21 +0200 | |
commit | ed3454301e663a7cb7d73cd56b57b188f4d14a2f (patch) | |
tree | 8118d1305c2eba3a1b45d04634cd0e8d050fc0fa /widget/src/pick_list.rs | |
parent | c9bd48704dd9679c033dd0b8588e2744a3df44a0 (diff) | |
download | iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.tar.gz iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.tar.bz2 iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.zip |
Implement explicit text caching in the widget state tree
Diffstat (limited to 'widget/src/pick_list.rs')
-rw-r--r-- | widget/src/pick_list.rs | 198 |
1 files changed, 121 insertions, 77 deletions
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 0a1e2a99..719aa066 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -7,17 +7,18 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; -use crate::core::text::{self, Text}; +use crate::core::text::{self, Paragraph as _, Text}; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, - Size, Widget, + Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, + Shell, Size, Widget, }; use crate::overlay::menu::{self, Menu}; use crate::scrollable; use std::borrow::Cow; +use std::cell::RefCell; pub use crate::style::pick_list::{Appearance, StyleSheet}; @@ -35,7 +36,7 @@ where selected: Option<T>, width: Length, padding: Padding, - text_size: Option<f32>, + text_size: Option<Pixels>, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option<Renderer::Font>, @@ -101,7 +102,7 @@ where /// Sets the text size of the [`PickList`]. pub fn text_size(mut self, size: impl Into<Pixels>) -> Self { - self.text_size = Some(size.into().0); + self.text_size = Some(size.into()); self } @@ -157,11 +158,11 @@ where From<<Renderer::Theme as StyleSheet>::Style>, { fn tag(&self) -> tree::Tag { - tree::Tag::of::<State>() + tree::Tag::of::<State<Renderer::Paragraph>>() } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::<Renderer::Paragraph>::new()) } fn width(&self) -> Length { @@ -174,10 +175,12 @@ where fn layout( &self, + tree: &Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( + tree.state.downcast_ref::<State<Renderer::Paragraph>>(), renderer, limits, self.width, @@ -210,7 +213,7 @@ where self.on_selected.as_ref(), self.selected.as_ref(), &self.options, - || tree.state.downcast_mut::<State>(), + || tree.state.downcast_mut::<State<Renderer::Paragraph>>(), ) } @@ -250,7 +253,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::<State>(), + || tree.state.downcast_ref::<State<Renderer::Paragraph>>(), ) } @@ -260,7 +263,7 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - let state = tree.state.downcast_mut::<State>(); + let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>(); overlay( layout, @@ -295,28 +298,32 @@ where } } -/// The local state of a [`PickList`]. +/// The state of a [`PickList`]. #[derive(Debug)] -pub struct State { +pub struct State<P: text::Paragraph> { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option<usize>, + option_paragraphs: RefCell<Vec<P>>, + placeholder_paragraph: RefCell<P>, } -impl State { +impl<P: text::Paragraph> State<P> { /// Creates a new [`State`] for a [`PickList`]. - pub fn new() -> Self { + fn new() -> Self { Self { menu: menu::State::default(), keyboard_modifiers: keyboard::Modifiers::default(), is_open: bool::default(), hovered_option: Option::default(), + option_paragraphs: RefCell::new(Vec::new()), + placeholder_paragraph: RefCell::new(Default::default()), } } } -impl Default for State { +impl<P: text::Paragraph> Default for State<P> { fn default() -> Self { Self::new() } @@ -330,7 +337,7 @@ pub enum Handle<Font> { /// This is the default. Arrow { /// Font size of the content. - size: Option<f32>, + size: Option<Pixels>, }, /// A custom static handle. Static(Icon<Font>), @@ -359,7 +366,7 @@ pub struct Icon<Font> { /// The unicode code point that will be used as the icon. pub code_point: char, /// Font size of the content. - pub size: Option<f32>, + pub size: Option<Pixels>, /// Line height of the content. pub line_height: text::LineHeight, /// The shaping strategy of the icon. @@ -368,11 +375,12 @@ pub struct Icon<Font> { /// Computes the layout of a [`PickList`]. pub fn layout<Renderer, T>( + state: &State<Renderer::Paragraph>, renderer: &Renderer, limits: &layout::Limits, width: Length, padding: Padding, - text_size: Option<f32>, + text_size: Option<Pixels>, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option<Renderer::Font>, @@ -386,38 +394,70 @@ where use std::f32; let limits = limits.width(width).height(Length::Shrink).pad(padding); + let font = font.unwrap_or_else(|| renderer.default_font()); let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - let max_width = match width { - Length::Shrink => { - let measure = |label: &str| -> f32 { - let width = renderer.measure_width( - label, - text_size, - font.unwrap_or_else(|| renderer.default_font()), - text_shaping, - ); - - width.round() - }; + let mut paragraphs = state.option_paragraphs.borrow_mut(); + + paragraphs.resize_with(options.len(), Default::default); + + let option_text = Text { + content: "", + bounds: Size::new( + f32::INFINITY, + text_line_height.to_absolute(text_size).into(), + ), + size: text_size, + line_height: text_line_height, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text_shaping, + }; - let labels = options.iter().map(ToString::to_string); + for (option, paragraph) in options.iter().zip(paragraphs.iter_mut()) { + let label = option.to_string(); - let labels_width = labels - .map(|label| measure(&label)) - .fold(100.0, |candidate, current| current.max(candidate)); + renderer.update_paragraph( + paragraph, + Text { + content: &label, + ..option_text + }, + ); + } - let placeholder_width = placeholder.map(measure).unwrap_or(100.0); + if let Some(placeholder) = placeholder { + let mut paragraph = state.placeholder_paragraph.borrow_mut(); + renderer.update_paragraph( + &mut paragraph, + Text { + content: placeholder, + ..option_text + }, + ); + } - labels_width.max(placeholder_width) + let max_width = match width { + Length::Shrink => { + let labels_width = + paragraphs.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + placeholder + .map(|_| state.placeholder_paragraph.borrow().min_width()) + .unwrap_or(0.0), + ) } _ => 0.0, }; let size = { let intrinsic = Size::new( - max_width + text_size + padding.left, - f32::from(text_line_height.to_absolute(Pixels(text_size))), + max_width + text_size.0 + padding.left, + f32::from(text_line_height.to_absolute(text_size)), ); limits.resolve(intrinsic).pad(padding) @@ -428,7 +468,7 @@ where /// Processes an [`Event`] and updates the [`State`] of a [`PickList`] /// accordingly. -pub fn update<'a, T, Message>( +pub fn update<'a, T, P, Message>( event: Event, layout: Layout<'_>, cursor: mouse::Cursor, @@ -436,10 +476,11 @@ pub fn update<'a, T, Message>( on_selected: &dyn Fn(T) -> Message, selected: Option<&T>, options: &[T], - state: impl FnOnce() -> &'a mut State, + state: impl FnOnce() -> &'a mut State<P>, ) -> event::Status where T: PartialEq + Clone + 'a, + P: text::Paragraph + 'a, { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -534,9 +575,9 @@ pub fn mouse_interaction( /// Returns the current overlay of a [`PickList`]. pub fn overlay<'a, T, Message, Renderer>( layout: Layout<'_>, - state: &'a mut State, + state: &'a mut State<Renderer::Paragraph>, padding: Padding, - text_size: Option<f32>, + text_size: Option<Pixels>, text_shaping: text::Shaping, font: Renderer::Font, options: &'a [T], @@ -591,7 +632,7 @@ pub fn draw<'a, T, Renderer>( layout: Layout<'_>, cursor: mouse::Cursor, padding: Padding, - text_size: Option<f32>, + text_size: Option<Pixels>, text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Renderer::Font, @@ -599,7 +640,7 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle<Renderer::Font>, style: &<Renderer::Theme as StyleSheet>::Style, - state: impl FnOnce() -> &'a State, + state: impl FnOnce() -> &'a State<Renderer::Paragraph>, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -665,22 +706,26 @@ pub fn draw<'a, T, Renderer>( if let Some((font, code_point, size, line_height, shaping)) = handle { let size = size.unwrap_or_else(|| renderer.default_size()); - renderer.fill_text(Text { - content: &code_point.to_string(), - size, - line_height, - font, - color: style.handle_color, - bounds: Rectangle { - x: bounds.x + bounds.width - padding.horizontal(), - y: bounds.center_y(), - height: f32::from(line_height.to_absolute(Pixels(size))), - ..bounds + renderer.fill_text( + Text { + content: &code_point.to_string(), + size, + line_height, + font, + bounds: Size::new( + bounds.width, + f32::from(line_height.to_absolute(size)), + ), + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Center, + shaping, }, - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - shaping, - }); + Point::new( + bounds.x + bounds.width - padding.horizontal(), + bounds.center_y(), + ), + style.handle_color, + ); } let label = selected.map(ToString::to_string); @@ -688,27 +733,26 @@ pub fn draw<'a, T, Renderer>( if let Some(label) = label.as_deref().or(placeholder) { let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - renderer.fill_text(Text { - content: label, - size: text_size, - line_height: text_line_height, - font, - color: if is_selected { + renderer.fill_text( + Text { + content: label, + size: text_size, + line_height: text_line_height, + font, + bounds: Size::new( + bounds.width - padding.horizontal(), + f32::from(text_line_height.to_absolute(text_size)), + ), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text_shaping, + }, + Point::new(bounds.x + padding.left, bounds.center_y()), + if is_selected { style.text_color } else { style.placeholder_color }, - bounds: Rectangle { - x: bounds.x + padding.left, - y: bounds.center_y(), - width: bounds.width - padding.horizontal(), - height: f32::from( - text_line_height.to_absolute(Pixels(text_size)), - ), - }, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }); + ); } } |