button_like.rs

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