use gpui::{Div, ElementId, SharedString, Stateful, div, prelude::*, px};
use icons::Icon;
use motion::{self, Fade};
use theme::{ControlSize, Sizing, Theme, ThemeExt};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ButtonStyle {
Ghost,
Prominent,
Destructive,
}
const GLYPH: f32 = 14.0;
const GROUP_PAD: f32 = 2.0;
fn frame() -> Div {
div()
.control_size(ControlSize::Regular)
.flex()
.items_center()
.cursor_pointer()
}
pub trait Buttons: ThemeExt {
fn button(
&self,
label: impl Into<SharedString>,
style: ButtonStyle,
fade: Option<Fade>,
) -> Div {
let (button, _) = appearance(self.theme(), frame(), style, None, fade, true);
button.child(label.into())
}
fn icon_button(&self, icon: impl Into<Icon>, style: ButtonStyle, fade: Option<Fade>) -> Div {
let square = frame()
.px(px(0.0))
.w(px(Theme::BUTTON_HEIGHT))
.justify_center();
let (button, tint) = appearance(self.theme(), square, style, None, fade, true);
button.child(crate::icons::icon(icon).size(px(GLYPH)).text_color(tint))
}
fn control_group(&self) -> Div {
let theme = self.theme();
div()
.self_start()
.flex()
.flex_row()
.gap(px(GROUP_PAD))
.p(px(GROUP_PAD))
.rounded(px(Theme::button_radius() + GROUP_PAD))
.bg(theme.surface_raised)
.border_1()
.border_color(theme.border)
}
fn ghost(&self, id: impl Into<ElementId>) -> Stateful<Div> {
let tint = self.theme().element_hover;
div()
.id(id)
.flex()
.flex_row()
.items_center()
.rounded(px(Theme::control_radius()))
.cursor_pointer()
.hover(move |el| el.bg(tint))
}
}
impl Buttons for Theme {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ButtonRole {
Cancel,
Destructive,
}
pub(super) fn appearance(
theme: &Theme,
frame: Div,
style: ButtonStyle,
role: Option<ButtonRole>,
fade: Option<Fade>,
interactive: bool,
) -> (Div, gpui::Hsla) {
let destructive = role == Some(ButtonRole::Destructive) || style == ButtonStyle::Destructive;
if style == ButtonStyle::Ghost {
let rest = if destructive {
theme.danger_strong
} else {
theme.text_muted
};
let hot = if destructive {
theme.danger_strong
} else {
theme.text
};
if let Some(fade) = fade {
let tint = motion::hover_blend(&fade, rest, hot);
let mut button = frame.text_color(tint).bg(motion::hover_blend(
&fade,
theme.ink(0.0),
theme.element_hover,
));
button
.interactivity()
.on_hover(motion::hover_listener(fade));
(button, tint)
} else {
(frame.text_color(rest), rest)
}
} else {
let (fill, tint) = if destructive {
(theme.danger_strong, gpui::white())
} else {
(theme.text, theme.on_solid)
};
(
frame
.bg(fill)
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(tint)
.when(interactive, |button| button.hover(|s| s.opacity(0.9))),
tint,
)
}
}