use gpui::prelude::FluentBuilder;
use gpui::*;
use crate::theme::*;
#[derive(Clone, Debug)]
pub enum ButtonEvent {
Click,
}
impl EventEmitter<ButtonEvent> for Button {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonVariant {
Primary,
Secondary,
Outline,
Text,
Danger,
}
pub struct Button {
label: String,
variant: ButtonVariant,
size: ComponentSize,
disabled: bool,
loading: bool,
full_width: bool,
}
impl Button {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
variant: ButtonVariant::Primary,
size: ComponentSize::Medium,
disabled: false,
loading: false,
full_width: false,
}
}
pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = variant;
self
}
pub fn size(mut self, size: ComponentSize) -> Self {
self.size = size;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
}
pub fn full_width(mut self, full_width: bool) -> Self {
self.full_width = full_width;
self
}
fn background_color(&self, theme: &Theme, is_hovered: bool, is_pressed: bool) -> Rgba {
if self.disabled {
return theme.colors.background_secondary;
}
match self.variant {
ButtonVariant::Primary => {
if is_pressed {
crate::utils::darken(theme.colors.primary, 0.1)
} else if is_hovered {
crate::utils::lighten(theme.colors.primary, 0.1)
} else {
theme.colors.primary
}
}
ButtonVariant::Danger => {
if is_pressed {
crate::utils::darken(theme.colors.error, 0.1)
} else if is_hovered {
crate::utils::lighten(theme.colors.error, 0.1)
} else {
theme.colors.error
}
}
ButtonVariant::Secondary => {
if is_pressed {
crate::utils::darken(theme.colors.background_hover, 0.05)
} else if is_hovered {
theme.colors.background_hover
} else {
theme.colors.background
}
}
ButtonVariant::Outline => {
if is_pressed || is_hovered {
rgba(0x00000005)
} else {
rgba(0x00000000) }
}
ButtonVariant::Text => {
if is_pressed || is_hovered {
rgba(0x00000008)
} else {
rgba(0x00000000) }
}
}
}
fn text_color(&self, theme: &Theme) -> Rgba {
if self.disabled {
return theme.colors.text_disabled;
}
match self.variant {
ButtonVariant::Primary => {
rgb(0xFFFFFF)
}
ButtonVariant::Danger => {
rgb(0xFFFFFF)
}
ButtonVariant::Secondary => {
let bg = self.background_color(theme, false, false);
if self.is_dark_background(bg) {
rgb(0xFFFFFF)
} else {
theme.colors.text
}
}
ButtonVariant::Outline | ButtonVariant::Text => {
theme.colors.text
}
}
}
fn is_dark_background(&self, color: Rgba) -> bool {
let luminance = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
luminance < 0.6 }
fn border_color(&self, theme: &Theme) -> Option<Rgba> {
if self.disabled {
return Some(theme.colors.border);
}
match self.variant {
ButtonVariant::Primary | ButtonVariant::Danger | ButtonVariant::Text => None,
ButtonVariant::Secondary | ButtonVariant::Outline => Some(theme.colors.border),
}
}
fn shadow_style(&self) -> Option<BoxShadow> {
if self.disabled {
return None;
}
match self.variant {
ButtonVariant::Primary => Some(BoxShadow {
color: rgba(0x00000018).into(),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}),
ButtonVariant::Secondary => Some(BoxShadow {
color: rgba(0x0000000A).into(),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}),
_ => None,
}
}
}
impl Render for Button {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::default();
let label = self.label.clone();
let disabled = self.disabled;
let loading = self.loading;
let size = self.size;
let (padding_y, padding_x) = size.padding();
let text_color = self.text_color(&theme);
let bg_color = self.background_color(&theme, false, false);
div()
.id("button")
.relative()
.flex()
.flex_shrink_0()
.items_center()
.justify_center()
.gap_2()
.py(padding_y)
.px(padding_x)
.rounded(px(BorderRadius::LG))
.text_size(size.font_size())
.font_weight(FontWeight::MEDIUM)
.bg(bg_color)
.when(self.full_width, |this| this.w_full())
.when(!disabled && !loading, |this| {
this.cursor(CursorStyle::PointingHand)
})
.when(disabled || loading, |this| {
this.opacity(0.64)
})
.when_some(self.border_color(&theme), |this, color| {
this.border_1().border_color(color)
})
.when_some(self.shadow_style(), |this, shadow| {
this.shadow(vec![shadow])
})
.on_mouse_down(MouseButton::Left, cx.listener(|this, _event: &MouseDownEvent, _window, cx| {
if !this.disabled && !this.loading {
cx.emit(ButtonEvent::Click);
}
}))
.text_color(text_color)
.when(loading, |this| {
this.child("⏳ ")
})
.child(label)
}
}