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.rs862
1 files changed, 402 insertions, 460 deletions
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index 1f20e2bc..beb4e0c1 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -1,5 +1,4 @@
//! Display a dropdown list of selectable values.
-use crate::container;
use crate::core::alignment;
use crate::core::event::{self, Event};
use crate::core::keyboard;
@@ -11,15 +10,13 @@ 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, Point, Rectangle,
- Shell, Size, Vector, Widget,
+ Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
+ Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::overlay::menu::{self, Menu};
-use crate::scrollable;
use std::borrow::Borrow;
-
-pub use crate::style::pick_list::{Appearance, StyleSheet};
+use std::f32;
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
@@ -35,7 +32,6 @@ pub struct PickList<
T: ToString + PartialEq + Clone,
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
- Theme: StyleSheet,
Renderer: text::Renderer,
{
on_select: Box<dyn Fn(T) -> Message + 'a>,
@@ -51,7 +47,7 @@ pub struct PickList<
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
- style: Theme::Style,
+ style: Style<Theme>,
}
impl<'a, T, L, V, Message, Theme, Renderer>
@@ -61,23 +57,18 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
- Theme: StyleSheet
- + scrollable::StyleSheet
- + menu::StyleSheet
- + container::StyleSheet,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer,
{
- /// The default padding of a [`PickList`].
- pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
-
/// Creates a new [`PickList`] with the given list of options, the current
/// selected value, and the message to produce when an option is selected.
pub fn new(
options: L,
selected: Option<V>,
on_select: impl Fn(T) -> Message + 'a,
- ) -> Self {
+ ) -> Self
+ where
+ Theme: DefaultStyle,
+ {
Self {
on_select: Box::new(on_select),
on_open: None,
@@ -86,13 +77,13 @@ where
placeholder: None,
selected,
width: Length::Shrink,
- padding: Self::DEFAULT_PADDING,
+ padding: crate::button::DEFAULT_PADDING,
text_size: None,
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
handle: Handle::default(),
- style: Default::default(),
+ style: Theme::default_style(),
}
}
@@ -160,10 +151,7 @@ where
}
/// Sets the style of the [`PickList`].
- pub fn style(
- mut self,
- style: impl Into<<Theme as StyleSheet>::Style>,
- ) -> Self {
+ pub fn style(mut self, style: impl Into<Style<Theme>>) -> Self {
self.style = style.into();
self
}
@@ -176,11 +164,6 @@ where
L: Borrow<[T]>,
V: Borrow<T>,
Message: Clone + 'a,
- Theme: StyleSheet
- + scrollable::StyleSheet
- + menu::StyleSheet
- + container::StyleSheet,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer + 'a,
{
fn tag(&self) -> tree::Tag {
@@ -204,19 +187,77 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- layout(
- tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
- renderer,
- limits,
- self.width,
- self.padding,
- self.text_size,
- self.text_line_height,
- self.text_shaping,
- self.font,
- self.placeholder.as_deref(),
- self.options.borrow(),
- )
+ let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+
+ let font = self.font.unwrap_or_else(|| renderer.default_font());
+ let text_size =
+ self.text_size.unwrap_or_else(|| renderer.default_size());
+ let options = self.options.borrow();
+
+ state.options.resize_with(options.len(), Default::default);
+
+ let option_text = Text {
+ content: "",
+ bounds: Size::new(
+ f32::INFINITY,
+ self.text_line_height.to_absolute(text_size).into(),
+ ),
+ size: text_size,
+ line_height: self.text_line_height,
+ font,
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping: self.text_shaping,
+ };
+
+ for (option, paragraph) in options.iter().zip(state.options.iter_mut())
+ {
+ let label = option.to_string();
+
+ paragraph.update(Text {
+ content: &label,
+ ..option_text
+ });
+ }
+
+ if let Some(placeholder) = &self.placeholder {
+ state.placeholder.update(Text {
+ content: placeholder,
+ ..option_text
+ });
+ }
+
+ let max_width = match self.width {
+ Length::Shrink => {
+ let labels_width =
+ state.options.iter().fold(0.0, |width, paragraph| {
+ f32::max(width, paragraph.min_width())
+ });
+
+ labels_width.max(
+ self.placeholder
+ .as_ref()
+ .map(|_| state.placeholder.min_width())
+ .unwrap_or(0.0),
+ )
+ }
+ _ => 0.0,
+ };
+
+ let size = {
+ let intrinsic = Size::new(
+ max_width + text_size.0 + self.padding.left,
+ f32::from(self.text_line_height.to_absolute(text_size)),
+ );
+
+ limits
+ .width(self.width)
+ .shrink(self.padding)
+ .resolve(self.width, Length::Shrink, intrinsic)
+ .expand(self.padding)
+ };
+
+ layout::Node::new(size)
}
fn on_event(
@@ -230,18 +271,98 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
- update(
- event,
- layout,
- cursor,
- shell,
- self.on_select.as_ref(),
- self.on_open.as_ref(),
- self.on_close.as_ref(),
- self.selected.as_ref().map(Borrow::borrow),
- self.options.borrow(),
- || tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
- )
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let state =
+ tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+
+ if state.is_open {
+ // Event wasn't processed by overlay, so cursor was clicked either outside its
+ // bounds or on the drop-down, either way we close the overlay.
+ state.is_open = false;
+
+ if let Some(on_close) = &self.on_close {
+ shell.publish(on_close.clone());
+ }
+
+ event::Status::Captured
+ } else if cursor.is_over(layout.bounds()) {
+ let selected = self.selected.as_ref().map(Borrow::borrow);
+
+ state.is_open = true;
+ state.hovered_option = self
+ .options
+ .borrow()
+ .iter()
+ .position(|option| Some(option) == selected);
+
+ if let Some(on_open) = &self.on_open {
+ shell.publish(on_open.clone());
+ }
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ Event::Mouse(mouse::Event::WheelScrolled {
+ delta: mouse::ScrollDelta::Lines { y, .. },
+ }) => {
+ let state =
+ tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+
+ if state.keyboard_modifiers.command()
+ && cursor.is_over(layout.bounds())
+ && !state.is_open
+ {
+ fn find_next<'a, T: PartialEq>(
+ selected: &'a T,
+ mut options: impl Iterator<Item = &'a T>,
+ ) -> Option<&'a T> {
+ let _ = options.find(|&option| option == selected);
+
+ options.next()
+ }
+
+ let options = self.options.borrow();
+ let selected = self.selected.as_ref().map(Borrow::borrow);
+
+ let next_option = if y < 0.0 {
+ if let Some(selected) = selected {
+ find_next(selected, options.iter())
+ } else {
+ options.first()
+ }
+ } else if y > 0.0 {
+ if let Some(selected) = selected {
+ find_next(selected, options.iter().rev())
+ } else {
+ options.last()
+ }
+ } else {
+ None
+ };
+
+ if let Some(next_option) = next_option {
+ shell.publish((self.on_select)(next_option.clone()));
+ }
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
+ let state =
+ tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+
+ state.keyboard_modifiers = modifiers;
+
+ event::Status::Ignored
+ }
+ _ => event::Status::Ignored,
+ }
}
fn mouse_interaction(
@@ -252,7 +373,14 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor)
+ let bounds = layout.bounds();
+ let is_mouse_over = cursor.is_over(bounds);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
}
fn draw(
@@ -266,23 +394,124 @@ where
viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
- draw(
- renderer,
- theme,
- layout,
- cursor,
- self.padding,
- self.text_size,
- self.text_line_height,
- self.text_shaping,
- font,
- self.placeholder.as_deref(),
- self.selected.as_ref().map(Borrow::borrow),
- &self.handle,
- &self.style,
- || tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
- viewport,
+ let selected = self.selected.as_ref().map(Borrow::borrow);
+ let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
+
+ let bounds = layout.bounds();
+ let is_mouse_over = cursor.is_over(bounds);
+ let is_selected = selected.is_some();
+
+ let status = if state.is_open {
+ Status::Opened
+ } else if is_mouse_over {
+ Status::Hovered
+ } else {
+ Status::Active
+ };
+
+ let appearance = (self.style.field)(theme, status);
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border: appearance.border,
+ ..renderer::Quad::default()
+ },
+ appearance.background,
);
+
+ let handle = match &self.handle {
+ Handle::Arrow { size } => Some((
+ Renderer::ICON_FONT,
+ Renderer::ARROW_DOWN_ICON,
+ *size,
+ text::LineHeight::default(),
+ text::Shaping::Basic,
+ )),
+ Handle::Static(Icon {
+ font,
+ code_point,
+ size,
+ line_height,
+ shaping,
+ }) => Some((*font, *code_point, *size, *line_height, *shaping)),
+ Handle::Dynamic { open, closed } => {
+ if state.is_open {
+ Some((
+ open.font,
+ open.code_point,
+ open.size,
+ open.line_height,
+ open.shaping,
+ ))
+ } else {
+ Some((
+ closed.font,
+ closed.code_point,
+ closed.size,
+ closed.line_height,
+ closed.shaping,
+ ))
+ }
+ }
+ Handle::None => None,
+ };
+
+ 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,
+ bounds: Size::new(
+ bounds.width,
+ f32::from(line_height.to_absolute(size)),
+ ),
+ horizontal_alignment: alignment::Horizontal::Right,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping,
+ },
+ Point::new(
+ bounds.x + bounds.width - self.padding.right,
+ bounds.center_y(),
+ ),
+ appearance.handle_color,
+ *viewport,
+ );
+ }
+
+ let label = selected.map(ToString::to_string);
+
+ if let Some(label) = label.as_deref().or(self.placeholder.as_deref()) {
+ let text_size =
+ self.text_size.unwrap_or_else(|| renderer.default_size());
+
+ renderer.fill_text(
+ Text {
+ content: label,
+ size: text_size,
+ line_height: self.text_line_height,
+ font,
+ bounds: Size::new(
+ bounds.width - self.padding.horizontal(),
+ f32::from(self.text_line_height.to_absolute(text_size)),
+ ),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ shaping: self.text_shaping,
+ },
+ Point::new(bounds.x + self.padding.left, bounds.center_y()),
+ if is_selected {
+ appearance.text_color
+ } else {
+ appearance.placeholder_color
+ },
+ *viewport,
+ );
+ }
}
fn overlay<'b>(
@@ -293,19 +522,38 @@ where
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+ let font = self.font.unwrap_or_else(|| renderer.default_font());
- overlay(
- layout,
- translation,
- state,
- self.padding,
- self.text_size,
- self.text_shaping,
- self.font.unwrap_or_else(|| renderer.default_font()),
- self.options.borrow(),
- &self.on_select,
- self.style.clone(),
- )
+ if state.is_open {
+ let bounds = layout.bounds();
+
+ let on_select = &self.on_select;
+
+ let mut menu = Menu::with_style(
+ &mut state.menu,
+ self.options.borrow(),
+ &mut state.hovered_option,
+ |option| {
+ state.is_open = false;
+
+ (on_select)(option)
+ },
+ None,
+ self.style.menu,
+ )
+ .width(bounds.width)
+ .padding(self.padding)
+ .font(font)
+ .text_shaping(self.text_shaping);
+
+ if let Some(text_size) = self.text_size {
+ menu = menu.text_size(text_size);
+ }
+
+ Some(menu.overlay(layout.position() + translation, bounds.height))
+ } else {
+ None
+ }
}
}
@@ -317,12 +565,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone + 'a,
- Theme: StyleSheet
- + scrollable::StyleSheet
- + menu::StyleSheet
- + container::StyleSheet
- + 'a,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
+ Theme: 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -332,9 +575,8 @@ where
}
}
-/// The state of a [`PickList`].
#[derive(Debug)]
-pub struct State<P: text::Paragraph> {
+struct State<P: text::Paragraph> {
menu: menu::State,
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
@@ -407,394 +649,94 @@ pub struct Icon<Font> {
pub shaping: text::Shaping,
}
-/// 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<Pixels>,
- text_line_height: text::LineHeight,
- text_shaping: text::Shaping,
- font: Option<Renderer::Font>,
- placeholder: Option<&str>,
- options: &[T],
-) -> layout::Node
-where
- Renderer: text::Renderer,
- T: ToString,
-{
- use std::f32;
-
- let font = font.unwrap_or_else(|| renderer.default_font());
- let text_size = text_size.unwrap_or_else(|| renderer.default_size());
-
- 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,
- };
-
- for (option, paragraph) in options.iter().zip(state.options.iter_mut()) {
- let label = option.to_string();
-
- paragraph.update(Text {
- content: &label,
- ..option_text
- });
- }
+/// The possible status of a [`PickList`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Status {
+ /// The [`PickList`] can be interacted with.
+ Active,
+ /// The [`PickList`] is being hovered.
+ Hovered,
+ /// The [`PickList`] is open.
+ Opened,
+}
- if let Some(placeholder) = placeholder {
- state.placeholder.update(Text {
- content: placeholder,
- ..option_text
- });
- }
+/// The appearance of a pick list.
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The text [`Color`] of the pick list.
+ pub text_color: Color,
+ /// The placeholder [`Color`] of the pick list.
+ pub placeholder_color: Color,
+ /// The handle [`Color`] of the pick list.
+ pub handle_color: Color,
+ /// The [`Background`] of the pick list.
+ pub background: Background,
+ /// The [`Border`] of the pick list.
+ pub border: Border,
+}
- 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,
- };
+/// The styles of the different parts of a [`PickList`].
+#[derive(Debug, PartialEq, Eq)]
+pub struct Style<Theme> {
+ /// The style of the [`PickList`] itself.
+ pub field: fn(&Theme, Status) -> Appearance,
- let size = {
- let intrinsic = Size::new(
- max_width + text_size.0 + padding.left,
- f32::from(text_line_height.to_absolute(text_size)),
- );
+ /// The style of the [`Menu`] of the pick list.
+ pub menu: menu::Style<Theme>,
+}
- limits
- .width(width)
- .shrink(padding)
- .resolve(width, Length::Shrink, intrinsic)
- .expand(padding)
+impl Style<Theme> {
+ /// The default style of a [`PickList`] with the built-in [`Theme`].
+ pub const DEFAULT: Self = Self {
+ field: default,
+ menu: menu::Style::<Theme>::DEFAULT,
};
-
- layout::Node::new(size)
}
-/// Processes an [`Event`] and updates the [`State`] of a [`PickList`]
-/// accordingly.
-pub fn update<'a, T, P, Message>(
- event: Event,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- shell: &mut Shell<'_, Message>,
- on_select: &dyn Fn(T) -> Message,
- on_open: Option<&Message>,
- on_close: Option<&Message>,
- selected: Option<&T>,
- options: &[T],
- state: impl FnOnce() -> &'a mut State<P>,
-) -> event::Status
-where
- T: PartialEq + Clone + 'a,
- P: text::Paragraph + 'a,
- Message: Clone,
-{
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let state = state();
-
- if state.is_open {
- // Event wasn't processed by overlay, so cursor was clicked either outside it's
- // bounds or on the drop-down, either way we close the overlay.
- state.is_open = false;
-
- if let Some(on_close) = on_close {
- shell.publish(on_close.clone());
- }
-
- event::Status::Captured
- } else if cursor.is_over(layout.bounds()) {
- state.is_open = true;
- state.hovered_option =
- options.iter().position(|option| Some(option) == selected);
-
- if let Some(on_open) = on_open {
- shell.publish(on_open.clone());
- }
-
- event::Status::Captured
- } else {
- event::Status::Ignored
- }
- }
- Event::Mouse(mouse::Event::WheelScrolled {
- delta: mouse::ScrollDelta::Lines { y, .. },
- }) => {
- let state = state();
-
- if state.keyboard_modifiers.command()
- && cursor.is_over(layout.bounds())
- && !state.is_open
- {
- fn find_next<'a, T: PartialEq>(
- selected: &'a T,
- mut options: impl Iterator<Item = &'a T>,
- ) -> Option<&'a T> {
- let _ = options.find(|&option| option == selected);
-
- options.next()
- }
-
- let next_option = if y < 0.0 {
- if let Some(selected) = selected {
- find_next(selected, options.iter())
- } else {
- options.first()
- }
- } else if y > 0.0 {
- if let Some(selected) = selected {
- find_next(selected, options.iter().rev())
- } else {
- options.last()
- }
- } else {
- None
- };
-
- if let Some(next_option) = next_option {
- shell.publish((on_select)(next_option.clone()));
- }
-
- event::Status::Captured
- } else {
- event::Status::Ignored
- }
- }
- Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
- let state = state();
-
- state.keyboard_modifiers = modifiers;
-
- event::Status::Ignored
- }
- _ => event::Status::Ignored,
+impl<Theme> Clone for Style<Theme> {
+ fn clone(&self) -> Self {
+ *self
}
}
-/// Returns the current [`mouse::Interaction`] of a [`PickList`].
-pub fn mouse_interaction(
- layout: Layout<'_>,
- cursor: mouse::Cursor,
-) -> mouse::Interaction {
- let bounds = layout.bounds();
- let is_mouse_over = cursor.is_over(bounds);
-
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- }
-}
+impl<Theme> Copy for Style<Theme> {}
-/// Returns the current overlay of a [`PickList`].
-pub fn overlay<'a, T, Message, Theme, Renderer>(
- layout: Layout<'_>,
- translation: Vector,
- state: &'a mut State<Renderer::Paragraph>,
- padding: Padding,
- text_size: Option<Pixels>,
- text_shaping: text::Shaping,
- font: Renderer::Font,
- options: &'a [T],
- on_selected: &'a dyn Fn(T) -> Message,
- style: <Theme as StyleSheet>::Style,
-) -> Option<overlay::Element<'a, Message, Theme, Renderer>>
-where
- T: Clone + ToString,
- Message: 'a,
- Theme: StyleSheet
- + scrollable::StyleSheet
- + menu::StyleSheet
- + container::StyleSheet
- + 'a,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
- Renderer: text::Renderer + 'a,
-{
- if state.is_open {
- let bounds = layout.bounds();
-
- let mut menu = Menu::new(
- &mut state.menu,
- options,
- &mut state.hovered_option,
- |option| {
- state.is_open = false;
-
- (on_selected)(option)
- },
- None,
- )
- .width(bounds.width)
- .padding(padding)
- .font(font)
- .text_shaping(text_shaping)
- .style(style);
-
- if let Some(text_size) = text_size {
- menu = menu.text_size(text_size);
- }
+/// The default style of a [`PickList`].
+pub trait DefaultStyle: Sized {
+ /// Returns the default style of a [`PickList`].
+ fn default_style() -> Style<Self>;
+}
- Some(menu.overlay(layout.position() + translation, bounds.height))
- } else {
- None
+impl DefaultStyle for Theme {
+ fn default_style() -> Style<Self> {
+ Style::<Self>::DEFAULT
}
}
-/// Draws a [`PickList`].
-pub fn draw<'a, T, Theme, Renderer>(
- renderer: &mut Renderer,
- theme: &Theme,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- padding: Padding,
- text_size: Option<Pixels>,
- text_line_height: text::LineHeight,
- text_shaping: text::Shaping,
- font: Renderer::Font,
- placeholder: Option<&str>,
- selected: Option<&T>,
- handle: &Handle<Renderer::Font>,
- style: &Theme::Style,
- state: impl FnOnce() -> &'a State<Renderer::Paragraph>,
- viewport: &Rectangle,
-) where
- Renderer: text::Renderer,
- Theme: StyleSheet,
- T: ToString + 'a,
-{
- let bounds = layout.bounds();
- let is_mouse_over = cursor.is_over(bounds);
- let is_selected = selected.is_some();
-
- let style = if is_mouse_over {
- theme.hovered(style)
- } else {
- theme.active(style)
- };
-
- renderer.fill_quad(
- renderer::Quad {
- bounds,
- border: style.border,
- ..renderer::Quad::default()
+/// The default style of the field of a [`PickList`].
+pub fn default(theme: &Theme, status: Status) -> Appearance {
+ let palette = theme.extended_palette();
+
+ let active = Appearance {
+ text_color: palette.background.weak.text,
+ background: palette.background.weak.color.into(),
+ placeholder_color: palette.background.strong.color,
+ handle_color: palette.background.weak.text,
+ border: Border {
+ radius: 2.0.into(),
+ width: 1.0,
+ color: palette.background.strong.color,
},
- style.background,
- );
-
- let handle = match handle {
- Handle::Arrow { size } => Some((
- Renderer::ICON_FONT,
- Renderer::ARROW_DOWN_ICON,
- *size,
- text::LineHeight::default(),
- text::Shaping::Basic,
- )),
- Handle::Static(Icon {
- font,
- code_point,
- size,
- line_height,
- shaping,
- }) => Some((*font, *code_point, *size, *line_height, *shaping)),
- Handle::Dynamic { open, closed } => {
- if state().is_open {
- Some((
- open.font,
- open.code_point,
- open.size,
- open.line_height,
- open.shaping,
- ))
- } else {
- Some((
- closed.font,
- closed.code_point,
- closed.size,
- closed.line_height,
- closed.shaping,
- ))
- }
- }
- Handle::None => None,
};
- 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,
- bounds: Size::new(
- bounds.width,
- f32::from(line_height.to_absolute(size)),
- ),
- 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,
- *viewport,
- );
- }
-
- let label = selected.map(ToString::to_string);
-
- 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,
- 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
+ match status {
+ Status::Active => active,
+ Status::Hovered | Status::Opened => Appearance {
+ border: Border {
+ color: palette.primary.strong.color,
+ ..active.border
},
- *viewport,
- );
+ ..active
+ },
}
}