Implement Tinted buttons and selected_style for buttons

Nate Butler created

Change summary

crates/ui/src/components/button/button.rs        |  7 ++
crates/ui/src/components/button/button_icon.rs   | 11 +++
crates/ui/src/components/button/button_like.rs   | 62 +++++++++++++++--
crates/ui/src/components/button/icon_button.rs   | 11 ++
crates/ui/src/components/button/toggle_button.rs |  7 ++
crates/ui/src/prelude.rs                         |  2 
6 files changed, 88 insertions(+), 12 deletions(-)

Detailed changes

crates/ui/src/components/button/button.rs 🔗

@@ -92,6 +92,13 @@ impl Selectable for Button {
     }
 }
 
+impl SelectableButton for Button {
+    fn selected_style(mut self, style: ButtonStyle) -> Self {
+        self.base = self.base.selected_style(style);
+        self
+    }
+}
+
 impl Disableable for Button {
     fn disabled(mut self, disabled: bool) -> Self {
         self.base = self.base.disabled(disabled);

crates/ui/src/components/button/button_icon.rs 🔗

@@ -12,6 +12,7 @@ pub(super) struct ButtonIcon {
     disabled: bool,
     selected: bool,
     selected_icon: Option<Icon>,
+    selected_style: Option<ButtonStyle>,
 }
 
 impl ButtonIcon {
@@ -23,6 +24,7 @@ impl ButtonIcon {
             disabled: false,
             selected: false,
             selected_icon: None,
+            selected_style: None,
         }
     }
 
@@ -62,6 +64,13 @@ impl Selectable for ButtonIcon {
     }
 }
 
+impl SelectableButton for ButtonIcon {
+    fn selected_style(mut self, style: ButtonStyle) -> Self {
+        self.selected_style = Some(style);
+        self
+    }
+}
+
 impl RenderOnce for ButtonIcon {
     fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
         let icon = self
@@ -71,6 +80,8 @@ impl RenderOnce for ButtonIcon {
 
         let icon_color = if self.disabled {
             Color::Disabled
+        } else if self.selected_style.is_some() && self.selected {
+            self.selected_style.unwrap().into()
         } else if self.selected {
             Color::Selected
         } else {

crates/ui/src/components/button/button_like.rs 🔗

@@ -4,6 +4,10 @@ use smallvec::SmallVec;
 
 use crate::prelude::*;
 
+pub trait SelectableButton: Selectable {
+    fn selected_style(self, style: ButtonStyle) -> Self;
+}
+
 pub trait ButtonCommon: Clickable + Disableable {
     /// A unique element ID to identify the button.
     fn id(&self) -> &ElementId;
@@ -41,7 +45,6 @@ pub enum TintColor {
     #[default]
     Accent,
     Negative,
-    Positive,
     Warning,
 }
 
@@ -54,17 +57,42 @@ impl TintColor {
                 label_color: cx.theme().colors().text,
                 icon_color: cx.theme().colors().text,
             },
-            // TODO: Finish tint colors.
-            _ => ButtonLikeStyles {
-                background: gpui::red(),
-                border_color: gpui::red(),
-                label_color: gpui::red(),
-                icon_color: gpui::red(),
+            TintColor::Negative => ButtonLikeStyles {
+                background: cx.theme().status().error_background,
+                border_color: cx.theme().status().error_border,
+                label_color: cx.theme().colors().text,
+                icon_color: cx.theme().colors().text,
+            },
+            TintColor::Warning => ButtonLikeStyles {
+                background: cx.theme().status().warning_background,
+                border_color: cx.theme().status().warning_border,
+                label_color: cx.theme().colors().text,
+                icon_color: cx.theme().colors().text,
             },
         }
     }
 }
 
+impl From<TintColor> for Color {
+    fn from(tint: TintColor) -> Self {
+        match tint {
+            TintColor::Accent => Color::Accent,
+            TintColor::Negative => Color::Error,
+            TintColor::Warning => Color::Warning,
+        }
+    }
+}
+
+// Used to go from ButtonStyle -> Color through tint colors.
+impl From<ButtonStyle> for Color {
+    fn from(style: ButtonStyle) -> Self {
+        match style {
+            ButtonStyle::Tinted(tint) => tint.into(),
+            _ => Color::Default,
+        }
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum ButtonStyle {
     /// A filled button with a solid background color. Provides emphasis versus
@@ -266,6 +294,7 @@ pub struct ButtonLike {
     pub(super) style: ButtonStyle,
     pub(super) disabled: bool,
     pub(super) selected: bool,
+    pub(super) selected_style: Option<ButtonStyle>,
     pub(super) width: Option<DefiniteLength>,
     size: ButtonSize,
     rounding: Option<ButtonLikeRounding>,
@@ -282,6 +311,7 @@ impl ButtonLike {
             style: ButtonStyle::default(),
             disabled: false,
             selected: false,
+            selected_style: None,
             width: None,
             size: ButtonSize::Default,
             rounding: Some(ButtonLikeRounding::All),
@@ -311,6 +341,13 @@ impl Selectable for ButtonLike {
     }
 }
 
+impl SelectableButton for ButtonLike {
+    fn selected_style(mut self, style: ButtonStyle) -> Self {
+        self.selected_style = Some(style);
+        self
+    }
+}
+
 impl Clickable for ButtonLike {
     fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
         self.on_click = Some(Box::new(handler));
@@ -366,6 +403,11 @@ impl ParentElement for ButtonLike {
 
 impl RenderOnce for ButtonLike {
     fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+        let style = self
+            .selected_style
+            .filter(|_| self.selected)
+            .unwrap_or(self.style);
+
         self.base
             .h_flex()
             .id(self.id.clone())
@@ -384,12 +426,12 @@ impl RenderOnce for ButtonLike {
                 ButtonSize::Default | ButtonSize::Compact => this.px_1(),
                 ButtonSize::None => this,
             })
-            .bg(self.style.enabled(cx).background)
+            .bg(style.enabled(cx).background)
             .when(self.disabled, |this| this.cursor_not_allowed())
             .when(!self.disabled, |this| {
                 this.cursor_pointer()
-                    .hover(|hover| hover.bg(self.style.hovered(cx).background))
-                    .active(|active| active.bg(self.style.active(cx).background))
+                    .hover(|hover| hover.bg(style.hovered(cx).background))
+                    .active(|active| active.bg(style.active(cx).background))
             })
             .when_some(
                 self.on_click.filter(|_| !self.disabled),

crates/ui/src/components/button/icon_button.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{AnyView, DefiniteLength};
 
-use crate::prelude::*;
+use crate::{prelude::*, SelectableButton};
 use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
 
 use super::button_icon::ButtonIcon;
@@ -55,6 +55,13 @@ impl Selectable for IconButton {
     }
 }
 
+impl SelectableButton for IconButton {
+    fn selected_style(mut self, style: ButtonStyle) -> Self {
+        self.base = self.base.selected_style(style);
+        self
+    }
+}
+
 impl Clickable for IconButton {
     fn on_click(
         mut self,
@@ -109,12 +116,14 @@ impl RenderOnce for IconButton {
     fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
         let is_disabled = self.base.disabled;
         let is_selected = self.base.selected;
+        let selected_style = self.base.selected_style;
 
         self.base.child(
             ButtonIcon::new(self.icon)
                 .disabled(is_disabled)
                 .selected(is_selected)
                 .selected_icon(self.selected_icon)
+                .when_some(selected_style, |this, style| this.selected_style(style))
                 .size(self.icon_size)
                 .color(self.icon_color),
         )

crates/ui/src/components/button/toggle_button.rs 🔗

@@ -63,6 +63,13 @@ impl Selectable for ToggleButton {
     }
 }
 
+impl SelectableButton for ToggleButton {
+    fn selected_style(mut self, style: ButtonStyle) -> Self {
+        self.base.selected_style = Some(style);
+        self
+    }
+}
+
 impl Disableable for ToggleButton {
     fn disabled(mut self, disabled: bool) -> Self {
         self.base = self.base.disabled(disabled);

crates/ui/src/prelude.rs 🔗

@@ -12,7 +12,7 @@ pub use crate::selectable::*;
 pub use crate::styles::{vh, vw};
 pub use crate::visible_on_hover::*;
 pub use crate::{h_stack, v_stack};
-pub use crate::{Button, ButtonSize, ButtonStyle, IconButton};
+pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
 pub use crate::{ButtonCommon, Color, StyledExt};
 pub use crate::{Icon, IconElement, IconPosition, IconSize};
 pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};