diff options
Diffstat (limited to 'widget/src/pick_list.rs')
-rw-r--r-- | widget/src/pick_list.rs | 196 |
1 files changed, 116 insertions, 80 deletions
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 832aae6b..00c1a7ff 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -7,12 +7,12 @@ 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; @@ -35,7 +35,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>, @@ -76,7 +76,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - handle: Default::default(), + handle: Handle::default(), style: Default::default(), } } @@ -101,11 +101,11 @@ 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 } - /// Sets the text [`LineHeight`] of the [`PickList`]. + /// Sets the text [`text::LineHeight`] of the [`PickList`]. pub fn text_line_height( mut self, line_height: impl Into<text::LineHeight>, @@ -157,11 +157,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 +174,12 @@ where 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, @@ -200,6 +202,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, + _viewport: &Rectangle, ) -> event::Status { update( event, @@ -209,7 +212,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>>(), ) } @@ -249,8 +252,8 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::<State>(), - ) + || tree.state.downcast_ref::<State<Renderer::Paragraph>>(), + ); } fn overlay<'b>( @@ -259,7 +262,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, @@ -294,28 +297,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>, + options: Vec<P>, + placeholder: 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(), + options: Vec::new(), + placeholder: P::default(), } } } -impl Default for State { +impl<P: text::Paragraph> Default for State<P> { fn default() -> Self { Self::new() } @@ -329,7 +336,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>), @@ -358,7 +365,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. @@ -367,11 +374,12 @@ pub struct Icon<Font> { /// Computes the layout of a [`PickList`]. pub fn layout<Renderer, T>( + state: &mut 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>, @@ -385,38 +393,61 @@ 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() - }; + state.options.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(state.options.iter_mut()) { + let label = option.to_string(); - let labels_width = labels - .map(|label| measure(&label)) - .fold(100.0, |candidate, current| current.max(candidate)); + paragraph.update(Text { + content: &label, + ..option_text + }); + } - let placeholder_width = placeholder.map(measure).unwrap_or(100.0); + if let Some(placeholder) = placeholder { + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); + } - labels_width.max(placeholder_width) + let max_width = match width { + Length::Shrink => { + let labels_width = + state.options.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + placeholder + .map(|_| state.placeholder.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) @@ -427,7 +458,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, @@ -435,10 +466,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)) @@ -533,9 +565,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], @@ -565,6 +597,7 @@ where (on_selected)(option) }, + None, ) .width(bounds.width) .padding(padding) @@ -589,7 +622,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, @@ -597,7 +630,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, @@ -663,22 +696,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); @@ -686,27 +723,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, - }); + ); } } |