summaryrefslogtreecommitdiffstats
path: root/widget/src
diff options
context:
space:
mode:
Diffstat (limited to 'widget/src')
-rw-r--r--widget/src/combo_box.rs89
-rw-r--r--widget/src/container.rs27
-rw-r--r--widget/src/helpers.rs11
-rw-r--r--widget/src/overlay/menu.rs143
-rw-r--r--widget/src/pane_grid/content.rs2
-rw-r--r--widget/src/pane_grid/title_bar.rs2
-rw-r--r--widget/src/pick_list.rs449
-rw-r--r--widget/src/scrollable.rs3
-rw-r--r--widget/src/tooltip.rs2
9 files changed, 421 insertions, 307 deletions
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index 2ecf799d..533daab2 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -1,4 +1,5 @@
//! Display a dropdown list of searchable and selectable options.
+use crate::container;
use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key;
@@ -13,8 +14,10 @@ use crate::core::{
Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Vector,
};
use crate::overlay::menu;
+use crate::scrollable;
+use crate::style::Theme;
use crate::text::LineHeight;
-use crate::{container, scrollable, text_input, TextInput};
+use crate::text_input::{self, TextInput};
use std::cell::RefCell;
use std::fmt::Display;
@@ -32,7 +35,6 @@ pub struct ComboBox<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Theme: text_input::Style + menu::StyleSheet,
Renderer: text::Renderer,
{
state: &'a State<T>,
@@ -43,7 +45,7 @@ pub struct ComboBox<
on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,
on_close: Option<Message>,
on_input: Option<Box<dyn Fn(String) -> Message>>,
- menu_style: <Theme as menu::StyleSheet>::Style,
+ menu_style: menu::Style<Theme>,
padding: Padding,
size: Option<f32>,
}
@@ -51,7 +53,6 @@ pub struct ComboBox<
impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
- Theme: text_input::Style + menu::StyleSheet,
Renderer: text::Renderer,
{
/// Creates a new [`ComboBox`] with the given list of options, a placeholder,
@@ -62,9 +63,16 @@ where
placeholder: &str,
selection: Option<&T>,
on_selected: impl Fn(T) -> Message + 'static,
- ) -> Self {
+ ) -> Self
+ where
+ Theme: text_input::Style,
+ Style<Theme>: Default,
+ {
+ let style = Style::<Theme>::default();
+
let text_input = TextInput::new(placeholder, &state.value())
- .on_input(TextInputEvent::TextChanged);
+ .on_input(TextInputEvent::TextChanged)
+ .style(style.text_input);
let selection = selection.map(T::to_string).unwrap_or_default();
@@ -77,7 +85,7 @@ where
on_option_hovered: None,
on_input: None,
on_close: None,
- menu_style: Default::default(),
+ menu_style: style.menu,
padding: text_input::DEFAULT_PADDING,
size: None,
}
@@ -118,21 +126,11 @@ where
}
/// Sets the style of the [`ComboBox`].
- // TODO: Define its own `StyleSheet` trait
- pub fn style<S>(mut self, style: S) -> Self
- where
- S: Into<<Theme as menu::StyleSheet>::Style>,
- {
- self.menu_style = style.into();
- self
- }
+ pub fn style(mut self, style: impl Into<Style<Theme>>) -> Self {
+ let style = style.into();
- /// Sets the style of the [`TextInput`] of the [`ComboBox`].
- pub fn text_input_style(
- mut self,
- style: fn(&Theme, text_input::Status) -> text_input::Appearance,
- ) -> Self {
- self.text_input = self.text_input.style(style);
+ self.text_input = self.text_input.style(style.text_input);
+ self.menu_style = style.menu;
self
}
@@ -296,10 +294,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone,
- Theme: container::Style
- + text_input::Style
- + scrollable::Style
- + menu::StyleSheet,
+ Theme: scrollable::Style + container::Style,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@@ -676,7 +671,7 @@ where
self.state.sync_filtered_options(filtered_options);
- let mut menu = menu::Menu::new(
+ let mut menu = menu::Menu::with_style(
menu,
&filtered_options.options,
hovered_option,
@@ -690,10 +685,10 @@ where
(self.on_selected)(x)
},
self.on_option_hovered.as_deref(),
+ self.menu_style,
)
.width(bounds.width)
- .padding(self.padding)
- .style(self.menu_style.clone());
+ .padding(self.padding);
if let Some(font) = self.font {
menu = menu.font(font);
@@ -716,11 +711,7 @@ impl<'a, T, Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone + 'a,
- Theme: container::Style
- + text_input::Style
- + scrollable::Style
- + menu::StyleSheet
- + 'a,
+ Theme: scrollable::Style + container::Style + 'a,
Renderer: text::Renderer + 'a,
{
fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self {
@@ -772,3 +763,35 @@ where
})
.collect()
}
+
+/// The appearance of a [`ComboBox`].
+#[derive(Debug, PartialEq, Eq)]
+pub struct Style<Theme> {
+ /// The style of the [`TextInput`] of the [`ComboBox`].
+ text_input: fn(&Theme, text_input::Status) -> text_input::Appearance,
+
+ /// The style of the [`Menu`] of the [`ComboBox`].
+ menu: menu::Style<Theme>,
+}
+
+impl<Theme> Clone for Style<Theme> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<Theme> Copy for Style<Theme> {}
+
+impl Default for Style<Theme> {
+ fn default() -> Self {
+ default()
+ }
+}
+
+/// The default style of a [`ComboBox`].
+pub fn default() -> Style<Theme> {
+ Style {
+ text_input: text_input::default,
+ menu: menu::Style::default(),
+ }
+}
diff --git a/widget/src/container.rs b/widget/src/container.rs
index 66e80820..58a24339 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -61,7 +61,7 @@ where
max_height: f32::INFINITY,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
- style: Theme::default(),
+ style: Theme::style(),
clip: false,
content,
}
@@ -540,24 +540,24 @@ pub enum Status {
/// The style of a [`Container`] for a theme.
pub trait Style {
/// The default style of a [`Container`].
- fn default() -> fn(&Self, Status) -> Appearance;
+ fn style() -> fn(&Self, Status) -> Appearance;
}
impl Style for Theme {
- fn default() -> fn(&Self, Status) -> Appearance {
+ fn style() -> fn(&Self, Status) -> Appearance {
transparent
}
}
impl Style for Appearance {
- fn default() -> fn(&Self, Status) -> Appearance {
+ fn style() -> fn(&Self, Status) -> Appearance {
|appearance, _status| *appearance
}
}
/// A transparent [`Container`].
pub fn transparent(_theme: &Theme, _status: Status) -> Appearance {
- <Appearance as Default>::default()
+ Appearance::default()
}
/// A rounded [`Container`] with a background.
@@ -567,6 +567,21 @@ pub fn box_(theme: &Theme, _status: Status) -> Appearance {
Appearance {
background: Some(palette.background.weak.color.into()),
border: Border::with_radius(2),
- ..<Appearance as Default>::default()
+ ..Appearance::default()
+ }
+}
+
+/// A bordered [`Container`] with a background.
+pub fn bordered_box(theme: &Theme, _status: Status) -> Appearance {
+ let palette = theme.extended_palette();
+
+ Appearance {
+ background: Some(palette.background.weak.color.into()),
+ border: Border {
+ width: 1.0,
+ radius: 0.0.into(),
+ color: palette.background.strong.color,
+ },
+ ..Appearance::default()
}
}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 8dc2e60f..da9a5792 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -7,7 +7,6 @@ use crate::core;
use crate::core::widget::operation;
use crate::core::{Element, Length, Pixels};
use crate::keyed;
-use crate::overlay;
use crate::pick_list::{self, PickList};
use crate::progress_bar::{self, ProgressBar};
use crate::radio::{self, Radio};
@@ -276,12 +275,7 @@ where
V: Borrow<T> + 'a,
Message: Clone,
Renderer: core::text::Renderer,
- Theme: pick_list::StyleSheet
- + scrollable::Style
- + overlay::menu::StyleSheet
- + container::Style,
- <Theme as overlay::menu::StyleSheet>::Style:
- From<<Theme as pick_list::StyleSheet>::Style>,
+ pick_list::Style<Theme>: Default,
{
PickList::new(options, selected, on_selected)
}
@@ -297,8 +291,9 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>(
) -> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
- Theme: text_input::Style + overlay::menu::StyleSheet,
+ Theme: text_input::Style,
Renderer: core::text::Renderer,
+ combo_box::Style<Theme>: Default,
{
ComboBox::new(state, placeholder, selection, on_selected)
}
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index d820592d..bb8ad0e0 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -10,12 +10,12 @@ use crate::core::text::{self, Text};
use crate::core::touch;
use crate::core::widget::Tree;
use crate::core::{
- Border, Clipboard, Length, Padding, Pixels, Point, Rectangle, Size, Vector,
+ Background, Border, Clipboard, Color, Length, Padding, Pixels, Point,
+ Rectangle, Size, Vector,
};
use crate::core::{Element, Shell, Widget};
use crate::scrollable::{self, Scrollable};
-
-pub use iced_style::menu::{Appearance, StyleSheet};
+use crate::style::Theme;
/// A list of selectable options.
#[allow(missing_debug_implementations)]
@@ -26,7 +26,6 @@ pub struct Menu<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Theme: StyleSheet,
Renderer: text::Renderer,
{
state: &'a mut State,
@@ -40,14 +39,14 @@ pub struct Menu<
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
- style: Theme::Style,
+ style: Style<Theme>,
}
impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
- Theme: StyleSheet + container::Style + scrollable::Style + 'a,
+ Theme: container::Style + scrollable::Style + 'a,
Renderer: text::Renderer + 'a,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
@@ -58,6 +57,29 @@ where
hovered_option: &'a mut Option<usize>,
on_selected: impl FnMut(T) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
+ ) -> Self
+ where
+ Style<Theme>: Default,
+ {
+ Self::with_style(
+ state,
+ options,
+ hovered_option,
+ on_selected,
+ on_option_hovered,
+ Style::default(),
+ )
+ }
+
+ /// Creates a new [`Menu`] with the given [`State`], a list of options,
+ /// the message to produced when an option is selected, and its [`Style`].
+ pub fn with_style(
+ state: &'a mut State,
+ options: &'a [T],
+ hovered_option: &'a mut Option<usize>,
+ on_selected: impl FnMut(T) -> Message + 'a,
+ on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
+ style: Style<Theme>,
) -> Self {
Menu {
state,
@@ -71,7 +93,7 @@ where
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
- style: Default::default(),
+ style,
}
}
@@ -115,10 +137,7 @@ where
}
/// Sets the style of the [`Menu`].
- 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
}
@@ -165,7 +184,6 @@ impl Default for State {
struct Overlay<'a, Message, Theme, Renderer>
where
- Theme: StyleSheet + container::Style,
Renderer: crate::core::Renderer,
{
position: Point,
@@ -173,13 +191,13 @@ where
container: Container<'a, Message, Theme, Renderer>,
width: f32,
target_height: f32,
- style: <Theme as StyleSheet>::Style,
+ style: Style<Theme>,
}
impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: StyleSheet + container::Style + scrollable::Style + 'a,
+ Theme: container::Style + scrollable::Style + 'a,
Renderer: text::Renderer + 'a,
{
pub fn new<T>(
@@ -205,18 +223,21 @@ where
style,
} = menu;
- let container = Container::new(Scrollable::new(List {
- options,
- hovered_option,
- on_selected,
- on_option_hovered,
- font,
- text_size,
- text_line_height,
- text_shaping,
- padding,
- style: style.clone(),
- }));
+ let container = Container::new(
+ Scrollable::new(List {
+ options,
+ hovered_option,
+ on_selected,
+ on_option_hovered,
+ font,
+ text_size,
+ text_line_height,
+ text_shaping,
+ padding,
+ style: style.menu,
+ })
+ .style(style.scrollable),
+ );
state.tree.diff(&container as &dyn Widget<_, _, _>);
@@ -235,7 +256,6 @@ impl<'a, Message, Theme, Renderer>
crate::core::Overlay<Message, Theme, Renderer>
for Overlay<'a, Message, Theme, Renderer>
where
- Theme: StyleSheet + container::Style,
Renderer: text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@@ -302,9 +322,10 @@ where
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
- let appearance = StyleSheet::appearance(theme, &self.style);
let bounds = layout.bounds();
+ let appearance = (self.style.menu)(theme);
+
renderer.fill_quad(
renderer::Quad {
bounds,
@@ -321,7 +342,6 @@ where
struct List<'a, T, Message, Theme, Renderer>
where
- Theme: StyleSheet,
Renderer: text::Renderer,
{
options: &'a [T],
@@ -333,14 +353,13 @@ where
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
- style: Theme::Style,
+ style: fn(&Theme) -> Appearance,
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for List<'a, T, Message, Theme, Renderer>
where
T: Clone + ToString,
- Theme: StyleSheet,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@@ -483,7 +502,7 @@ where
_cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- let appearance = theme.appearance(&self.style);
+ let appearance = (self.style)(theme);
let bounds = layout.bounds();
let text_size =
@@ -553,10 +572,68 @@ impl<'a, T, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
- Theme: StyleSheet + 'a,
+ Theme: 'a,
Renderer: 'a + text::Renderer,
{
fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self {
Element::new(list)
}
}
+
+/// The appearance of a [`Menu`].
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The [`Background`] of the menu.
+ pub background: Background,
+ /// The [`Border`] of the menu.
+ pub border: Border,
+ /// The text [`Color`] of the menu.
+ pub text_color: Color,
+ /// The text [`Color`] of a selected option in the menu.
+ pub selected_text_color: Color,
+ /// The background [`Color`] of a selected option in the menu.
+ pub selected_background: Background,
+}
+
+/// The definiton of the default style of a [`Menu`].
+#[derive(Debug, PartialEq, Eq)]
+pub struct Style<Theme> {
+ /// The style of the [`Menu`].
+ menu: fn(&Theme) -> Appearance,
+ /// The style of the [`Scrollable`] of the [`Menu`].
+ scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance,
+}
+
+impl<Theme> Clone for Style<Theme> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<Theme> Copy for Style<Theme> {}
+
+impl Default for Style<Theme> {
+ fn default() -> Self {
+ Self {
+ menu: default,
+ scrollable: scrollable::default,
+ }
+ }
+}
+
+/// The default style of a [`Menu`].
+pub fn default(theme: &Theme) -> Appearance {
+ let palette = theme.extended_palette();
+
+ Appearance {
+ background: palette.background.weak.color.into(),
+ border: Border {
+ width: 1.0,
+ radius: 0.0.into(),
+ color: palette.background.strong.color,
+ },
+ text_color: palette.background.weak.text,
+ selected_text_color: palette.primary.strong.text,
+ selected_background: palette.primary.strong.color.into(),
+ }
+}
diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index 78a4f347..25b64e17 100644
--- a/widget/src/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -39,7 +39,7 @@ where
Self {
title_bar: None,
body: body.into(),
- style: Theme::default(),
+ style: Theme::style(),
}
}
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 6d786f96..787510cc 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -43,7 +43,7 @@ where
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
- style: Theme::default(),
+ style: Theme::style(),
}
}
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index aeb0f246..546bf294 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -11,16 +11,15 @@ 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, Vector, Widget,
};
use crate::overlay::menu::{self, Menu};
use crate::scrollable;
+use crate::style::Theme;
use std::borrow::Borrow;
-pub use crate::style::pick_list::{Appearance, StyleSheet};
-
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
pub struct PickList<
@@ -35,7 +34,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 +49,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,8 +59,6 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
- Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
Renderer: text::Renderer,
{
/// The default padding of a [`PickList`].
@@ -74,7 +70,10 @@ where
options: L,
selected: Option<V>,
on_select: impl Fn(T) -> Message + 'a,
- ) -> Self {
+ ) -> Self
+ where
+ Style<Theme>: Default,
+ {
Self {
on_select: Box::new(on_select),
on_open: None,
@@ -89,7 +88,7 @@ where
text_shaping: text::Shaping::Basic,
font: None,
handle: Handle::default(),
- style: Default::default(),
+ style: Style::default(),
}
}
@@ -157,10 +156,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
}
@@ -173,8 +169,7 @@ where
L: Borrow<[T]>,
V: Borrow<T>,
Message: Clone + 'a,
- Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
+ Theme: scrollable::Style + container::Style,
Renderer: text::Renderer + 'a,
{
fn tag(&self) -> tree::Tag {
@@ -260,23 +255,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.pick_list)(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.horizontal(),
+ 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>(
@@ -287,19 +383,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
+ }
}
}
@@ -311,12 +426,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone + 'a,
- Theme: StyleSheet
- + scrollable::Style
- + menu::StyleSheet
- + container::Style
- + 'a,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
+ Theme: scrollable::Style + container::Style + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -605,190 +715,83 @@ pub fn mouse_interaction(
}
}
-/// 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::Style
- + menu::StyleSheet
- + container::Style
- + 'a,
- <Theme as menu::StyleSheet>::Style: From<<Theme as StyleSheet>::Style>,
- Renderer: text::Renderer + 'a,
-{
- if state.is_open {
- let bounds = layout.bounds();
+/// 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,
+}
- let mut menu = Menu::new(
- &mut state.menu,
- options,
- &mut state.hovered_option,
- |option| {
- state.is_open = false;
+/// 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,
+}
- (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 different styles of a [`PickList`].
+#[derive(Debug, PartialEq, Eq)]
+pub struct Style<Theme> {
+ /// The style of the [`PickList`] itself.
+ pub pick_list: fn(&Theme, Status) -> Appearance,
- Some(menu.overlay(layout.position() + translation, bounds.height))
- } else {
- None
- }
+ /// The style of the [`Menu`] of the pick list.
+ pub menu: menu::Style<Theme>,
}
-/// 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();
+impl<Theme> Clone for Style<Theme> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
- let style = if is_mouse_over {
- theme.hovered(style)
- } else {
- theme.active(style)
- };
+impl<Theme> Copy for Style<Theme> {}
- renderer.fill_quad(
- renderer::Quad {
- bounds,
- border: style.border,
- ..renderer::Quad::default()
- },
- 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,
- ))
- }
+impl Default for Style<Theme> {
+ fn default() -> Self {
+ Self {
+ pick_list: default,
+ menu: menu::Style::default(),
}
- 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());
+/// The default style 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,
+ },
+ };
- 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
+ },
}
}
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 864fbec8..9772855e 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -1665,7 +1665,8 @@ impl Style for Theme {
}
}
-fn default(theme: &Theme, status: Status) -> Appearance {
+/// The default style of a [`Scrollable`].
+pub fn default(theme: &Theme, status: Status) -> Appearance {
let palette = theme.extended_palette();
let scrollbar = Scrollbar {
diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs
index 11df391e..956383da 100644
--- a/widget/src/tooltip.rs
+++ b/widget/src/tooltip.rs
@@ -56,7 +56,7 @@ where
gap: 0.0,
padding: Self::DEFAULT_PADDING,
snap_within_viewport: true,
- style: Theme::default(),
+ style: Theme::style(),
}
}