icon_button.rs

  1use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
  2use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
  3use std::sync::Arc;
  4
  5struct IconButtonHandlers<V: 'static> {
  6    click: Option<ClickHandler<V>>,
  7}
  8
  9impl<V: 'static> Default for IconButtonHandlers<V> {
 10    fn default() -> Self {
 11        Self { click: None }
 12    }
 13}
 14
 15#[derive(RenderOnce)]
 16pub struct IconButton<V: 'static> {
 17    id: ElementId,
 18    icon: Icon,
 19    color: TextColor,
 20    variant: ButtonVariant,
 21    state: InteractionState,
 22    selected: bool,
 23    tooltip: Option<Box<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>>,
 24    handlers: IconButtonHandlers<V>,
 25}
 26
 27impl<V: 'static> Component<V> for IconButton<V> {
 28    type Rendered = Stateful<V, Div<V>>;
 29
 30    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
 31        let icon_color = match (self.state, self.color) {
 32            (InteractionState::Disabled, _) => TextColor::Disabled,
 33            (InteractionState::Active, _) => TextColor::Selected,
 34            _ => self.color,
 35        };
 36
 37        let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
 38            ButtonVariant::Filled => (
 39                cx.theme().colors().element_background,
 40                cx.theme().colors().element_hover,
 41                cx.theme().colors().element_active,
 42            ),
 43            ButtonVariant::Ghost => (
 44                cx.theme().colors().ghost_element_background,
 45                cx.theme().colors().ghost_element_hover,
 46                cx.theme().colors().ghost_element_active,
 47            ),
 48        };
 49
 50        if self.selected {
 51            bg_color = bg_hover_color;
 52        }
 53
 54        let mut button = h_stack()
 55            .id(self.id.clone())
 56            .justify_center()
 57            .rounded_md()
 58            .p_1()
 59            .bg(bg_color)
 60            .cursor_pointer()
 61            // Nate: Trying to figure out the right places we want to show a
 62            // hover state here. I think it is a bit heavy to have it on every
 63            // place we use an icon button.
 64            // .hover(|style| style.bg(bg_hover_color))
 65            .active(|style| style.bg(bg_active_color))
 66            .child(IconElement::new(self.icon).color(icon_color));
 67
 68        if let Some(click_handler) = self.handlers.click.clone() {
 69            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
 70                cx.stop_propagation();
 71                click_handler(state, cx);
 72            })
 73        }
 74
 75        if let Some(tooltip) = self.tooltip {
 76            if !self.selected {
 77                button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
 78            }
 79        }
 80
 81        button
 82    }
 83}
 84
 85impl<V: 'static> IconButton<V> {
 86    pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
 87        Self {
 88            id: id.into(),
 89            icon,
 90            color: TextColor::default(),
 91            variant: ButtonVariant::default(),
 92            state: InteractionState::default(),
 93            selected: false,
 94            tooltip: None,
 95            handlers: IconButtonHandlers::default(),
 96        }
 97    }
 98
 99    pub fn icon(mut self, icon: Icon) -> Self {
100        self.icon = icon;
101        self
102    }
103
104    pub fn color(mut self, color: TextColor) -> Self {
105        self.color = color;
106        self
107    }
108
109    pub fn variant(mut self, variant: ButtonVariant) -> Self {
110        self.variant = variant;
111        self
112    }
113
114    pub fn state(mut self, state: InteractionState) -> Self {
115        self.state = state;
116        self
117    }
118
119    pub fn selected(mut self, selected: bool) -> Self {
120        self.selected = selected;
121        self
122    }
123
124    pub fn tooltip(
125        mut self,
126        tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
127    ) -> Self {
128        self.tooltip = Some(Box::new(tooltip));
129        self
130    }
131
132    pub fn on_click(mut self, handler: impl 'static + Fn(&mut V, &mut ViewContext<V>)) -> Self {
133        self.handlers.click = Some(Arc::new(handler));
134        self
135    }
136
137    pub fn action(self, action: Box<dyn Action>) -> Self {
138        self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
139    }
140}