diff options
Diffstat (limited to 'widget/src')
-rw-r--r-- | widget/src/combo_box.rs | 89 | ||||
-rw-r--r-- | widget/src/container.rs | 27 | ||||
-rw-r--r-- | widget/src/helpers.rs | 11 | ||||
-rw-r--r-- | widget/src/overlay/menu.rs | 143 | ||||
-rw-r--r-- | widget/src/pane_grid/content.rs | 2 | ||||
-rw-r--r-- | widget/src/pane_grid/title_bar.rs | 2 | ||||
-rw-r--r-- | widget/src/pick_list.rs | 449 | ||||
-rw-r--r-- | widget/src/scrollable.rs | 3 | ||||
-rw-r--r-- | widget/src/tooltip.rs | 2 |
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(), } } |