summaryrefslogtreecommitdiffstats
path: root/widget/src/pick_list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'widget/src/pick_list.rs')
-rw-r--r--widget/src/pick_list.rs198
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,
- });
+ );
}
}