diff options
Diffstat (limited to '')
| -rw-r--r-- | widget/src/button.rs | 382 | ||||
| -rw-r--r-- | widget/src/helpers.rs | 2 | 
2 files changed, 235 insertions, 149 deletions
diff --git a/widget/src/button.rs b/widget/src/button.rs index 867fbfaf..798a8206 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -10,11 +10,11 @@ use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation;  use crate::core::{ -    Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, -    Shell, Size, Vector, Widget, +    Background, Border, Clipboard, Color, Element, Layout, Length, Padding, +    Rectangle, Shadow, Shell, Size, Vector, Widget,  }; - -pub use crate::style::button::{Appearance, StyleSheet}; +use crate::style::theme::palette; +use crate::style::Theme;  /// A generic widget that produces a message when pressed.  /// @@ -53,7 +53,7 @@ pub use crate::style::button::{Appearance, StyleSheet};  #[allow(missing_debug_implementations)]  pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>  where -    Theme: StyleSheet, +    Theme: Style,      Renderer: crate::core::Renderer,  {      content: Element<'a, Message, Theme, Renderer>, @@ -62,12 +62,12 @@ where      height: Length,      padding: Padding,      clip: bool, -    style: Theme::Style, +    style: fn(&Theme, Status) -> Appearance,  }  impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>  where -    Theme: StyleSheet, +    Theme: Style,      Renderer: crate::core::Renderer,  {      /// Creates a new [`Button`] with the given content. @@ -84,7 +84,7 @@ where              height: size.height.fluid(),              padding: Padding::new(5.0),              clip: false, -            style: Theme::Style::default(), +            style: Theme::DEFAULT,          }      } @@ -124,7 +124,7 @@ where      }      /// Sets the style variant of this [`Button`]. -    pub fn style(mut self, style: impl Into<Theme::Style>) -> Self { +    pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self {          self.style = style.into();          self      } @@ -137,11 +137,16 @@ where      }  } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +struct State { +    is_pressed: bool, +} +  impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for Button<'a, Message, Theme, Renderer>  where      Message: 'a + Clone, -    Theme: StyleSheet, +    Theme: Style,      Renderer: 'a + crate::core::Renderer,  {      fn tag(&self) -> tree::Tag { @@ -149,7 +154,7 @@ where      }      fn state(&self) -> tree::State { -        tree::State::new(State::new()) +        tree::State::new(State::default())      }      fn children(&self) -> Vec<Tree> { @@ -173,13 +178,19 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        layout(limits, self.width, self.height, self.padding, |limits| { -            self.content.as_widget().layout( -                &mut tree.children[0], -                renderer, -                limits, -            ) -        }) +        layout::padded( +            limits, +            self.width, +            self.height, +            self.padding, +            |limits| { +                self.content.as_widget().layout( +                    &mut tree.children[0], +                    renderer, +                    limits, +                ) +            }, +        )      }      fn operate( @@ -223,9 +234,48 @@ where              return event::Status::Captured;          } -        update(event, layout, cursor, shell, &self.on_press, || { -            tree.state.downcast_mut::<State>() -        }) +        match event { +            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) +            | Event::Touch(touch::Event::FingerPressed { .. }) => { +                if self.on_press.is_some() { +                    let bounds = layout.bounds(); + +                    if cursor.is_over(bounds) { +                        let state = tree.state.downcast_mut::<State>(); + +                        state.is_pressed = true; + +                        return event::Status::Captured; +                    } +                } +            } +            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) +            | Event::Touch(touch::Event::FingerLifted { .. }) => { +                if let Some(on_press) = self.on_press.clone() { +                    let state = tree.state.downcast_mut::<State>(); + +                    if state.is_pressed { +                        state.is_pressed = false; + +                        let bounds = layout.bounds(); + +                        if cursor.is_over(bounds) { +                            shell.publish(on_press); +                        } + +                        return event::Status::Captured; +                    } +                } +            } +            Event::Touch(touch::Event::FingerLost { .. }) => { +                let state = tree.state.downcast_mut::<State>(); + +                state.is_pressed = false; +            } +            _ => {} +        } + +        event::Status::Ignored      }      fn draw( @@ -240,16 +290,39 @@ where      ) {          let bounds = layout.bounds();          let content_layout = layout.children().next().unwrap(); +        let is_mouse_over = cursor.is_over(bounds); -        let styling = draw( -            renderer, -            bounds, -            cursor, -            self.on_press.is_some(), -            theme, -            &self.style, -            || tree.state.downcast_ref::<State>(), -        ); +        let status = if self.on_press.is_none() { +            Status::Disabled +        } else if is_mouse_over { +            let state = tree.state.downcast_ref::<State>(); + +            if state.is_pressed { +                Status::Pressed +            } else { +                Status::Hovered +            } +        } else { +            Status::Active +        }; + +        let styling = (self.style)(theme, status); + +        if styling.background.is_some() +            || styling.border.width > 0.0 +            || styling.shadow.color.a > 0.0 +        { +            renderer.fill_quad( +                renderer::Quad { +                    bounds, +                    border: styling.border, +                    shadow: styling.shadow, +                }, +                styling +                    .background +                    .unwrap_or(Background::Color(Color::TRANSPARENT)), +            ); +        }          let viewport = if self.clip {              bounds.intersection(viewport).unwrap_or(*viewport) @@ -278,7 +351,13 @@ where          _viewport: &Rectangle,          _renderer: &Renderer,      ) -> mouse::Interaction { -        mouse_interaction(layout, cursor, self.on_press.is_some()) +        let is_mouse_over = cursor.is_over(layout.bounds()); + +        if is_mouse_over && self.on_press.is_some() { +            mouse::Interaction::Pointer +        } else { +            mouse::Interaction::default() +        }      }      fn overlay<'b>( @@ -301,7 +380,7 @@ impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>      for Element<'a, Message, Theme, Renderer>  where      Message: Clone + 'a, -    Theme: StyleSheet + 'a, +    Theme: Style + 'a,      Renderer: crate::core::Renderer + 'a,  {      fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -309,143 +388,150 @@ where      }  } -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { -    is_pressed: bool, +/// The possible status of a [`Button`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { +    /// The [`Button`] can be pressed. +    Active, +    /// The [`Button`] can be pressed and it is being hovered. +    Hovered, +    /// The [`Button`] is being pressed. +    Pressed, +    /// The [`Button`] cannot be pressed. +    Disabled,  } -impl State { -    /// Creates a new [`State`]. -    pub fn new() -> State { -        State::default() -    } +/// The appearance of a button. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { +    /// The amount of offset to apply to the shadow of the button. +    pub shadow_offset: Vector, +    /// The [`Background`] of the button. +    pub background: Option<Background>, +    /// The text [`Color`] of the button. +    pub text_color: Color, +    /// The [`Border`] of the buton. +    pub border: Border, +    /// The [`Shadow`] of the butoon. +    pub shadow: Shadow,  } -/// Processes the given [`Event`] and updates the [`State`] of a [`Button`] -/// accordingly. -pub fn update<'a, Message: Clone>( -    event: Event, -    layout: Layout<'_>, -    cursor: mouse::Cursor, -    shell: &mut Shell<'_, Message>, -    on_press: &Option<Message>, -    state: impl FnOnce() -> &'a mut State, -) -> event::Status { -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            if on_press.is_some() { -                let bounds = layout.bounds(); - -                if cursor.is_over(bounds) { -                    let state = state(); - -                    state.is_pressed = true; - -                    return event::Status::Captured; -                } -            } +impl std::default::Default for Appearance { +    fn default() -> Self { +        Self { +            shadow_offset: Vector::default(), +            background: None, +            text_color: Color::BLACK, +            border: Border::default(), +            shadow: Shadow::default(),          } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) => { -            if let Some(on_press) = on_press.clone() { -                let state = state(); - -                if state.is_pressed { -                    state.is_pressed = false; +    } +} -                    let bounds = layout.bounds(); +/// The default style of a [`Button`] for a given theme. +pub trait Style { +    /// The default style. +    const DEFAULT: fn(&Self, Status) -> Appearance; +} -                    if cursor.is_over(bounds) { -                        shell.publish(on_press); -                    } +impl Style for Theme { +    const DEFAULT: fn(&Self, Status) -> Appearance = primary; +} -                    return event::Status::Captured; -                } -            } -        } -        Event::Touch(touch::Event::FingerLost { .. }) => { -            let state = state(); +/// A primary button; denoting a main action. +pub fn primary(theme: &Theme, status: Status) -> Appearance { +    let palette = theme.extended_palette(); +    let base = styled(palette.primary.strong); + +    match status { +        Status::Active | Status::Pressed => base, +        Status::Hovered => Appearance { +            background: Some(Background::Color(palette.primary.base.color)), +            ..base +        }, +        Status::Disabled => disabled(base), +    } +} -            state.is_pressed = false; -        } -        _ => {} +/// A secondary button; denoting a complementary action. +pub fn secondary(theme: &Theme, status: Status) -> Appearance { +    let palette = theme.extended_palette(); +    let base = styled(palette.secondary.base); + +    match status { +        Status::Active | Status::Pressed => base, +        Status::Hovered => Appearance { +            background: Some(Background::Color(palette.secondary.strong.color)), +            ..base +        }, +        Status::Disabled => disabled(base),      } +} -    event::Status::Ignored +/// A positive button; denoting a good outcome. +pub fn positive(theme: &Theme, status: Status) -> Appearance { +    let palette = theme.extended_palette(); +    let base = styled(palette.success.base); + +    match status { +        Status::Active | Status::Pressed => base, +        Status::Hovered => Appearance { +            background: Some(Background::Color(palette.success.strong.color)), +            ..base +        }, +        Status::Disabled => disabled(base), +    }  } -/// Draws a [`Button`]. -pub fn draw<'a, Theme, Renderer: crate::core::Renderer>( -    renderer: &mut Renderer, -    bounds: Rectangle, -    cursor: mouse::Cursor, -    is_enabled: bool, -    theme: &Theme, -    style: &Theme::Style, -    state: impl FnOnce() -> &'a State, -) -> Appearance -where -    Theme: StyleSheet, -{ -    let is_mouse_over = cursor.is_over(bounds); +/// A destructive button; denoting a dangerous action. +pub fn destructive(theme: &Theme, status: Status) -> Appearance { +    let palette = theme.extended_palette(); +    let base = styled(palette.danger.base); + +    match status { +        Status::Active | Status::Pressed => base, +        Status::Hovered => Appearance { +            background: Some(Background::Color(palette.danger.strong.color)), +            ..base +        }, +        Status::Disabled => disabled(base), +    } +} -    let styling = if !is_enabled { -        theme.disabled(style) -    } else if is_mouse_over { -        let state = state(); +/// A text button; useful for links. +pub fn text(theme: &Theme, status: Status) -> Appearance { +    let palette = theme.extended_palette(); -        if state.is_pressed { -            theme.pressed(style) -        } else { -            theme.hovered(style) -        } -    } else { -        theme.active(style) +    let base = Appearance { +        text_color: palette.background.base.text, +        ..Appearance::default()      }; -    if styling.background.is_some() -        || styling.border.width > 0.0 -        || styling.shadow.color.a > 0.0 -    { -        renderer.fill_quad( -            renderer::Quad { -                bounds, -                border: styling.border, -                shadow: styling.shadow, -            }, -            styling -                .background -                .unwrap_or(Background::Color(Color::TRANSPARENT)), -        ); +    match status { +        Status::Active | Status::Pressed => base, +        Status::Hovered => Appearance { +            text_color: palette.background.base.text.transparentize(0.8), +            ..base +        }, +        Status::Disabled => disabled(base),      } - -    styling  } -/// Computes the layout of a [`Button`]. -pub fn layout( -    limits: &layout::Limits, -    width: Length, -    height: Length, -    padding: Padding, -    layout_content: impl FnOnce(&layout::Limits) -> layout::Node, -) -> layout::Node { -    layout::padded(limits, width, height, padding, layout_content) +fn styled(pair: palette::Pair) -> Appearance { +    Appearance { +        background: Some(Background::Color(pair.color)), +        text_color: pair.text, +        border: Border::with_radius(2), +        ..Appearance::default() +    }  } -/// Returns the [`mouse::Interaction`] of a [`Button`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor: mouse::Cursor, -    is_enabled: bool, -) -> mouse::Interaction { -    let is_mouse_over = cursor.is_over(layout.bounds()); - -    if is_mouse_over && is_enabled { -        mouse::Interaction::Pointer -    } else { -        mouse::Interaction::default() +fn disabled(appearance: Appearance) -> Appearance { +    Appearance { +        background: appearance +            .background +            .map(|background| background.transparentize(0.5)), +        text_color: appearance.text_color.transparentize(0.5), +        ..appearance      }  } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e6322926..86331e14 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -118,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>(  ) -> Button<'a, Message, Theme, Renderer>  where      Renderer: core::Renderer, -    Theme: button::StyleSheet, +    Theme: button::Style,  {      Button::new(content)  }  | 
