Add Link style Button (#97)

<img width="1305" alt="image"
src="https://github.com/user-attachments/assets/ee42d86f-e237-4544-aa2d-1414dabfb55e">

- Update `primary`, `danger`, `outline`, `ghost`, `link` methods to as a
builder method to just change style.
- Add `compact` used to reduce padding.

<img width="1279" alt="image"
src="https://github.com/user-attachments/assets/ccbd71c0-9157-4ef8-b164-b99ade24cf92">
This commit is contained in:
Jason Lee 2024-08-01 19:54:35 +08:00 committed by GitHub
parent 4a5b387b40
commit e9d409a4b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 490 additions and 178 deletions

1
assets/icons/menu.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>

After

Width:  |  Height:  |  Size: 327 B

View file

@ -5,18 +5,29 @@ use gpui::{
use ui::{
button::{Button, ButtonCustomStyle, ButtonStyle},
checkbox::Checkbox,
h_flex,
theme::ActiveTheme,
v_flex, Clickable, Disableable as _, Icon, IconName, Selectable, Size,
v_flex, Clickable, Disableable as _, Icon, IconName, Selectable as _, Size,
};
use crate::section;
pub struct ButtonStory {}
pub struct ButtonStory {
disabled: bool,
loading: bool,
selected: bool,
compact: bool,
}
impl ButtonStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|_| Self {})
cx.new_view(|_| Self {
disabled: false,
loading: false,
selected: false,
compact: false,
})
}
fn on_click(ev: &ClickEvent, _: &mut WindowContext) {
@ -26,8 +37,54 @@ impl ButtonStory {
impl Render for ButtonStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let disabled = self.disabled;
let loading = self.loading;
let selected = self.selected;
let compact = self.compact;
v_flex()
.gap_6()
.child(
h_flex()
.gap_3()
.child("State")
.child(
Checkbox::new("disabled-button")
.label("Disabled")
.checked(self.disabled)
.on_click(cx.listener(|view, _, cx| {
view.disabled = !view.disabled;
cx.notify();
})),
)
.child(
Checkbox::new("loading-button")
.label("Loading")
.checked(self.loading)
.on_click(cx.listener(|view, _, cx| {
view.loading = !view.loading;
cx.notify();
})),
)
.child(
Checkbox::new("selected-button")
.label("Selected")
.checked(self.selected)
.on_click(cx.listener(|view, _, cx| {
view.selected = !view.selected;
cx.notify();
})),
)
.child(
Checkbox::new("compact-button")
.label("Compact")
.checked(self.compact)
.on_click(cx.listener(|view, _, cx| {
view.compact = !view.compact;
cx.notify();
})),
),
)
.child(
h_flex()
.gap_6()
@ -35,45 +92,78 @@ impl Render for ButtonStory {
section("Normal Button", cx)
.child(
Button::new("button-1", cx)
.primary()
.label("Primary Button")
.style(ButtonStyle::Primary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-2", cx)
.label("Secondary Button")
.style(ButtonStyle::Secondary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-4", cx)
.danger()
.label("Danger Button")
.style(ButtonStyle::Danger)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-5", cx)
.outline()
.label("Outline Button")
.style(ButtonStyle::Outline)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-5-ghost", cx)
.ghost()
.label("Ghost Button")
.style(ButtonStyle::Ghost)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-5-link", cx)
.link()
.label("Link Button")
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-6-custom", cx)
.label("Custom Button")
.style(ButtonStyle::Custom(
.custom(
ButtonCustomStyle::new(cx)
.color(cx.theme().muted)
.foreground(cx.theme().destructive)
.border(cx.theme().scrollbar)
.hover(cx.theme().tab_active_foreground)
.active(cx.theme().selection),
))
)
.label("Custom Button")
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
),
)
@ -81,23 +171,33 @@ impl Render for ButtonStory {
section("Button with Icon", cx)
.child(
Button::new("button-icon-1", cx)
.primary()
.label("Confirm")
.icon(IconName::Check)
.style(ButtonStyle::Primary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-icon-2", cx)
.label("Abort")
.icon(IconName::Close)
.style(ButtonStyle::Secondary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-icon-3", cx)
.label("Maximize")
.icon(Icon::new(IconName::Maximize))
.style(ButtonStyle::Secondary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
@ -111,6 +211,32 @@ impl Render for ButtonStory {
.child(IconName::ChevronDown)
.child(IconName::Eye),
)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-icon-5-ghost", cx)
.style(ButtonStyle::Ghost)
.icon(IconName::Check)
.label("Confirm")
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-icon-6-link", cx)
.style(ButtonStyle::Link)
.icon(IconName::Check)
.label("Link")
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
),
),
@ -126,6 +252,10 @@ impl Render for ButtonStory {
.style(ButtonStyle::Primary)
.size(Size::Small)
.loading(true)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
@ -133,6 +263,10 @@ impl Render for ButtonStory {
.label("Secondary Button")
.style(ButtonStyle::Secondary)
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
@ -140,6 +274,43 @@ impl Render for ButtonStory {
.label("Danger Button")
.style(ButtonStyle::Danger)
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-8-outline", cx)
.label("Outline Button")
.style(ButtonStyle::Outline)
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-8-ghost", cx)
.label("Ghost Button")
.style(ButtonStyle::Ghost)
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-8-link", cx)
.label("Link Button")
.style(ButtonStyle::Link)
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
),
)
@ -150,6 +321,10 @@ impl Render for ButtonStory {
.label("Primary Button")
.style(ButtonStyle::Primary)
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
@ -158,6 +333,10 @@ impl Render for ButtonStory {
.style(ButtonStyle::Secondary)
.size(Size::XSmall)
.loading(true)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
@ -165,92 +344,103 @@ impl Render for ButtonStory {
.label("Danger Button")
.style(ButtonStyle::Danger)
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
),
),
)
.child(
h_flex()
.gap_6()
.child(
section("Disabled Button", cx)
.child(
Button::new("button-disabled1", cx)
.label("Disabled Button")
.style(ButtonStyle::Primary)
.on_click(Self::on_click)
.disabled(true),
)
.child(
Button::new("button-disabled1", cx)
.label("Disabled Button")
.style(ButtonStyle::Secondary)
.on_click(Self::on_click)
.disabled(true),
Button::new("button-xs-3-ghost", cx)
.label("Ghost Button")
.style(ButtonStyle::Ghost)
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-disabled1", cx)
.label("Disabled Button")
.style(ButtonStyle::Danger)
.on_click(Self::on_click)
.disabled(true)
.loading(true),
),
)
.child(
section("Selected Style", cx)
.child(
Button::new("button-selected-1", cx)
.label("Selected Button")
.style(ButtonStyle::Primary)
.selected(true),
Button::new("button-xs-3-outline", cx)
.label("Outline Button")
.style(ButtonStyle::Outline)
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
)
.child(
Button::new("button-selected-2", cx)
.label("Selected Button")
.style(ButtonStyle::Secondary)
.selected(true),
)
.child(
Button::new("button-selected-3", cx)
.label("Selected Button")
.style(ButtonStyle::Danger)
.selected(true),
Button::new("button-xs-3-link", cx)
.label("Link Button")
.style(ButtonStyle::Link)
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact)
.on_click(Self::on_click),
),
),
)
.child(
section("Icon Button", cx)
.child(
Button::new("icon-button-0", cx)
Button::new("icon-button-primary", cx)
.icon(IconName::Search)
.style(ButtonStyle::Primary),
.style(ButtonStyle::Primary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-1", cx)
Button::new("icon-button-secondary", cx)
.icon(IconName::Info)
.loading(true),
.loading(true)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-2", cx)
Button::new("icon-button-danger", cx)
.icon(IconName::Close)
.style(ButtonStyle::Danger),
.style(ButtonStyle::Danger)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-3", cx)
Button::new("icon-button-small-primary", cx)
.icon(IconName::Search)
.size(Size::Small)
.style(ButtonStyle::Primary),
.style(ButtonStyle::Primary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-0-outline", cx)
Button::new("icon-button-outline", cx)
.icon(IconName::Search)
.style(ButtonStyle::Outline),
.style(ButtonStyle::Outline)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-1", cx)
.icon(IconName::Info)
.style(ButtonStyle::Ghost),
Button::new("icon-button-ghost", cx)
.icon(IconName::ArrowLeft)
.style(ButtonStyle::Ghost)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
),
)
.child(
@ -258,36 +448,60 @@ impl Render for ButtonStory {
.child(
Button::new("icon-button-4", cx)
.icon(IconName::Info)
.size(Size::Small),
.size(Size::Small)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-5", cx)
.icon(IconName::Close)
.size(Size::Small)
.style(ButtonStyle::Danger),
.style(ButtonStyle::Danger)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-6", cx)
.icon(IconName::Search)
.size(Size::XSmall)
.style(ButtonStyle::Primary),
.style(ButtonStyle::Primary)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-7", cx)
.icon(IconName::Info)
.size(Size::XSmall),
.size(Size::XSmall)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-8", cx)
.icon(IconName::Close)
.size(Size::XSmall)
.style(ButtonStyle::Danger),
.style(ButtonStyle::Danger)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
)
.child(
Button::new("icon-button-9", cx)
.icon(IconName::Heart)
.size(px(24.))
.style(ButtonStyle::Ghost),
.style(ButtonStyle::Ghost)
.disabled(disabled)
.selected(selected)
.loading(loading)
.compact(compact),
),
)
}

View file

@ -12,7 +12,7 @@ use ui::{
popup_menu::PopupMenu,
prelude::FluentBuilder,
switch::Switch,
v_flex, Clickable, IconName, Size,
v_flex, Clickable as _, IconName, Size,
};
actions!(
@ -49,7 +49,8 @@ impl Render for Form {
.child("This is a form container.")
.child(self.input1.clone())
.child(
Button::primary("submit", "Submit", cx)
Button::new("submit", cx)
.primary()
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
)
}
@ -143,7 +144,7 @@ impl Render for PopoverStory {
.child(
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.w(px(80.))
.size(Size::Small),
)
.into_any()
@ -166,7 +167,7 @@ impl Render for PopoverStory {
.child(
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.w(px(80.))
.size(Size::Small),
)
.into_any()
@ -180,7 +181,7 @@ impl Render for PopoverStory {
.child(
Popover::new("popup-menu")
.when(window_mode, |this| this.window_mode())
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Info))
.trigger(Button::new("popup-menu-1", cx).icon(IconName::Ellipsis))
.content(move |cx| {
let focus_handle = focus_handle.clone();
PopupMenu::build(cx, |mut this, _cx| {
@ -213,9 +214,7 @@ impl Render for PopoverStory {
.when(self.window_mode, |this| this.window_mode())
.anchor(AnchorCorner::BottomLeft)
.trigger(
Button::new("pop", cx)
.label("Popup with Form")
.width(px(300.)),
Button::new("pop", cx).label("Popup with Form").w(px(300.)),
)
.content(move |_| form.clone()),
)
@ -227,7 +226,7 @@ impl Render for PopoverStory {
.trigger(
Button::new("pop", cx)
.label("Mouse Right Click")
.width(px(300.)),
.w(px(300.)),
)
.content(|cx| {
PopoverContent::new(cx, |cx| {
@ -238,7 +237,7 @@ impl Render for PopoverStory {
.child(
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.w(px(80.))
.size(Size::Small),
)
.into_any()

View file

@ -5,9 +5,9 @@ use crate::{
Clickable, Disableable, Icon, Selectable, Size,
};
use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, ClickEvent, DefiniteLength, Div, ElementId,
FocusHandle, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext,
div, prelude::FluentBuilder as _, px, AnyElement, ClickEvent, Div, ElementId, FocusHandle,
Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce,
SharedString, StatefulInteractiveElement as _, Styled, WindowContext,
};
pub enum ButtonRounded {
@ -24,7 +24,7 @@ impl From<Pixels> for ButtonRounded {
}
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ButtonCustomStyle {
color: Hsla,
foreground: Hsla,
@ -70,16 +70,23 @@ impl ButtonCustomStyle {
}
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ButtonStyle {
Primary,
Secondary,
Danger,
Outline,
Ghost,
Link,
Custom(ButtonCustomStyle),
}
impl ButtonStyle {
fn is_link(&self) -> bool {
matches!(self, Self::Link)
}
}
#[derive(IntoElement)]
pub struct Button {
pub base: Div,
@ -90,11 +97,10 @@ pub struct Button {
children: Vec<AnyElement>,
disabled: bool,
selected: bool,
width: Option<DefiniteLength>,
height: Option<DefiniteLength>,
style: ButtonStyle,
rounded: ButtonRounded,
size: Size,
compact: bool,
tooltip: Option<SharedString>,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
loading: bool,
@ -111,61 +117,65 @@ impl Button {
disabled: false,
selected: false,
style: ButtonStyle::Secondary,
width: None,
height: None,
rounded: ButtonRounded::Medium,
size: Size::Medium,
tooltip: None,
on_click: None,
loading: false,
compact: false,
children: Vec::new(),
}
}
pub fn primary(
id: impl Into<ElementId>,
label: impl Into<SharedString>,
cx: &mut WindowContext,
) -> Self {
Self::new(id, cx).label(label).style(ButtonStyle::Primary)
}
pub fn danger(
id: impl Into<ElementId>,
label: impl Into<SharedString>,
cx: &mut WindowContext,
) -> Self {
Self::new(id, cx).label(label).style(ButtonStyle::Danger)
}
pub fn small(
id: impl Into<ElementId>,
label: impl Into<SharedString>,
cx: &mut WindowContext,
) -> Self {
Self::new(id, cx).label(label).size(Size::Small)
}
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.width = Some(width.into());
/// With the primary style for the Button.
pub fn primary(mut self) -> Self {
self.style = ButtonStyle::Primary;
self
}
pub fn height(mut self, height: impl Into<DefiniteLength>) -> Self {
self.height = Some(height.into());
/// 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 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.
pub fn rounded(mut self, rounded: impl Into<ButtonRounded>) -> Self {
self.rounded = rounded.into();
self
}
/// Set the ui::Size of the Button.
pub fn size(mut self, size: impl Into<Size>) -> Self {
self.size = size.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<SharedString>) -> Self {
self.label = Some(label.into());
self
@ -177,20 +187,29 @@ impl Button {
self
}
/// Set the tooltip of the button.
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
/// Set the ButtonStyle
pub fn style(mut self, style: ButtonStyle) -> Self {
self.style = style;
self
}
/// Set true to show the loading indicator.
pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
}
/// Set the button to compact mode, then padding will be reduced.
pub fn compact(mut self, compact: bool) -> Self {
self.compact = compact;
self
}
}
impl Disableable for Button {
@ -243,9 +262,7 @@ impl RenderOnce for Button {
.items_center()
.justify_center()
.cursor_pointer()
.when_some(self.width, |this, width| this.w(width))
.when_some(self.height, |this, height| this.h(height))
.map(|this| {
.when(!style.is_link(), |this| {
if self.label.is_none() && self.children.is_empty() {
// Icon Button
match self.size {
@ -258,9 +275,17 @@ impl RenderOnce for Button {
// Normal Button
match self.size {
Size::Size(size) => this.p(size * 0.2),
Size::XSmall => this.px_1().py_1().h_5(),
Size::Small => this.px_3().py_2().h_6(),
_ => this.px_4().py_2().h_8(),
Size::XSmall => this.h_5().p_1(),
Size::Small => this
.px_3()
.py_2()
.h_6()
.when(self.compact, |this| this.p_2()),
_ => this
.px_4()
.py_2()
.h_8()
.when(self.compact, |this| this.p_2()),
}
}
})
@ -271,22 +296,29 @@ impl RenderOnce for Button {
ButtonRounded::Size(px) => this.rounded(px),
ButtonRounded::None => this.rounded_none(),
})
.text_color(normal_style.fg)
.when(self.selected, |this| {
let selected_style = style.selected(cx);
this.bg(selected_style.bg)
.border_color(selected_style.border)
.text_color(selected_style.fg)
})
.when(!self.disabled && !self.selected, |this| {
this.hover(|this| {
let hover_style = style.hovered(cx);
this.bg(hover_style.bg).border_color(hover_style.border)
})
.active(|this| {
let active_style = style.active(cx);
this.bg(active_style.bg).border_color(active_style.border)
})
.border_color(normal_style.border)
.bg(normal_style.bg)
this.border_color(normal_style.border)
.bg(normal_style.bg)
.when(normal_style.underline, |this| this.text_decoration_1())
.hover(|this| {
let hover_style = style.hovered(cx);
this.bg(hover_style.bg)
.border_color(hover_style.border)
.text_color(crate::red_400())
})
.active(|this| {
let active_style = style.active(cx);
this.bg(active_style.bg)
.border_color(active_style.border)
.text_color(active_style.fg)
})
})
.when(focused, |this| this.border_color(cx.theme().ring))
.when_some(
@ -305,36 +337,29 @@ impl RenderOnce for Button {
let disabled_style = style.disabled(cx);
this.cursor_not_allowed()
.bg(disabled_style.bg)
.text_color(disabled_style.fg)
.border_color(disabled_style.border)
})
.border_1()
.child({
let text_color = if self.disabled {
normal_style.fg.opacity(0.6)
} else {
normal_style.fg
};
h_flex()
.id("label")
.items_center()
.justify_center()
.gap_2()
.text_color(text_color)
.when(!self.loading, |this| {
this.when_some(self.icon, |this, icon| {
this.child(div().text_color(text_color).child(icon.size(icon_size)))
})
})
.when(self.loading, |this| {
this.child(Indicator::new().size(self.size).color(text_color))
})
.when_some(self.label, |this, label| this.child(label))
.children(self.children)
.map(|this| match self.size {
Size::XSmall => this.text_xs(),
Size::Small => this.text_sm(),
_ => this.text_base(),
})
.when(!self.loading, |this| {
this.when_some(self.icon, |this, icon| this.child(icon.size(icon_size)))
})
.when(self.loading, |this| {
this.child(Indicator::new().size(self.size))
})
.when_some(self.label, |this, label| this.child(label))
.children(self.children)
})
}
}
@ -343,6 +368,7 @@ struct ButtonStyles {
bg: Hsla,
border: Hsla,
fg: Hsla,
underline: bool,
}
impl ButtonStyle {
@ -353,6 +379,7 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive,
ButtonStyle::Outline => cx.theme().transparent,
ButtonStyle::Ghost => cx.theme().transparent,
ButtonStyle::Link => cx.theme().transparent,
ButtonStyle::Custom(colors) => colors.color,
}
}
@ -364,6 +391,7 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive_foreground,
ButtonStyle::Outline => cx.theme().secondary_foreground,
ButtonStyle::Ghost => cx.theme().secondary_foreground,
ButtonStyle::Link => cx.theme().link,
ButtonStyle::Custom(colors) => colors.foreground,
}
}
@ -375,16 +403,30 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive,
ButtonStyle::Outline => cx.theme().border,
ButtonStyle::Ghost => cx.theme().transparent,
ButtonStyle::Link => cx.theme().transparent,
ButtonStyle::Custom(colors) => colors.border,
}
}
fn underline(&self, _: &WindowContext) -> bool {
match self {
ButtonStyle::Link => true,
_ => false,
}
}
fn normal(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color(cx);
let border = self.border_color(cx);
let fg = self.text_color(cx);
let underline = self.underline(cx);
ButtonStyles { bg, border, fg }
ButtonStyles {
bg,
border,
fg,
underline,
}
}
fn hovered(&self, cx: &WindowContext) -> ButtonStyles {
@ -394,12 +436,22 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive_hover,
ButtonStyle::Outline => cx.theme().secondary_hover,
ButtonStyle::Ghost => cx.theme().secondary,
ButtonStyle::Link => cx.theme().transparent,
ButtonStyle::Custom(colors) => colors.hover,
};
let border = self.border_color(cx);
let fg = self.text_color(cx);
let fg = match self {
ButtonStyle::Link => cx.theme().link_hover,
_ => self.text_color(cx),
};
let underline = self.underline(cx);
ButtonStyles { bg, border, fg }
ButtonStyles {
bg,
border,
fg,
underline,
}
}
fn active(&self, cx: &WindowContext) -> ButtonStyles {
@ -409,12 +461,22 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive_active,
ButtonStyle::Outline => cx.theme().secondary_active,
ButtonStyle::Ghost => cx.theme().secondary_active,
ButtonStyle::Link => cx.theme().transparent,
ButtonStyle::Custom(colors) => colors.active,
};
let border = self.border_color(cx);
let fg = self.text_color(cx);
let fg = match self {
ButtonStyle::Link => cx.theme().link_active,
_ => self.text_color(cx),
};
let underline = self.underline(cx);
ButtonStyles { bg, border, fg }
ButtonStyles {
bg,
border,
fg,
underline,
}
}
fn selected(&self, cx: &WindowContext) -> ButtonStyles {
@ -424,19 +486,42 @@ impl ButtonStyle {
ButtonStyle::Danger => cx.theme().destructive_active,
ButtonStyle::Outline => cx.theme().secondary_active,
ButtonStyle::Ghost => cx.theme().secondary_active,
ButtonStyle::Link => cx.theme().transparent,
ButtonStyle::Custom(colors) => colors.active,
};
let border = self.border_color(cx);
let fg = self.text_color(cx);
let fg = match self {
ButtonStyle::Link => cx.theme().link_active,
_ => self.text_color(cx),
};
let underline = self.underline(cx);
ButtonStyles { bg, border, fg }
ButtonStyles {
bg,
border,
fg,
underline,
}
}
fn disabled(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color(cx).grayscale().opacity(0.9);
let border = self.border_color(cx).grayscale().opacity(0.9);
let fg = self.text_color(cx).grayscale();
let bg = match self {
ButtonStyle::Link => cx.theme().transparent,
_ => cx.theme().secondary.darken(0.2).grayscale(),
};
let fg = match self {
ButtonStyle::Link => cx.theme().link.grayscale(),
_ => cx.theme().secondary_foreground.darken(0.2).grayscale(),
};
ButtonStyles { bg, border, fg }
let border = bg;
let underline = self.underline(cx);
ButtonStyles {
bg,
border,
fg,
underline,
}
}
}

View file

@ -39,8 +39,8 @@ impl Checkbox {
self
}
pub fn checked(mut self, checked: Selection) -> Self {
self.checked = checked;
pub fn checked(mut self, checked: impl Into<Selection>) -> Self {
self.checked = checked.into();
self
}

View file

@ -44,6 +44,7 @@ pub enum IconName {
Sun,
ThumbsDown,
ThumbsUp,
Menu,
}
impl IconName {
@ -87,6 +88,7 @@ impl IconName {
IconName::Sun => "icons/sun.svg",
IconName::ThumbsDown => "icons/thumbs-down.svg",
IconName::ThumbsUp => "icons/thumbs-up.svg",
IconName::Menu => "icons/menu.svg",
}
.into()
}

View file

@ -1,9 +1,9 @@
use std::time::Duration;
use crate::{theme::ActiveTheme, Icon, IconName, Size};
use crate::{Icon, IconName, Size};
use gpui::{
div, ease_in_out, percentage, Animation, AnimationExt as _, Hsla, IntoElement, ParentElement,
RenderOnce, Styled as _, Transformation,
div, ease_in_out, percentage, prelude::FluentBuilder as _, Animation, AnimationExt as _, Hsla,
IntoElement, ParentElement, RenderOnce, Styled as _, Transformation,
};
#[derive(IntoElement)]
@ -41,13 +41,12 @@ impl Indicator {
}
impl RenderOnce for Indicator {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
let color = self.color.unwrap_or_else(|| cx.theme().indicator);
fn render(self, _: &mut gpui::WindowContext) -> impl IntoElement {
div()
.child(
Icon::new(self.icon.clone())
.size(self.size)
.text_color(color)
.when_some(self.color, |this, color| this.text_color(color))
.with_animation(
"circle",
Animation::new(self.speed).repeat().with_easing(ease_in_out),

View file

@ -20,6 +20,16 @@ pub enum Selection {
Selected,
}
impl From<bool> for Selection {
fn from(selected: bool) -> Self {
if selected {
Self::Selected
} else {
Self::Unselected
}
}
}
impl Display for Selection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {

View file

@ -293,7 +293,6 @@ pub struct Theme {
pub tab_active: Hsla,
pub tab_foreground: Hsla,
pub tab_active_foreground: Hsla,
pub indicator: Hsla,
pub progress_bar: Hsla,
pub slider_bar: Hsla,
pub slider_thumb: Hsla,
@ -308,6 +307,8 @@ pub struct Theme {
pub table_active: Hsla,
pub table_hover: Hsla,
pub link: Hsla,
pub link_hover: Hsla,
pub link_active: Hsla,
}
impl Global for Theme {}
@ -369,7 +370,6 @@ impl From<Colors> for Theme {
tab_active: colors.background,
tab_foreground: colors.foreground,
tab_active_foreground: colors.foreground,
indicator: colors.secondary_foreground,
progress_bar: colors.primary,
slider_bar: colors.primary,
slider_thumb: colors.background,
@ -384,6 +384,8 @@ impl From<Colors> for Theme {
table_active: colors.list_active,
table_hover: colors.list_active.opacity(0.6),
link: colors.link,
link_hover: colors.link.lighten(0.2),
link_active: colors.link.darken(0.2),
}
}
}