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,12 +1,10 @@
|
|||
mod avatar;
|
||||
mod button;
|
||||
mod button2;
|
||||
mod checkbox;
|
||||
mod context_menu;
|
||||
mod disclosure;
|
||||
mod divider;
|
||||
mod icon;
|
||||
mod icon_button;
|
||||
mod keybinding;
|
||||
mod label;
|
||||
mod list;
|
||||
|
@ -19,13 +17,11 @@ mod stories;
|
|||
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use button2::*;
|
||||
pub use checkbox::*;
|
||||
pub use context_menu::*;
|
||||
pub use disclosure::*;
|
||||
pub use divider::*;
|
||||
pub use icon::*;
|
||||
pub use icon_button::*;
|
||||
pub use keybinding::*;
|
||||
pub use label::*;
|
||||
pub use list::*;
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
use gpui::{
|
||||
ClickEvent, DefiniteLength, Div, Hsla, IntoElement, StatefulInteractiveElement, WindowContext,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, Color, Icon, IconButton, IconElement, Label, LineHeightStyle};
|
||||
|
||||
/// Provides the flexibility to use either a standard
|
||||
/// button or an icon button in a given context.
|
||||
pub enum ButtonOrIconButton {
|
||||
Button(Button),
|
||||
IconButton(IconButton),
|
||||
}
|
||||
|
||||
impl From<Button> for ButtonOrIconButton {
|
||||
fn from(value: Button) -> Self {
|
||||
Self::Button(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IconButton> for ButtonOrIconButton {
|
||||
fn from(value: IconButton) -> Self {
|
||||
Self::IconButton(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum IconPosition {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub enum ButtonVariant {
|
||||
#[default]
|
||||
Ghost,
|
||||
Filled,
|
||||
}
|
||||
|
||||
impl ButtonVariant {
|
||||
pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_background,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_background,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_hover,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla {
|
||||
match self {
|
||||
ButtonVariant::Ghost => cx.theme().colors().ghost_element_active,
|
||||
ButtonVariant::Filled => cx.theme().colors().element_active,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Button {
|
||||
disabled: bool,
|
||||
click_handler: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
icon: Option<Icon>,
|
||||
icon_position: Option<IconPosition>,
|
||||
label: SharedString,
|
||||
variant: ButtonVariant,
|
||||
width: Option<DefiniteLength>,
|
||||
color: Option<Color>,
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
type Rendered = gpui::Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let (icon_color, label_color) = match (self.disabled, self.color) {
|
||||
(true, _) => (Color::Disabled, Color::Disabled),
|
||||
(_, None) => (Color::Default, Color::Default),
|
||||
(_, Some(color)) => (Color::from(color), color),
|
||||
};
|
||||
|
||||
let mut button = h_stack()
|
||||
.id(SharedString::from(format!("{}", self.label)))
|
||||
.relative()
|
||||
.p_1()
|
||||
.text_ui()
|
||||
.rounded_md()
|
||||
.bg(self.variant.bg_color(cx))
|
||||
.cursor_pointer()
|
||||
.hover(|style| style.bg(self.variant.bg_color_hover(cx)))
|
||||
.active(|style| style.bg(self.variant.bg_color_active(cx)));
|
||||
|
||||
match (self.icon, self.icon_position) {
|
||||
(Some(_), Some(IconPosition::Left)) => {
|
||||
button = button
|
||||
.gap_1()
|
||||
.child(self.render_label(label_color))
|
||||
.children(self.render_icon(icon_color))
|
||||
}
|
||||
(Some(_), Some(IconPosition::Right)) => {
|
||||
button = button
|
||||
.gap_1()
|
||||
.children(self.render_icon(icon_color))
|
||||
.child(self.render_label(label_color))
|
||||
}
|
||||
(_, _) => button = button.child(self.render_label(label_color)),
|
||||
}
|
||||
|
||||
if let Some(width) = self.width {
|
||||
button = button.w(width).justify_center();
|
||||
}
|
||||
|
||||
if let Some(click_handler) = self.click_handler.clone() {
|
||||
button = button.on_click(move |event, cx| {
|
||||
click_handler(event, cx);
|
||||
});
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
disabled: false,
|
||||
click_handler: None,
|
||||
icon: None,
|
||||
icon_position: None,
|
||||
label: label.into(),
|
||||
variant: Default::default(),
|
||||
width: Default::default(),
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ghost(label: impl Into<SharedString>) -> Self {
|
||||
Self::new(label).variant(ButtonVariant::Ghost)
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
|
||||
if self.icon.is_none() {
|
||||
panic!("An icon must be present if an icon_position is provided.");
|
||||
}
|
||||
self.icon_position = Some(icon_position);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.click_handler = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Option<Color>) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_color(&self, color: Option<Color>) -> Color {
|
||||
if self.disabled {
|
||||
Color::Disabled
|
||||
} else if let Some(color) = color {
|
||||
color
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_label(&self, color: Color) -> Label {
|
||||
Label::new(self.label.clone())
|
||||
.color(color)
|
||||
.line_height_style(LineHeightStyle::UILabel)
|
||||
}
|
||||
|
||||
fn render_icon(&self, icon_color: Color) -> Option<IconElement> {
|
||||
self.icon.map(|i| IconElement::new(i).color(icon_color))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ButtonGroup {
|
||||
buttons: Vec<Button>,
|
||||
}
|
||||
|
||||
impl RenderOnce for ButtonGroup {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let mut group = h_stack();
|
||||
|
||||
for button in self.buttons.into_iter() {
|
||||
group = group.child(button.render(cx));
|
||||
}
|
||||
|
||||
group
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonGroup {
|
||||
pub fn new(buttons: Vec<Button>) -> Self {
|
||||
Self { buttons }
|
||||
}
|
||||
}
|
91
crates/ui2/src/components/button/button.rs
Normal file
91
crates/ui2/src/components/button/button.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use gpui::AnyView;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Button {
|
||||
base: ButtonLike,
|
||||
label: SharedString,
|
||||
label_color: Option<Color>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
base: ButtonLike::new(id),
|
||||
label: label.into(),
|
||||
label_color: None,
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
|
||||
self.label_color = label_color.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clickable for Button {
|
||||
fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.base = self.base.on_click(handler);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for Button {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.base = self.base.disabled(disabled);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonCommon for Button {
|
||||
fn id(&self) -> &ElementId {
|
||||
self.base.id()
|
||||
}
|
||||
|
||||
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||
self.base = self.base.style(style);
|
||||
self
|
||||
}
|
||||
|
||||
fn size(mut self, size: ButtonSize2) -> Self {
|
||||
self.base = self.base.size(size);
|
||||
self
|
||||
}
|
||||
|
||||
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.base = self.base.tooltip(tooltip);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
type Rendered = ButtonLike;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
let label_color = if self.base.disabled {
|
||||
Color::Disabled
|
||||
} else if self.selected {
|
||||
Color::Selected
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
|
||||
self.base.child(
|
||||
Label::new(self.label)
|
||||
.color(label_color)
|
||||
.line_height_style(LineHeightStyle::UILabel),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,31 +1,17 @@
|
|||
use gpui::{
|
||||
rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
|
||||
StatefulInteractiveElement, WindowContext,
|
||||
};
|
||||
use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{h_stack, prelude::*};
|
||||
use crate::h_stack;
|
||||
use crate::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,
|
||||
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;
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||
pub enum ButtonStyle2 {
|
||||
#[default]
|
||||
Filled,
|
||||
|
@ -34,7 +20,7 @@ pub enum ButtonStyle2 {
|
|||
Transparent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ButtonStyle {
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
|
@ -181,82 +167,11 @@ impl ButtonSize2 {
|
|||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
pub(super) style: ButtonStyle2,
|
||||
pub(super) disabled: bool,
|
||||
size: ButtonSize2,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
|
@ -325,6 +240,12 @@ impl ButtonCommon for ButtonLike {
|
|||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ButtonLike {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ButtonLike {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
|
@ -349,57 +270,3 @@ impl RenderOnce for ButtonLike {
|
|||
.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...
|
||||
// }
|
102
crates/ui2/src/components/button/icon_button.rs
Normal file
102
crates/ui2/src/components/button/icon_button.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use gpui::{Action, AnyView};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct IconButton {
|
||||
base: ButtonLike,
|
||||
icon: Icon,
|
||||
icon_size: IconSize,
|
||||
icon_color: Color,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl IconButton {
|
||||
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
|
||||
Self {
|
||||
base: ButtonLike::new(id),
|
||||
icon,
|
||||
icon_size: IconSize::default(),
|
||||
icon_color: Color::Default,
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon_size(mut self, icon_size: IconSize) -> Self {
|
||||
self.icon_size = icon_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon_color(mut self, icon_color: Color) -> Self {
|
||||
self.icon_color = icon_color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(self, action: Box<dyn Action>) -> Self {
|
||||
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clickable for IconButton {
|
||||
fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.base = self.base.on_click(handler);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for IconButton {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.base = self.base.disabled(disabled);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonCommon for IconButton {
|
||||
fn id(&self) -> &ElementId {
|
||||
self.base.id()
|
||||
}
|
||||
|
||||
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||
self.base = self.base.style(style);
|
||||
self
|
||||
}
|
||||
|
||||
fn size(mut self, size: ButtonSize2) -> Self {
|
||||
self.base = self.base.size(size);
|
||||
self
|
||||
}
|
||||
|
||||
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.base = self.base.tooltip(tooltip);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for IconButton {
|
||||
type Rendered = ButtonLike;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
let icon_color = if self.base.disabled {
|
||||
Color::Disabled
|
||||
} else if self.selected {
|
||||
Color::Selected
|
||||
} else {
|
||||
self.icon_color
|
||||
};
|
||||
|
||||
self.base.child(
|
||||
IconElement::new(self.icon)
|
||||
.size(self.icon_size)
|
||||
.color(icon_color),
|
||||
)
|
||||
}
|
||||
}
|
7
crates/ui2/src/components/button/mod.rs
Normal file
7
crates/ui2/src/components/button/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod button;
|
||||
mod button_like;
|
||||
mod icon_button;
|
||||
|
||||
pub use button::*;
|
||||
pub use button_like::*;
|
||||
pub use icon_button::*;
|
|
@ -39,8 +39,8 @@ impl RenderOnce for Disclosure {
|
|||
false => Icon::ChevronRight,
|
||||
},
|
||||
)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.when_some(self.on_toggle, move |this, on_toggle| {
|
||||
this.on_click(move |event, cx| on_toggle(event, cx))
|
||||
})
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
|
||||
use gpui::{prelude::*, Action, AnyView, ClickEvent, Div, Stateful};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct IconButton {
|
||||
id: ElementId,
|
||||
icon: Icon,
|
||||
color: Color,
|
||||
size: IconSize,
|
||||
variant: ButtonVariant,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
}
|
||||
|
||||
impl RenderOnce for IconButton {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let icon_color = match (self.disabled, self.selected, self.color) {
|
||||
(true, _, _) => Color::Disabled,
|
||||
(false, true, _) => Color::Selected,
|
||||
_ => self.color,
|
||||
};
|
||||
|
||||
let (mut bg_color, bg_active_color) = match self.variant {
|
||||
ButtonVariant::Filled => (
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
ButtonVariant::Ghost => (
|
||||
cx.theme().colors().ghost_element_background,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
};
|
||||
|
||||
if self.selected {
|
||||
bg_color = cx.theme().colors().element_selected;
|
||||
}
|
||||
|
||||
let mut button = h_stack()
|
||||
.id(self.id.clone())
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.p_1()
|
||||
.bg(bg_color)
|
||||
.cursor_pointer()
|
||||
// Nate: Trying to figure out the right places we want to show a
|
||||
// hover state here. I think it is a bit heavy to have it on every
|
||||
// place we use an icon button.
|
||||
// .hover(|style| style.bg(bg_hover_color))
|
||||
.active(|style| style.bg(bg_active_color))
|
||||
.child(
|
||||
IconElement::new(self.icon)
|
||||
.size(self.size)
|
||||
.color(icon_color),
|
||||
);
|
||||
|
||||
if let Some(click_handler) = self.on_click {
|
||||
button = button.on_click(move |event, cx| {
|
||||
cx.stop_propagation();
|
||||
click_handler(event, cx);
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(tooltip) = self.tooltip {
|
||||
if !self.selected {
|
||||
button = button.tooltip(move |cx| tooltip(cx))
|
||||
}
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
}
|
||||
|
||||
impl IconButton {
|
||||
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
icon,
|
||||
color: Color::default(),
|
||||
size: Default::default(),
|
||||
variant: ButtonVariant::default(),
|
||||
selected: false,
|
||||
disabled: false,
|
||||
tooltip: None,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = icon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: IconSize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: ButtonVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl 'static + Fn(&ClickEvent, &mut WindowContext)) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn action(self, action: Box<dyn Action>) -> Self {
|
||||
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ impl RenderOnce for ListHeader {
|
|||
h_stack()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
|
||||
.children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
|
||||
),
|
||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||
|
|
|
@ -2,7 +2,7 @@ use gpui::{Div, Render};
|
|||
use story::Story;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, Button, Icon, IconPosition};
|
||||
use crate::{Button, ButtonStyle2};
|
||||
|
||||
pub struct ButtonStory;
|
||||
|
||||
|
@ -12,66 +12,11 @@ impl Render for ButtonStory {
|
|||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Button>())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_8()
|
||||
.child(
|
||||
div().child(Story::label("Ghost (Default)")).child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.child(Button::new("Label").variant(ButtonVariant::Ghost)),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Ghost – Left Icon"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Ghost – Right Icon"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().child(Story::label("Filled")).child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.child(Button::new("Label").variant(ButtonVariant::Filled)),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Filled – Left Button"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Filled – Right Button"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Button with `on_click`"))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(|_, _cx| println!("Button clicked.")),
|
||||
)
|
||||
.child(Story::label("Default"))
|
||||
.child(Button::new("default_filled", "Click me"))
|
||||
.child(Story::label("Default (Subtle)"))
|
||||
.child(Button::new("default_subtle", "Click me").style(ButtonStyle2::Subtle))
|
||||
.child(Story::label("Default (Transparent)"))
|
||||
.child(Button::new("default_transparent", "Click me").style(ButtonStyle2::Transparent))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,5 @@ pub use crate::clickable::*;
|
|||
pub use crate::disableable::*;
|
||||
pub use crate::fixed::*;
|
||||
pub use crate::selectable::*;
|
||||
pub use crate::StyledExt;
|
||||
pub use crate::{ButtonVariant, Color};
|
||||
pub use crate::{ButtonCommon, Color, StyledExt};
|
||||
pub use theme::ActiveTheme;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue