button: Improve ButtonGroup to add style, size methods. (#241)

- Add `ButtonStyled` trait to apply the style for Button and
ButtonGroup.
This commit is contained in:
Jason Lee 2024-09-13 12:00:47 +08:00 committed by GitHub
parent b83ece3cd8
commit a8d4abea69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 211 additions and 148 deletions

View file

@ -9,7 +9,7 @@ use story::{
SwitchStory, TableStory, TextStory, TooltipStory, SwitchStory, TableStory, TextStory, TooltipStory,
}; };
use ui::{ use ui::{
button::Button, button::{Button, ButtonStyled as _},
color_picker::{ColorPicker, ColorPickerEvent}, color_picker::{ColorPicker, ColorPickerEvent},
dock::{DockArea, DockEvent, DockItem, DockItemState}, dock::{DockArea, DockEvent, DockItem, DockItemState},
h_flex, h_flex,

View file

@ -4,7 +4,7 @@ use gpui::{
}; };
use ui::{ use ui::{
button::{Button, ButtonCustomStyle}, button::{Button, ButtonCustomStyle, ButtonStyled as _},
button_group::ButtonGroup, button_group::ButtonGroup,
checkbox::Checkbox, checkbox::Checkbox,
h_flex, h_flex,
@ -21,7 +21,7 @@ pub struct ButtonStory {
loading: bool, loading: bool,
selected: bool, selected: bool,
compact: bool, compact: bool,
multiple: bool, toggle_multiple: bool,
} }
impl ButtonStory { impl ButtonStory {
@ -32,7 +32,7 @@ impl ButtonStory {
loading: false, loading: false,
selected: false, selected: false,
compact: false, compact: false,
multiple: false, toggle_multiple: false,
}) })
} }
@ -71,7 +71,7 @@ impl Render for ButtonStory {
let loading = self.loading; let loading = self.loading;
let selected = self.selected; let selected = self.selected;
let compact = self.compact; let compact = self.compact;
let multiple = self.multiple; let toggle_multiple = self.toggle_multiple;
v_flex() v_flex()
.gap_6() .gap_6()
@ -436,79 +436,81 @@ impl Render for ButtonStory {
), ),
) )
.child( .child(
section("Button Group", cx).child( section("Button Group", cx)
ButtonGroup::new("button-group")
.disabled(disabled)
.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("Toggle Button Group", cx)
.child( .child(
Checkbox::new("multiple-button") ButtonGroup::new("button-group")
.label("Multiple") .small()
.checked(self.multiple) .disabled(disabled)
.on_click(cx.listener(|view, _, cx| { .child(
view.multiple = !view.multiple; Button::new("button-one", cx)
cx.notify(); .label("One")
})), .disabled(disabled)
.selected(selected)
.when(compact, |this| this.compact())
.on_click(Self::on_click),
)
.child(
Button::new("button-two", cx)
.label("Two")
.disabled(disabled)
.selected(selected)
.when(compact, |this| this.compact())
.on_click(Self::on_click),
)
.child(
Button::new("button-three", cx)
.label("Three")
.disabled(disabled)
.selected(selected)
.when(compact, |this| this.compact())
.on_click(Self::on_click),
),
) )
.child( .child(
ButtonGroup::new("toggle-button-group") h_flex()
.multiple(multiple) .gap_2()
.child( .child(
Button::new("disabled-toggle-button", cx) Checkbox::new("multiple-button")
.label("Disabled") .label("Multiple")
.selected(disabled), .checked(toggle_multiple)
.on_click(cx.listener(|view, _, cx| {
view.toggle_multiple = !view.toggle_multiple;
cx.notify();
})),
) )
.child( .child(
Button::new("loading-toggle-button", cx) ButtonGroup::new("toggle-button-group")
.label("Loading") .primary()
.selected(loading), .compact()
) .multiple(toggle_multiple)
.child( .child(
Button::new("selected-toggle-button", cx) Button::new("disabled-toggle-button", cx)
.label("Selected") .label("Disabled")
.selected(selected), .selected(disabled),
) )
.child( .child(
Button::new("compact-toggle-button", cx) Button::new("loading-toggle-button", cx)
.label("Compact") .label("Loading")
.selected(compact), .selected(loading),
) )
.on_click(cx.listener(|view, selected: &Vec<usize>, cx| { .child(
view.disabled = selected.contains(&0); Button::new("selected-toggle-button", cx)
view.loading = selected.contains(&1); .label("Selected")
view.selected = selected.contains(&2); .selected(selected),
view.compact = selected.contains(&3); )
cx.notify(); .child(
})), Button::new("compact-toggle-button", cx)
.label("Compact")
.selected(compact),
)
.on_click(cx.listener(|view, selected: &Vec<usize>, cx| {
view.disabled = selected.contains(&0);
view.loading = selected.contains(&1);
view.selected = selected.contains(&2);
view.compact = selected.contains(&3);
cx.notify();
})),
),
), ),
) )
.child( .child(

View file

@ -8,7 +8,7 @@ use gpui::{
}; };
use ui::{ use ui::{
button::{Button, ButtonStyle}, button::{Button, ButtonStyle, ButtonStyled as _},
checkbox::Checkbox, checkbox::Checkbox,
date_picker::DatePicker, date_picker::DatePicker,
h_flex, h_flex,

View file

@ -6,7 +6,7 @@ use gpui::{
}; };
use serde::Deserialize; use serde::Deserialize;
use ui::{ use ui::{
button::Button, button::{Button, ButtonStyled as _},
context_menu::ContextMenuExt, context_menu::ContextMenuExt,
divider::Divider, divider::Divider,
h_flex, h_flex,

View file

@ -34,6 +34,45 @@ pub struct ButtonCustomStyle {
active: Hsla, active: Hsla,
} }
pub trait ButtonStyled: Sized {
fn with_style(self, style: ButtonStyle) -> Self;
/// With the primary style for the Button.
fn primary(self) -> Self {
self.with_style(ButtonStyle::Primary)
}
/// With the danger style for the Button.
fn danger(self) -> Self {
self.with_style(ButtonStyle::Danger)
}
/// With the outline style for the Button.
fn outline(self) -> Self {
self.with_style(ButtonStyle::Outline)
}
/// With the ghost style for the Button.
fn ghost(self) -> Self {
self.with_style(ButtonStyle::Ghost)
}
/// With the link style for the Button.
fn link(self) -> Self {
self.with_style(ButtonStyle::Link)
}
/// With the text style for the Button, it will no padding look like a normal text.
fn text(self) -> Self {
self.with_style(ButtonStyle::Text)
}
/// With the custom style for the Button.
fn custom(self, style: ButtonCustomStyle) -> Self {
self.with_style(ButtonStyle::Custom(style))
}
}
impl ButtonCustomStyle { impl ButtonCustomStyle {
pub fn new(cx: &WindowContext) -> Self { pub fn new(cx: &WindowContext) -> Self {
Self { Self {
@ -149,48 +188,6 @@ impl Button {
} }
} }
/// With the primary style for the Button.
pub fn primary(mut self) -> Self {
self.style = ButtonStyle::Primary;
self
}
/// With the secondary style for the Button.
pub fn danger(mut self) -> Self {
self.style = ButtonStyle::Danger;
self
}
/// With the ghost style for the Button.
pub fn ghost(mut self) -> Self {
self.style = ButtonStyle::Ghost;
self
}
/// With the outline style for the Button.
pub fn outline(mut self) -> Self {
self.style = ButtonStyle::Outline;
self
}
/// With the link style for the Button.
pub fn link(mut self) -> Self {
self.style = ButtonStyle::Link;
self
}
/// With the text style for the Button, it will no padding look like a normal text.
pub fn text(mut self) -> Self {
self.style = ButtonStyle::Text;
self
}
/// With the custom style for the Button.
pub fn custom(mut self, custom: ButtonCustomStyle) -> Self {
self.style = ButtonStyle::Custom(custom);
self
}
/// Set the border radius of the Button. /// Set the border radius of the Button.
pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self { pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
self.rounded = rounded.into(); self.rounded = rounded.into();
@ -277,6 +274,13 @@ impl Sizable for Button {
} }
} }
impl ButtonStyled for Button {
fn with_style(mut self, style: ButtonStyle) -> Self {
self.style = style;
self
}
}
impl Styled for Button { impl Styled for Button {
fn style(&mut self) -> &mut gpui::StyleRefinement { fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style() self.base.style()

View file

@ -4,7 +4,10 @@ use gpui::{
}; };
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
use crate::{button::Button, Disableable}; use crate::{
button::{Button, ButtonStyle, ButtonStyled},
Disableable, Sizable, Size,
};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ButtonGroup { pub struct ButtonGroup {
@ -13,6 +16,12 @@ pub struct ButtonGroup {
children: Vec<Button>, children: Vec<Button>,
multiple: bool, multiple: bool,
disabled: bool, disabled: bool,
// The button props
compact: Option<bool>,
style: Option<ButtonStyle>,
size: Option<Size>,
on_click: Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>, on_click: Option<Box<dyn Fn(&Vec<usize>, &mut WindowContext) + 'static>>,
} }
@ -30,6 +39,9 @@ impl ButtonGroup {
base: div(), base: div(),
children: Vec::new(), children: Vec::new(),
id: id.into(), id: id.into(),
style: None,
size: None,
compact: None,
multiple: false, multiple: false,
disabled: false, disabled: false,
on_click: None, on_click: None,
@ -42,34 +54,56 @@ impl ButtonGroup {
self self
} }
/// Sets the multiple selection mode. /// With the multiple selection mode.
pub fn multiple(mut self, multiple: bool) -> Self { pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = multiple; self.multiple = multiple;
self self
} }
/// With the compact mode for the ButtonGroup.
pub fn compact(mut self) -> Self {
self.compact = Some(true);
self
}
/// Sets the on_click handler for the ButtonGroup. /// Sets the on_click handler for the ButtonGroup.
///
/// The handler first argument is a vector of the selected button indices.
pub fn on_click(mut self, handler: impl Fn(&Vec<usize>, &mut WindowContext) + 'static) -> Self { pub fn on_click(mut self, handler: impl Fn(&Vec<usize>, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
self self
} }
} }
impl Sizable for ButtonGroup {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = Some(size.into());
self
}
}
impl Styled for ButtonGroup { impl Styled for ButtonGroup {
fn style(&mut self) -> &mut gpui::StyleRefinement { fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style() self.base.style()
} }
} }
impl ButtonStyled for ButtonGroup {
fn with_style(mut self, style: ButtonStyle) -> Self {
self.style = Some(style);
self
}
}
impl RenderOnce for ButtonGroup { impl RenderOnce for ButtonGroup {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let children_len = self.children.len(); let children_len = self.children.len();
let mut selected: Vec<usize> = Vec::new(); let mut selected_ixs: Vec<usize> = Vec::new();
let shared_state = Rc::new(Cell::new(None)); // Shared state to store the child index let state = Rc::new(Cell::new(None));
for (child_index, child) in self.children.iter().enumerate() { for (ix, child) in self.children.iter().enumerate() {
if child.selected { if child.selected {
selected.push(child_index); selected_ixs.push(ix);
} }
} }
@ -82,7 +116,7 @@ impl RenderOnce for ButtonGroup {
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(child_index, child)| { .map(|(child_index, child)| {
let shared_state_clone = Rc::clone(&shared_state); let state = Rc::clone(&state);
let child = if children_len == 1 { let child = if children_len == 1 {
child child
} else if child_index == 0 { } else if child_index == 0 {
@ -127,8 +161,11 @@ impl RenderOnce for ButtonGroup {
}) })
} }
.stop_propagation(false) .stop_propagation(false)
.on_click(move |_, cx| { .when_some(self.size, |this, size| this.with_size(size))
shared_state_clone.set(Some(child_index)); .when_some(self.style, |this, style| this.style(style))
.when_some(self.compact, |this, _| this.compact())
.on_click(move |_, _| {
state.set(Some(child_index));
}); });
child child
@ -138,21 +175,21 @@ impl RenderOnce for ButtonGroup {
self.on_click.filter(|_| !self.disabled), self.on_click.filter(|_| !self.disabled),
move |this, on_click| { move |this, on_click| {
this.on_click(move |_, cx| { this.on_click(move |_, cx| {
let mut selected = selected.clone(); let mut selected_ixs = selected_ixs.clone();
if let Some(index) = shared_state.get() { if let Some(ix) = state.get() {
if self.multiple { if self.multiple {
if let Some(pos) = selected.iter().position(|&i| i == index) { if let Some(pos) = selected_ixs.iter().position(|&i| i == ix) {
selected.remove(pos); // Toggle off if already selected selected_ixs.remove(pos);
} else { } else {
selected.push(index); // Toggle on if not selected selected_ixs.push(ix);
} }
} else { } else {
selected.clear(); // Clear the existing selection selected_ixs.clear();
selected.push(index); // Replace with the new selection selected_ixs.push(ix);
} }
} }
on_click(&selected, cx); on_click(&selected_ixs, cx);
}) })
}, },
) )

View file

@ -3,7 +3,10 @@ use gpui::{
RenderOnce, SharedString, Styled, WindowContext, RenderOnce, SharedString, Styled, WindowContext,
}; };
use crate::{button::Button, h_flex, IconName, Sizable}; use crate::{
button::{Button, ButtonStyled as _},
h_flex, IconName, Sizable as _,
};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Clipboard { pub struct Clipboard {

View file

@ -9,7 +9,7 @@ use gpui::{
use rust_i18n::t; use rust_i18n::t;
use crate::{ use crate::{
button::Button, button::{Button, ButtonStyled as _},
dock::DockItemInfo, dock::DockItemInfo,
h_flex, h_flex,
popup_menu::{PopupMenu, PopupMenuExt}, popup_menu::{PopupMenu, PopupMenuExt},

View file

@ -8,8 +8,13 @@ use gpui::{
}; };
use crate::{ use crate::{
button::Button, h_flex, modal::overlay_color, root::ContextModal as _, scroll::ScrollbarAxis, button::{Button, ButtonStyled as _},
theme::ActiveTheme, v_flex, IconName, Placement, Sizable, StyledExt as _, h_flex,
modal::overlay_color,
root::ContextModal as _,
scroll::ScrollbarAxis,
theme::ActiveTheme,
v_flex, IconName, Placement, Sizable, StyledExt as _,
}; };
#[derive(IntoElement)] #[derive(IntoElement)]

View file

@ -1,6 +1,9 @@
use gpui::{px, WindowContext}; use gpui::{px, WindowContext};
use crate::{button::Button, IconName, Sizable as _}; use crate::{
button::{Button, ButtonStyled as _},
IconName, Sizable as _,
};
pub(crate) struct ClearButton {} pub(crate) struct ClearButton {}

View file

@ -8,8 +8,10 @@ use gpui::{
}; };
use crate::{ use crate::{
animation::cubic_bezier, button::Button, theme::ActiveTheme as _, v_flex, ContextModal, animation::cubic_bezier,
IconName, Sizable as _, button::{Button, ButtonStyled as _},
theme::ActiveTheme as _,
v_flex, ContextModal, IconName, Sizable as _,
}; };
actions!(modal, [Escape]); actions!(modal, [Escape]);

View file

@ -8,8 +8,11 @@ use gpui::{
use smol::Timer; use smol::Timer;
use crate::{ use crate::{
animation::cubic_bezier, button::Button, h_flex, theme::ActiveTheme as _, v_flex, Icon, animation::cubic_bezier,
IconName, Sizable as _, StyledExt, button::{Button, ButtonStyled as _},
h_flex,
theme::ActiveTheme as _,
v_flex, Icon, IconName, Sizable as _, StyledExt,
}; };
pub enum NotificationType { pub enum NotificationType {

View file

@ -182,8 +182,8 @@ impl Colors {
popover: hsl(0.0, 0.0, 100.0), popover: hsl(0.0, 0.0, 100.0),
popover_foreground: hsl(240.0, 10.0, 3.9), popover_foreground: hsl(240.0, 10.0, 3.9),
primary: hsl(223.0, 5.9, 10.0), primary: hsl(223.0, 5.9, 10.0),
primary_hover: hsl(223.0, 5.9, 30.0), primary_hover: hsl(223.0, 5.9, 15.0),
primary_active: hsl(223.0, 5.9, 45.0), primary_active: hsl(223.0, 1.9, 25.0),
primary_foreground: hsl(223.0, 0.0, 98.0), primary_foreground: hsl(223.0, 0.0, 98.0),
secondary: hsl(240.0, 4.8, 95.9), secondary: hsl(240.0, 4.8, 95.9),
secondary_hover: hsl(240.0, 5.8, 10.).opacity(0.05), secondary_hover: hsl(240.0, 5.8, 10.).opacity(0.05),
@ -224,8 +224,8 @@ impl Colors {
popover: hsl(240.0, 10.0, 3.9), popover: hsl(240.0, 10.0, 3.9),
popover_foreground: hsl(0.0, 0.0, 98.0), popover_foreground: hsl(0.0, 0.0, 98.0),
primary: hsl(223.0, 0.0, 98.0), primary: hsl(223.0, 0.0, 98.0),
primary_hover: hsl(223.0, 0.0, 85.0), primary_hover: hsl(223.0, 0.0, 90.0),
primary_active: hsl(223.0, 0.0, 60.0), primary_active: hsl(223.0, 0.0, 80.0),
primary_foreground: hsl(223.0, 5.9, 10.0), primary_foreground: hsl(223.0, 5.9, 10.0),
secondary: hsl(240.0, 3.7, 15.9), secondary: hsl(240.0, 3.7, 15.9),
secondary_hover: hsl(240.0, 3.7, 20.9).opacity(0.5), secondary_hover: hsl(240.0, 3.7, 20.9).opacity(0.5),

View file

@ -9,7 +9,10 @@ use gpui::{
use rust_i18n::t; use rust_i18n::t;
use crate::{ use crate::{
button::Button, h_flex, theme::ActiveTheme, v_flex, Disableable as _, IconName, Selectable, button::{Button, ButtonStyled as _},
h_flex,
theme::ActiveTheme,
v_flex, Disableable as _, IconName, Selectable,
}; };
use super::utils::days_in_month; use super::utils::days_in_month;
@ -593,7 +596,8 @@ impl Calendar {
let active = (ix + 1) as u8 == self.current_month; let active = (ix + 1) as u8 == self.current_month;
self.item_button(ix, month.to_string(), active, false, false, cx) self.item_button(ix, month.to_string(), active, false, false, cx)
.w(relative(0.3)).text_sm() .w(relative(0.3))
.text_sm()
.on_click(cx.listener(move |view, _, cx| { .on_click(cx.listener(move |view, _, cx| {
view.current_month = (ix + 1) as u8; view.current_month = (ix + 1) as u8;
view.set_view_mode(ViewMode::Day, cx); view.set_view_mode(ViewMode::Day, cx);