button_like.rs

  1use gpui::{relative, DefiniteLength};
  2use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
  3use smallvec::SmallVec;
  4
  5use crate::h_stack;
  6use crate::prelude::*;
  7
  8pub trait ButtonCommon: Clickable + Disableable {
  9    fn id(&self) -> &ElementId;
 10    fn style(self, style: ButtonStyle) -> Self;
 11    fn size(self, size: ButtonSize) -> Self;
 12    fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
 13}
 14
 15#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 16pub enum ButtonStyle {
 17    #[default]
 18    Filled,
 19    // Tinted,
 20    Subtle,
 21    Transparent,
 22}
 23
 24#[derive(Debug, Clone)]
 25pub(crate) struct ButtonLikeStyles {
 26    pub background: Hsla,
 27    #[allow(unused)]
 28    pub border_color: Hsla,
 29    #[allow(unused)]
 30    pub label_color: Hsla,
 31    #[allow(unused)]
 32    pub icon_color: Hsla,
 33}
 34
 35impl ButtonStyle {
 36    pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
 37        match self {
 38            ButtonStyle::Filled => ButtonLikeStyles {
 39                background: cx.theme().colors().element_background,
 40                border_color: transparent_black(),
 41                label_color: Color::Default.color(cx),
 42                icon_color: Color::Default.color(cx),
 43            },
 44            ButtonStyle::Subtle => ButtonLikeStyles {
 45                background: cx.theme().colors().ghost_element_background,
 46                border_color: transparent_black(),
 47                label_color: Color::Default.color(cx),
 48                icon_color: Color::Default.color(cx),
 49            },
 50            ButtonStyle::Transparent => ButtonLikeStyles {
 51                background: transparent_black(),
 52                border_color: transparent_black(),
 53                label_color: Color::Default.color(cx),
 54                icon_color: Color::Default.color(cx),
 55            },
 56        }
 57    }
 58
 59    pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
 60        match self {
 61            ButtonStyle::Filled => ButtonLikeStyles {
 62                background: cx.theme().colors().element_hover,
 63                border_color: transparent_black(),
 64                label_color: Color::Default.color(cx),
 65                icon_color: Color::Default.color(cx),
 66            },
 67            ButtonStyle::Subtle => ButtonLikeStyles {
 68                background: cx.theme().colors().ghost_element_hover,
 69                border_color: transparent_black(),
 70                label_color: Color::Default.color(cx),
 71                icon_color: Color::Default.color(cx),
 72            },
 73            ButtonStyle::Transparent => ButtonLikeStyles {
 74                background: transparent_black(),
 75                border_color: transparent_black(),
 76                // TODO: These are not great
 77                label_color: Color::Muted.color(cx),
 78                // TODO: These are not great
 79                icon_color: Color::Muted.color(cx),
 80            },
 81        }
 82    }
 83
 84    pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
 85        match self {
 86            ButtonStyle::Filled => ButtonLikeStyles {
 87                background: cx.theme().colors().element_active,
 88                border_color: transparent_black(),
 89                label_color: Color::Default.color(cx),
 90                icon_color: Color::Default.color(cx),
 91            },
 92            ButtonStyle::Subtle => ButtonLikeStyles {
 93                background: cx.theme().colors().ghost_element_active,
 94                border_color: transparent_black(),
 95                label_color: Color::Default.color(cx),
 96                icon_color: Color::Default.color(cx),
 97            },
 98            ButtonStyle::Transparent => ButtonLikeStyles {
 99                background: transparent_black(),
100                border_color: transparent_black(),
101                // TODO: These are not great
102                label_color: Color::Muted.color(cx),
103                // TODO: These are not great
104                icon_color: Color::Muted.color(cx),
105            },
106        }
107    }
108
109    #[allow(unused)]
110    pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
111        match self {
112            ButtonStyle::Filled => ButtonLikeStyles {
113                background: cx.theme().colors().element_background,
114                border_color: cx.theme().colors().border_focused,
115                label_color: Color::Default.color(cx),
116                icon_color: Color::Default.color(cx),
117            },
118            ButtonStyle::Subtle => ButtonLikeStyles {
119                background: cx.theme().colors().ghost_element_background,
120                border_color: cx.theme().colors().border_focused,
121                label_color: Color::Default.color(cx),
122                icon_color: Color::Default.color(cx),
123            },
124            ButtonStyle::Transparent => ButtonLikeStyles {
125                background: transparent_black(),
126                border_color: cx.theme().colors().border_focused,
127                label_color: Color::Accent.color(cx),
128                icon_color: Color::Accent.color(cx),
129            },
130        }
131    }
132
133    pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
134        match self {
135            ButtonStyle::Filled => ButtonLikeStyles {
136                background: cx.theme().colors().element_disabled,
137                border_color: cx.theme().colors().border_disabled,
138                label_color: Color::Disabled.color(cx),
139                icon_color: Color::Disabled.color(cx),
140            },
141            ButtonStyle::Subtle => ButtonLikeStyles {
142                background: cx.theme().colors().ghost_element_disabled,
143                border_color: cx.theme().colors().border_disabled,
144                label_color: Color::Disabled.color(cx),
145                icon_color: Color::Disabled.color(cx),
146            },
147            ButtonStyle::Transparent => ButtonLikeStyles {
148                background: transparent_black(),
149                border_color: transparent_black(),
150                label_color: Color::Disabled.color(cx),
151                icon_color: Color::Disabled.color(cx),
152            },
153        }
154    }
155}
156
157#[derive(Default, PartialEq, Clone, Copy)]
158pub enum ButtonSize {
159    #[default]
160    Default,
161    Compact,
162    None,
163}
164
165impl ButtonSize {
166    fn height(self) -> Rems {
167        match self {
168            ButtonSize::Default => rems(22. / 16.),
169            ButtonSize::Compact => rems(18. / 16.),
170            ButtonSize::None => rems(16. / 16.),
171        }
172    }
173}
174
175#[derive(IntoElement)]
176pub struct ButtonLike {
177    id: ElementId,
178    pub(super) style: ButtonStyle,
179    pub(super) disabled: bool,
180    pub(super) selected: bool,
181    pub(super) width: Option<DefiniteLength>,
182    size: ButtonSize,
183    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
184    on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
185    children: SmallVec<[AnyElement; 2]>,
186}
187
188impl ButtonLike {
189    pub fn new(id: impl Into<ElementId>) -> Self {
190        Self {
191            id: id.into(),
192            style: ButtonStyle::default(),
193            disabled: false,
194            selected: false,
195            width: None,
196            size: ButtonSize::Default,
197            tooltip: None,
198            children: SmallVec::new(),
199            on_click: None,
200        }
201    }
202}
203
204impl Disableable for ButtonLike {
205    fn disabled(mut self, disabled: bool) -> Self {
206        self.disabled = disabled;
207        self
208    }
209}
210
211impl Selectable for ButtonLike {
212    fn selected(mut self, selected: bool) -> Self {
213        self.selected = selected;
214        self
215    }
216}
217
218impl Clickable for ButtonLike {
219    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
220        self.on_click = Some(Box::new(handler));
221        self
222    }
223}
224
225impl FixedWidth for ButtonLike {
226    fn width(mut self, width: DefiniteLength) -> Self {
227        self.width = Some(width);
228        self
229    }
230
231    fn full_width(mut self) -> Self {
232        self.width = Some(relative(1.));
233        self
234    }
235}
236
237impl ButtonCommon for ButtonLike {
238    fn id(&self) -> &ElementId {
239        &self.id
240    }
241
242    fn style(mut self, style: ButtonStyle) -> Self {
243        self.style = style;
244        self
245    }
246
247    fn size(mut self, size: ButtonSize) -> Self {
248        self.size = size;
249        self
250    }
251
252    fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
253        self.tooltip = Some(Box::new(tooltip));
254        self
255    }
256}
257
258impl ParentElement for ButtonLike {
259    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
260        &mut self.children
261    }
262}
263
264impl RenderOnce for ButtonLike {
265    type Rendered = Stateful<Div>;
266
267    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
268        h_stack()
269            .id(self.id.clone())
270            .group("")
271            .flex_none()
272            .h(self.size.height())
273            .when_some(self.width, |this, width| this.w(width))
274            .rounded_md()
275            .cursor_pointer()
276            .gap_1()
277            .px_1()
278            .bg(self.style.enabled(cx).background)
279            .hover(|hover| hover.bg(self.style.hovered(cx).background))
280            .active(|active| active.bg(self.style.active(cx).background))
281            .when_some(
282                self.on_click.filter(|_| !self.disabled),
283                |this, on_click| {
284                    this.on_click(move |event, cx| {
285                        cx.stop_propagation();
286                        (on_click)(event, cx)
287                    })
288                },
289            )
290            .when_some(self.tooltip, |this, tooltip| {
291                if !self.selected {
292                    this.tooltip(move |cx| tooltip(cx))
293                } else {
294                    this
295                }
296            })
297            .children(self.children)
298    }
299}