Add new Button
and IconButton
components (#3448)
This PR adds new `Button` and `IconButton` components built on top of our new button abstractions. Both of these buttons are built from the common `ButtonLike` base, and implement the `ButtonCommon` (name TBD) trait in order to provide a common interface. There are still some visual tweaks that we'll need to make to the new buttons, but those should be straightforward to make after we land this. Release Notes: - N/A
This commit is contained in:
parent
df5de47a78
commit
b357ae4dc3
23 changed files with 324 additions and 682 deletions
|
@ -1,405 +0,0 @@
|
|||
use gpui::{
|
||||
rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
|
||||
StatefulInteractiveElement, WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{h_stack, prelude::*};
|
||||
|
||||
// 🚧 Heavily WIP 🚧
|
||||
|
||||
// #[derive(Default, PartialEq, Clone, Copy)]
|
||||
// pub enum ButtonType2 {
|
||||
// #[default]
|
||||
// DefaultButton,
|
||||
// IconButton,
|
||||
// ButtonLike,
|
||||
// SplitButton,
|
||||
// ToggleButton,
|
||||
// }
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum IconPosition2 {
|
||||
#[default]
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum ButtonStyle2 {
|
||||
#[default]
|
||||
Filled,
|
||||
// Tinted,
|
||||
Subtle,
|
||||
Transparent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ButtonStyle {
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub label_color: Hsla,
|
||||
pub icon_color: Hsla,
|
||||
}
|
||||
|
||||
impl ButtonStyle2 {
|
||||
pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_background,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_background,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_hover,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_hover,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
// TODO: These are not great
|
||||
label_color: Color::Muted.color(cx),
|
||||
// TODO: These are not great
|
||||
icon_color: Color::Muted.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_active,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_active,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
// TODO: These are not great
|
||||
label_color: Color::Muted.color(cx),
|
||||
// TODO: These are not great
|
||||
icon_color: Color::Muted.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_background,
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_background,
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Accent.color(cx),
|
||||
icon_color: Color::Accent.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_disabled,
|
||||
border_color: cx.theme().colors().border_disabled,
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_disabled,
|
||||
border_color: cx.theme().colors().border_disabled,
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum ButtonSize2 {
|
||||
#[default]
|
||||
Default,
|
||||
Compact,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ButtonSize2 {
|
||||
fn height(self) -> Rems {
|
||||
match self {
|
||||
ButtonSize2::Default => rems(22. / 16.),
|
||||
ButtonSize2::Compact => rems(18. / 16.),
|
||||
ButtonSize2::None => rems(16. / 16.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct Button {
|
||||
// id: ElementId,
|
||||
// icon: Option<Icon>,
|
||||
// icon_color: Option<Color>,
|
||||
// icon_position: Option<IconPosition2>,
|
||||
// label: Option<Label>,
|
||||
// label_color: Option<Color>,
|
||||
// appearance: ButtonAppearance2,
|
||||
// state: InteractionState,
|
||||
// selected: bool,
|
||||
// disabled: bool,
|
||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
// width: Option<DefiniteLength>,
|
||||
// action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
// /// Used to pass down some content to the button
|
||||
// /// to enable creating custom buttons.
|
||||
// children: SmallVec<[AnyElement; 2]>,
|
||||
// }
|
||||
|
||||
pub trait ButtonCommon: Clickable + Disableable {
|
||||
fn id(&self) -> &ElementId;
|
||||
fn style(self, style: ButtonStyle2) -> Self;
|
||||
fn size(self, size: ButtonSize2) -> Self;
|
||||
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
|
||||
}
|
||||
|
||||
// pub struct LabelButton {
|
||||
// // Base properties...
|
||||
// id: ElementId,
|
||||
// appearance: ButtonAppearance,
|
||||
// state: InteractionState,
|
||||
// disabled: bool,
|
||||
// size: ButtonSize,
|
||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
// width: Option<DefiniteLength>,
|
||||
// // Button-specific properties...
|
||||
// label: Option<SharedString>,
|
||||
// label_color: Option<Color>,
|
||||
// icon: Option<Icon>,
|
||||
// icon_color: Option<Color>,
|
||||
// icon_position: Option<IconPosition>,
|
||||
// // Define more fields for additional properties as needed
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for LabelButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
|
||||
// fn appearance(&mut self, appearance: ButtonAppearance) -> &mut Self {
|
||||
// self.style= style;
|
||||
// self
|
||||
// }
|
||||
// // implement methods from ButtonCommon trait...
|
||||
// }
|
||||
|
||||
// impl LabelButton {
|
||||
// pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
// Self {
|
||||
// id: id.into(),
|
||||
// label: Some(label.into()),
|
||||
// // initialize other fields with default values...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to Button type...
|
||||
// }
|
||||
|
||||
// TODO: Icon Button
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ButtonLike {
|
||||
id: ElementId,
|
||||
style: ButtonStyle2,
|
||||
disabled: bool,
|
||||
size: ButtonSize2,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ButtonLike {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
style: ButtonStyle2::default(),
|
||||
disabled: false,
|
||||
size: ButtonSize2::Default,
|
||||
tooltip: None,
|
||||
children: SmallVec::new(),
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for ButtonLike {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
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));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// impl Selectable for ButtonLike {
|
||||
// fn selected(&mut self, selected: bool) -> &mut Self {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// fn selected_tooltip(
|
||||
// &mut self,
|
||||
// tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
|
||||
// ) -> &mut Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ButtonCommon for ButtonLike {
|
||||
fn id(&self) -> &ElementId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
fn size(mut self, size: ButtonSize2) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ButtonLike {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
h_stack()
|
||||
.id(self.id.clone())
|
||||
.h(self.size.height())
|
||||
.rounded_md()
|
||||
.cursor_pointer()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.bg(self.style.enabled(cx).background)
|
||||
.hover(|hover| hover.bg(self.style.hovered(cx).background))
|
||||
.active(|active| active.bg(self.style.active(cx).background))
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|this, on_click| this.on_click(move |event, cx| (on_click)(event, cx)),
|
||||
)
|
||||
.when_some(self.tooltip, |this, tooltip| {
|
||||
this.tooltip(move |cx| tooltip(cx))
|
||||
})
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ButtonLike {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct ToggleButton {
|
||||
// // based on either IconButton2 or Button, with additional 'selected: bool' property
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for ToggleButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
||||
// }
|
||||
|
||||
// impl ToggleButton {
|
||||
// pub fn new() -> Self {
|
||||
// // Initialize with default values
|
||||
// Self {
|
||||
// // ... initialize fields, possibly with defaults or required parameters...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to ToggleButton type...
|
||||
// }
|
||||
|
||||
// pub struct SplitButton {
|
||||
// // Base properties...
|
||||
// id: ElementId,
|
||||
// // Button-specific properties, possibly including a DefaultButton
|
||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
|
||||
// // More fields as necessary...
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for SplitButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
||||
// }
|
||||
|
||||
// impl SplitButton {
|
||||
// pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
// Self {
|
||||
// id: id.into(),
|
||||
// // ... initialize other fields with default values...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to SplitButton type...
|
||||
// }
|
Loading…
Add table
Add a link
Reference in a new issue