diff --git a/crates/story/src/button_story.rs b/crates/story/src/button_story.rs index 06d4c0a8..18a392b7 100644 --- a/crates/story/src/button_story.rs +++ b/crates/story/src/button_story.rs @@ -5,6 +5,7 @@ use gpui::{ use ui::{ button::{Button, ButtonCustomStyle}, + button_group::ButtonGroup, checkbox::Checkbox, h_flex, prelude::FluentBuilder, @@ -405,6 +406,38 @@ impl Render for ButtonStory { ), ), ) + .child( + section("Button Group", cx).child( + ButtonGroup::new("button-group") + .child( + Button::new("button-one", cx) + .label("One") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-two", cx) + .label("Two") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-three", cx) + .label("Three") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ), + ), + ) .child( section("Icon Button", cx) .child( diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 17f0dfd5..1085b2f3 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -6,9 +6,9 @@ use crate::{ Disableable, Icon, Selectable, Sizable, Size, }; use gpui::{ - div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, ElementId, - FocusHandle, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, - RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext, + div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Corners, Div, Edges, + ElementId, FocusHandle, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, + Pixels, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext, }; pub enum ButtonRounded { @@ -109,6 +109,8 @@ pub struct Button { selected: bool, style: ButtonStyle, rounded: ButtonRounded, + border_corners: Corners, + border_edges: Edges, size: Size, compact: bool, tooltip: Option, @@ -134,6 +136,8 @@ impl Button { selected: false, style: ButtonStyle::Secondary, rounded: ButtonRounded::Medium, + border_corners: Corners::all(true), + border_edges: Edges::all(true), size: Size::Medium, tooltip: None, on_click: None, @@ -191,6 +195,18 @@ impl Button { self } + /// Set the border corners side of the Button. + pub(crate) fn border_corners(mut self, corners: impl Into>) -> Self { + self.border_corners = corners.into(); + self + } + + /// Set the border edges of the Button. + pub(crate) fn border_edges(mut self, edges: impl Into>) -> Self { + self.border_edges = edges.into(); + self + } + /// Set label to the Button, if no label is set, the button will be in Icon Button mode. pub fn label(mut self, label: impl Into) -> Self { self.label = Some(label.into()); @@ -310,13 +326,30 @@ impl RenderOnce for Button { } } }) - .map(|this| match self.rounded { - ButtonRounded::Small => this.rounded(px(cx.theme().radius * 0.5)), - ButtonRounded::Medium => this.rounded(px(cx.theme().radius)), - ButtonRounded::Large => this.rounded(px(cx.theme().radius * 2.0)), - ButtonRounded::Size(px) => this.rounded(px), - ButtonRounded::None => this.rounded_none(), - }) + .when( + self.border_corners.top_left && self.border_corners.bottom_left, + |this| match self.rounded { + ButtonRounded::Small => this.rounded_l(px(cx.theme().radius * 0.5)), + ButtonRounded::Medium => this.rounded_l(px(cx.theme().radius)), + ButtonRounded::Large => this.rounded_l(px(cx.theme().radius * 2.0)), + ButtonRounded::Size(px) => this.rounded_l(px), + ButtonRounded::None => this.rounded_none(), + }, + ) + .when( + self.border_corners.top_right && self.border_corners.bottom_right, + |this| match self.rounded { + ButtonRounded::Small => this.rounded_r(px(cx.theme().radius * 0.5)), + ButtonRounded::Medium => this.rounded_r(px(cx.theme().radius)), + ButtonRounded::Large => this.rounded_r(px(cx.theme().radius * 2.0)), + ButtonRounded::Size(px) => this.rounded_r(px), + ButtonRounded::None => this.rounded_none(), + }, + ) + .when(self.border_edges.left, |this| this.border_l_1()) + .when(self.border_edges.right, |this| this.border_r_1()) + .when(self.border_edges.top, |this| this.border_t_1()) + .when(self.border_edges.bottom, |this| this.border_b_1()) .text_color(normal_style.fg) .when(self.selected, |this| { let selected_style = style.selected(cx); @@ -361,7 +394,6 @@ impl RenderOnce for Button { .text_color(disabled_style.fg) .border_color(disabled_style.border) }) - .border_1() .child({ h_flex() .id("label") diff --git a/crates/ui/src/button_group.rs b/crates/ui/src/button_group.rs new file mode 100644 index 00000000..15080921 --- /dev/null +++ b/crates/ui/src/button_group.rs @@ -0,0 +1,98 @@ +use gpui::{ + div, Corners, Div, Edges, ElementId, InteractiveElement, IntoElement, ParentElement, + RenderOnce, Styled, WindowContext, +}; + +use crate::button::Button; + +#[derive(IntoElement)] +pub struct ButtonGroup { + pub base: Div, + id: ElementId, + children: Vec