diff --git a/assets/icons/menu.svg b/assets/icons/menu.svg
new file mode 100644
index 00000000..6598697f
--- /dev/null
+++ b/assets/icons/menu.svg
@@ -0,0 +1 @@
+
diff --git a/crates/story/src/button_story.rs b/crates/story/src/button_story.rs
index 62173d2f..fc1784e9 100644
--- a/crates/story/src/button_story.rs
+++ b/crates/story/src/button_story.rs
@@ -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 {
- 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) -> 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),
),
)
}
diff --git a/crates/story/src/popover_story.rs b/crates/story/src/popover_story.rs
index 46ab3c73..aac40fa7 100644
--- a/crates/story/src/popover_story.rs
+++ b/crates/story/src/popover_story.rs
@@ -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()
diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs
index 5de165ff..7b75347f 100644
--- a/crates/ui/src/button.rs
+++ b/crates/ui/src/button.rs
@@ -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 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,
disabled: bool,
selected: bool,
- width: Option,
- height: Option,
style: ButtonStyle,
rounded: ButtonRounded,
size: Size,
+ compact: bool,
tooltip: Option,
on_click: Option>,
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,
- label: impl Into,
- cx: &mut WindowContext,
- ) -> Self {
- Self::new(id, cx).label(label).style(ButtonStyle::Primary)
- }
-
- pub fn danger(
- id: impl Into,
- label: impl Into,
- cx: &mut WindowContext,
- ) -> Self {
- Self::new(id, cx).label(label).style(ButtonStyle::Danger)
- }
-
- pub fn small(
- id: impl Into,
- label: impl Into,
- cx: &mut WindowContext,
- ) -> Self {
- Self::new(id, cx).label(label).size(Size::Small)
- }
-
- pub fn width(mut self, width: impl Into) -> 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) -> 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) -> Self {
self.rounded = rounded.into();
self
}
+ /// Set the ui::Size of the Button.
pub fn size(mut self, size: impl Into) -> 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) -> 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) -> 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,
+ }
}
}
diff --git a/crates/ui/src/checkbox.rs b/crates/ui/src/checkbox.rs
index 3dd0e9b1..58be8d68 100644
--- a/crates/ui/src/checkbox.rs
+++ b/crates/ui/src/checkbox.rs
@@ -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) -> Self {
+ self.checked = checked.into();
self
}
diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs
index c5f7195a..645b6699 100644
--- a/crates/ui/src/icon.rs
+++ b/crates/ui/src/icon.rs
@@ -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()
}
diff --git a/crates/ui/src/indicator.rs b/crates/ui/src/indicator.rs
index f6b95492..7b3cb3cb 100644
--- a/crates/ui/src/indicator.rs
+++ b/crates/ui/src/indicator.rs
@@ -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),
diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs
index 2ed168d7..841363c0 100644
--- a/crates/ui/src/selectable.rs
+++ b/crates/ui/src/selectable.rs
@@ -20,6 +20,16 @@ pub enum Selection {
Selected,
}
+impl From 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 {
diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs
index aa4234e7..4206ca7a 100644
--- a/crates/ui/src/theme.rs
+++ b/crates/ui/src/theme.rs
@@ -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 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 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),
}
}
}