Skip to main content

ui/widgets/
buttons.rs

1//! The button — one component, three shipped looks. A catalog trait like
2//! every widget group: `use ui::widgets::{ButtonStyle, Buttons};` →
3//! `theme.button("Save", ButtonStyle::Prominent, None)`.
4//!
5//! [`ButtonStyle`] is a closed enum, not free-form knobs: it selects between
6//! the looks that ship, while per-call overrides stay chain modifiers.
7
8use gpui::{Div, SharedString, div, prelude::*, px};
9use motion;
10use theme::{Theme, ThemeExt, ink, wash};
11
12/// The shipped looks (the reference `btnGhost` / `btnPrimary` /
13/// `btnDestructive`).
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum ButtonStyle {
16    /// Quiet text on a translucent wash.
17    Ghost,
18    /// The maximum-contrast plate — the primary action.
19    Prominent,
20    /// The muted red fill — carries the destructive semantics with the paint.
21    Destructive,
22}
23
24/// The frame every style shares.
25fn frame(mut el: Div) -> Div {
26    el = el
27        .px(px(12.0))
28        .py(px(6.0))
29        .rounded(px(Theme::button_radius()))
30        .text_size(px(13.0))
31        .cursor_pointer();
32    el
33}
34
35pub trait Buttons: ThemeExt {
36    /// A labeled button in one of the shipped styles. `fade_key` matters only
37    /// for [`ButtonStyle::Ghost`]: `Some` animates the hover wash per instance
38    /// (pass a unique key per button), `None` is the plain ghost with the
39    /// hover left to the caller.
40    fn button(
41        &self,
42        label: impl Into<SharedString>,
43        style: ButtonStyle,
44        fade_key: Option<SharedString>,
45    ) -> Div {
46        let theme = self.theme();
47        let label = label.into();
48        match style {
49            ButtonStyle::Ghost => match fade_key {
50                Some(fade_key) => {
51                    let mut btn = frame(div())
52                        .text_color(motion::hover_blend(&fade_key, theme.text_muted, theme.text))
53                        .bg(motion::hover_blend(&fade_key, wash(0.0), ink(0.06)))
54                        .child(label);
55                    btn.interactivity()
56                        .on_hover(motion::hover_listener(fade_key));
57                    btn
58                }
59                None => frame(div()).text_color(theme.text_muted).child(label),
60            },
61            ButtonStyle::Prominent => frame(div())
62                .bg(theme.text)
63                .font_weight(gpui::FontWeight::MEDIUM)
64                .text_color(theme.on_solid)
65                .hover(|s| s.opacity(0.9))
66                .child(label),
67            ButtonStyle::Destructive => frame(div())
68                .bg(theme.danger_strong)
69                .font_weight(gpui::FontWeight::MEDIUM)
70                .text_color(gpui::white())
71                .hover(|s| s.opacity(0.9))
72                .child(label),
73        }
74    }
75}
76
77impl Buttons for Theme {}