1use std::rc::Rc;
4
5use gpui::{
6 App, ElementId, IntoElement, Refineable, RenderOnce, SharedString, StyleRefinement, Window,
7 div, prelude::*, px,
8};
9use icons::Icon;
10use motion::Fade;
11use theme::{ControlSize, Sizing, Theme};
12
13use super::{ButtonRole, ButtonStyle, buttons::appearance};
14
15#[derive(IntoElement)]
18pub struct Button {
19 id: ElementId,
20 label: SharedString,
21 icon: Option<Icon>,
22 label_hidden: bool,
23 style: ButtonStyle,
24 size: ControlSize,
25 role: Option<ButtonRole>,
26 fade: Option<Fade>,
27 enabled: bool,
28 refinement: StyleRefinement,
29 activate: Option<Rc<Activation>>,
30}
31
32type Activation = dyn Fn(&(), &mut Window, &mut App);
33
34impl Button {
35 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
36 Self {
37 id: id.into(),
38 label: label.into(),
39 icon: None,
40 label_hidden: false,
41 style: ButtonStyle::Ghost,
42 size: ControlSize::Regular,
43 role: None,
44 fade: None,
45 enabled: true,
46 refinement: StyleRefinement::default(),
47 activate: None,
48 }
49 }
50
51 pub fn button_style(mut self, style: ButtonStyle) -> Self {
52 self.style = style;
53 self
54 }
55
56 pub fn control_size(mut self, size: ControlSize) -> Self {
58 self.size = size;
59 self
60 }
61
62 pub fn role(mut self, role: ButtonRole) -> Self {
63 self.role = Some(role);
64 self
65 }
66
67 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
68 self.icon = Some(icon.into());
69 self
70 }
71
72 pub fn label_hidden(mut self) -> Self {
74 self.label_hidden = true;
75 self
76 }
77
78 pub fn hover_fade(mut self, fade: Fade) -> Self {
79 self.fade = Some(fade);
80 self
81 }
82
83 pub fn enabled(mut self, enabled: bool) -> Self {
84 self.enabled = enabled;
85 self
86 }
87
88 pub fn on_press(mut self, activate: impl Fn(&(), &mut Window, &mut App) + 'static) -> Self {
90 self.activate = Some(Rc::new(activate));
91 self
92 }
93}
94
95impl Styled for Button {
96 fn style(&mut self) -> &mut StyleRefinement {
97 &mut self.refinement
98 }
99}
100
101impl RenderOnce for Button {
102 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
103 let focus = window.use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle());
104 let focus = focus.read(cx).clone();
105 let theme = Theme::of(cx);
106 let enabled = self.enabled && self.activate.is_some();
107 let frame = div()
108 .control_size(self.size)
109 .py(px((self.size.pad_y() - 1.0).max(0.0)))
111 .flex()
112 .items_center()
113 .justify_center()
114 .gap(px(Theme::SPACE))
115 .border_1()
116 .border_color(super::RING_SLOT)
117 .when(self.label_hidden, |el| {
118 el.px(px(0.0)).w(px(self.size.height()))
119 });
120 let fade = if enabled { self.fade } else { None };
121 let plain_ghost = self.style == ButtonStyle::Ghost && fade.is_none();
122 let (mut frame, tint) = appearance(theme, frame, self.style, self.role, fade, enabled);
123 if enabled && plain_ghost {
124 frame = frame.hover(|style| style.bg(theme.element_hover));
125 }
126 let tint = self.refinement.text.color.unwrap_or(tint);
127 frame.style().refine(&self.refinement);
128 let frame = frame
129 .when_some(self.icon, |el, icon| {
130 el.child(crate::icons::icon(icon).size(px(14.0)).text_color(tint))
131 })
132 .when(!self.label_hidden, |el| el.child(self.label.clone()))
133 .when(enabled, |el| el.cursor_pointer())
134 .when(!enabled, |el| el.opacity(0.5))
135 .id(self.id)
136 .role(gpui::Role::Button)
137 .aria_label(self.label);
138 crate::focus::pressable(theme, &focus, frame, enabled, move |event, window, cx| {
139 if let Some(activate) = &self.activate {
140 activate(event, window, cx);
141 }
142 })
143 }
144}