Skip to main content

guise/
actionicon.rs

1//! `ActionIcon` — a square icon-only button.
2
3use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
5
6use crate::devtools::Probed;
7use crate::icon::Glyph;
8use crate::input::ClickHandler;
9use crate::style::{icon_size, surface, ColorValue, Variant};
10use crate::theme::{theme, ColorName, Size};
11
12/// A compact, square icon button.
13#[derive(IntoElement)]
14pub struct ActionIcon {
15  id: ElementId,
16  icon: Glyph,
17  label: Option<SharedString>,
18  variant: Variant,
19  color: ColorValue,
20  size: Size,
21  radius: Option<Size>,
22  disabled: bool,
23  on_click: Option<ClickHandler>,
24}
25
26impl ActionIcon {
27  pub fn new(id: impl Into<ElementId>, icon: impl Into<Glyph>) -> Self {
28    ActionIcon {
29      id: id.into(),
30      icon: icon.into(),
31      label: None,
32      variant: Variant::Subtle,
33      color: ColorValue::Named(ColorName::Gray),
34      size: Size::Md,
35      radius: None,
36      disabled: false,
37      on_click: None,
38    }
39  }
40
41  /// Give the icon-only control a human-readable name and hover tooltip.
42  pub fn label(mut self, label: impl Into<SharedString>) -> Self {
43    self.label = Some(label.into());
44    self
45  }
46
47  pub fn variant(mut self, variant: Variant) -> Self {
48    self.variant = variant;
49    self
50  }
51
52  pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
53    self.color = color.into();
54    self
55  }
56
57  pub fn size(mut self, size: Size) -> Self {
58    self.size = size;
59    self
60  }
61
62  pub fn radius(mut self, radius: Size) -> Self {
63    self.radius = Some(radius);
64    self
65  }
66
67  pub fn disabled(mut self, disabled: bool) -> Self {
68    self.disabled = disabled;
69    self
70  }
71
72  pub fn on_click(
73    mut self,
74    handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
75  ) -> Self {
76    self.on_click = Some(Box::new(handler));
77    self
78  }
79}
80
81impl RenderOnce for ActionIcon {
82  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
83    let t = theme(cx);
84    let s = surface(t, self.color, self.variant);
85    let dim = icon_size(self.size);
86    let radius = t.radius(self.radius.unwrap_or(t.default_radius));
87    let focus = t.primary().hsla();
88
89    let mut el = div()
90      .id(self.id)
91      .w(px(dim))
92      .h(px(dim))
93      .flex()
94      .items_center()
95      .justify_center()
96      .rounded(px(radius))
97      .bg(s.bg)
98      .text_color(s.fg)
99      .text_size(px(dim * 0.5))
100      .child(self.icon)
101      .tab_index(0)
102      .focus(move |style| style.border_2().border_color(focus))
103      .on_key_down(crate::input::handle_tab);
104    if let Some(border) = s.border {
105      el = el.border_1().border_color(border);
106    }
107
108    let label = self.label;
109    let element = if self.disabled {
110      el.tab_stop(false).opacity(0.5)
111    } else {
112      let hover_bg = s.bg_hover;
113      el = el.hover(move |st| st.bg(hover_bg));
114      if let Some(handler) = self.on_click {
115        el = el.on_click(handler);
116      }
117      el
118    };
119
120    let element = element.when_some(label.clone(), |element, label| {
121      element.tooltip(crate::overlay::tooltip(label))
122    });
123
124    element
125      .probe("ActionIcon")
126      .attr("variant", self.variant.label())
127      .attr("size", self.size.label())
128      .attr_opt("label", label)
129      .attr_if("disabled", self.disabled)
130  }
131}