diff --git a/crates/ui2/src/components/button.rs b/crates/ui2/src/components/button.rs index 085d5f376b596763999a2feac17dab90e7f4e825..25e88201f47417860b85ffc6267408ea47b1c052 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -1,4 +1,5 @@ mod button; +pub(self) mod button_icon; mod button_like; mod icon_button; diff --git a/crates/ui2/src/components/button/button.rs b/crates/ui2/src/components/button/button.rs index 7524a2598724cbdecb3af636e98842f509747ef3..d5f8ce92879c0f025481b6641ab0e896feb9ec5c 100644 --- a/crates/ui2/src/components/button/button.rs +++ b/crates/ui2/src/components/button/button.rs @@ -1,7 +1,11 @@ use gpui::AnyView; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle}; +use crate::{ + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, +}; + +use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct Button { @@ -9,6 +13,10 @@ pub struct Button { label: SharedString, label_color: Option, selected_label: Option, + icon: Option, + icon_size: Option, + icon_color: Option, + selected_icon: Option, } impl Button { @@ -18,6 +26,10 @@ impl Button { label: label.into(), label_color: None, selected_label: None, + icon: None, + icon_size: None, + icon_color: None, + selected_icon: None, } } @@ -30,6 +42,26 @@ impl Button { self.selected_label = label.into().map(Into::into); self } + + pub fn icon(mut self, icon: impl Into>) -> Self { + self.icon = icon.into(); + self + } + + pub fn icon_size(mut self, icon_size: impl Into>) -> Self { + self.icon_size = icon_size.into(); + self + } + + pub fn icon_color(mut self, icon_color: impl Into>) -> Self { + self.icon_color = icon_color.into(); + self + } + + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } } impl Selectable for Button { @@ -81,23 +113,35 @@ impl RenderOnce for Button { type Rendered = ButtonLike; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + let is_disabled = self.base.disabled; + let is_selected = self.base.selected; + let label = self .selected_label - .filter(|_| self.base.selected) + .filter(|_| is_selected) .unwrap_or(self.label); - let label_color = if self.base.disabled { + let label_color = if is_disabled { Color::Disabled - } else if self.base.selected { + } else if is_selected { Color::Selected } else { self.label_color.unwrap_or_default() }; - self.base.child( - Label::new(label) - .color(label_color) - .line_height_style(LineHeightStyle::UILabel), - ) + self.base + .children(self.icon.map(|icon| { + ButtonIcon::new(icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .size(self.icon_size) + .color(self.icon_color) + })) + .child( + Label::new(label) + .color(label_color) + .line_height_style(LineHeightStyle::UILabel), + ) } } diff --git a/crates/ui2/src/components/button/button_icon.rs b/crates/ui2/src/components/button/button_icon.rs new file mode 100644 index 0000000000000000000000000000000000000000..3b2c703938bf0564179a1ce3b9305933b55b0810 --- /dev/null +++ b/crates/ui2/src/components/button/button_icon.rs @@ -0,0 +1,84 @@ +use crate::{prelude::*, Icon, IconElement, IconSize}; + +/// An icon that appears within a button. +/// +/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button), +/// or as a standalone icon, like in [`IconButton`](crate::IconButton). +#[derive(IntoElement)] +pub(super) struct ButtonIcon { + icon: Icon, + size: IconSize, + color: Color, + disabled: bool, + selected: bool, + selected_icon: Option, +} + +impl ButtonIcon { + pub fn new(icon: Icon) -> Self { + Self { + icon, + size: IconSize::default(), + color: Color::default(), + disabled: false, + selected: false, + selected_icon: None, + } + } + + pub fn size(mut self, size: impl Into>) -> Self { + if let Some(size) = size.into() { + self.size = size; + } + + self + } + + pub fn color(mut self, color: impl Into>) -> Self { + if let Some(color) = color.into() { + self.color = color; + } + + self + } + + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } +} + +impl Disableable for ButtonIcon { + fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +impl Selectable for ButtonIcon { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +impl RenderOnce for ButtonIcon { + type Rendered = IconElement; + + fn render(self, _cx: &mut WindowContext) -> Self::Rendered { + let icon = self + .selected_icon + .filter(|_| self.selected) + .unwrap_or(self.icon); + + let icon_color = if self.disabled { + Color::Disabled + } else if self.selected { + Color::Selected + } else { + self.color + }; + + IconElement::new(icon).size(self.size).color(icon_color) + } +} diff --git a/crates/ui2/src/components/button/icon_button.rs b/crates/ui2/src/components/button/icon_button.rs index e405d2296ccb30e2491e63f76db162eb74c74dce..981eb3aaca23a847cdcf834d130eb2dfefe3bee6 100644 --- a/crates/ui2/src/components/button/icon_button.rs +++ b/crates/ui2/src/components/button/icon_button.rs @@ -1,7 +1,9 @@ use gpui::{Action, AnyView}; use crate::prelude::*; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; + +use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct IconButton { @@ -92,23 +94,16 @@ impl RenderOnce for IconButton { type Rendered = ButtonLike; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - let icon = self - .selected_icon - .filter(|_| self.base.selected) - .unwrap_or(self.icon); - - let icon_color = if self.base.disabled { - Color::Disabled - } else if self.base.selected { - Color::Selected - } else { - self.icon_color - }; + let is_disabled = self.base.disabled; + let is_selected = self.base.selected; self.base.child( - IconElement::new(icon) + ButtonIcon::new(self.icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) .size(self.icon_size) - .color(icon_color), + .color(self.icon_color), ) } } diff --git a/crates/ui2/src/components/stories/button.rs b/crates/ui2/src/components/stories/button.rs index 1a95aef6005a056a18792a145f0b75627b7b5652..9fe4f55dcb35c820c4553ef23e462714a51da5a0 100644 --- a/crates/ui2/src/components/stories/button.rs +++ b/crates/ui2/src/components/stories/button.rs @@ -1,7 +1,7 @@ use gpui::{Div, Render}; use story::Story; -use crate::prelude::*; +use crate::{prelude::*, Icon}; use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -24,6 +24,14 @@ impl Render for ButtonStory { ) .child(Story::label("With `label_color`")) .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) + .child(Story::label("With `icon`")) + .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit)) + .child(Story::label("Selected with `icon`")) + .child( + Button::new("filled_and_selected_with_icon", "Click me") + .selected(true) + .icon(Icon::FileGit), + ) .child(Story::label("Default (Subtle)")) .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) .child(Story::label("Default (Transparent)"))