diff options
Diffstat (limited to '')
| -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(),          }      } | 
