button.rs

  1use std::marker::PhantomData;
  2use std::rc::Rc;
  3
  4use gpui3::{DefiniteLength, Hsla, MouseButton, WindowContext};
  5
  6use crate::prelude::*;
  7use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize};
  8
  9#[derive(Default, PartialEq, Clone, Copy)]
 10pub enum IconPosition {
 11    #[default]
 12    Left,
 13    Right,
 14}
 15
 16#[derive(Default, Copy, Clone, PartialEq)]
 17pub enum ButtonVariant {
 18    #[default]
 19    Ghost,
 20    Filled,
 21}
 22
 23// struct ButtonHandlers<V> {
 24//     click: Option<Rc<dyn Fn(&mut V, &mut EventContext<V>)>>,
 25// }
 26
 27// impl<V> Default for ButtonHandlers<V> {
 28//     fn default() -> Self {
 29//         Self { click: None }
 30//     }
 31// }
 32
 33#[derive(Element)]
 34pub struct Button<S: 'static + Send + Sync + Clone> {
 35    state_type: PhantomData<S>,
 36    label: String,
 37    variant: ButtonVariant,
 38    state: InteractionState,
 39    icon: Option<Icon>,
 40    icon_position: Option<IconPosition>,
 41    width: Option<DefiniteLength>,
 42    // handlers: ButtonHandlers<S>,
 43}
 44
 45impl<S: 'static + Send + Sync + Clone> Button<S> {
 46    pub fn new<L>(label: L) -> Self
 47    where
 48        L: Into<String>,
 49    {
 50        Self {
 51            state_type: PhantomData,
 52            label: label.into(),
 53            variant: Default::default(),
 54            state: Default::default(),
 55            icon: None,
 56            icon_position: None,
 57            width: Default::default(),
 58            // handlers: ButtonHandlers::default(),
 59        }
 60    }
 61
 62    pub fn ghost<L>(label: L) -> Self
 63    where
 64        L: Into<String>,
 65    {
 66        Self::new(label).variant(ButtonVariant::Ghost)
 67    }
 68
 69    pub fn variant(mut self, variant: ButtonVariant) -> Self {
 70        self.variant = variant;
 71        self
 72    }
 73
 74    pub fn state(mut self, state: InteractionState) -> Self {
 75        self.state = state;
 76        self
 77    }
 78
 79    pub fn icon(mut self, icon: Icon) -> Self {
 80        self.icon = Some(icon);
 81        self
 82    }
 83
 84    pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
 85        if self.icon.is_none() {
 86            panic!("An icon must be present if an icon_position is provided.");
 87        }
 88        self.icon_position = Some(icon_position);
 89        self
 90    }
 91
 92    pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
 93        self.width = width;
 94        self
 95    }
 96
 97    // pub fn on_click(mut self, handler: impl Fn(&mut S, &mut EventContext<S>) + 'static) -> Self {
 98    //     self.handlers.click = Some(Rc::new(handler));
 99    //     self
100    // }
101
102    fn background_color(&self, cx: &mut ViewContext<S>) -> Hsla {
103        let theme = theme(cx);
104        let system_color = SystemColor::new();
105
106        match (self.variant, self.state) {
107            (ButtonVariant::Ghost, InteractionState::Hovered) => {
108                theme.lowest.base.hovered.background
109            }
110            (ButtonVariant::Ghost, InteractionState::Active) => {
111                theme.lowest.base.pressed.background
112            }
113            (ButtonVariant::Filled, InteractionState::Enabled) => {
114                theme.lowest.on.default.background
115            }
116            (ButtonVariant::Filled, InteractionState::Hovered) => {
117                theme.lowest.on.hovered.background
118            }
119            (ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background,
120            (ButtonVariant::Filled, InteractionState::Disabled) => {
121                theme.lowest.on.disabled.background
122            }
123            _ => system_color.transparent,
124        }
125    }
126
127    fn label_color(&self) -> LabelColor {
128        match self.state {
129            InteractionState::Disabled => LabelColor::Disabled,
130            _ => Default::default(),
131        }
132    }
133
134    fn icon_color(&self) -> IconColor {
135        match self.state {
136            InteractionState::Disabled => IconColor::Disabled,
137            _ => Default::default(),
138        }
139    }
140
141    fn border_color(&self, cx: &WindowContext) -> Hsla {
142        let theme = theme(cx);
143        let system_color = SystemColor::new();
144
145        match self.state {
146            InteractionState::Focused => theme.lowest.accent.default.border,
147            _ => system_color.transparent,
148        }
149    }
150
151    fn render_label(&self) -> Label<S> {
152        Label::new(self.label.clone())
153            .size(LabelSize::Small)
154            .color(self.label_color())
155    }
156
157    fn render_icon(&self, icon_color: IconColor) -> Option<IconElement<S>> {
158        self.icon.map(|i| IconElement::new(i).color(icon_color))
159    }
160
161    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
162        let theme = theme(cx);
163        let icon_color = self.icon_color();
164        let system_color = SystemColor::new();
165        let border_color = self.border_color(cx);
166
167        let mut el = h_stack()
168            .h_6()
169            .px_1()
170            .items_center()
171            .rounded_md()
172            .border()
173            .border_color(border_color)
174            .fill(self.background_color(cx));
175
176        match (self.icon, self.icon_position) {
177            (Some(_), Some(IconPosition::Left)) => {
178                el = el
179                    .gap_1()
180                    .child(self.render_label())
181                    .children(self.render_icon(icon_color))
182            }
183            (Some(_), Some(IconPosition::Right)) => {
184                el = el
185                    .gap_1()
186                    .children(self.render_icon(icon_color))
187                    .child(self.render_label())
188            }
189            (_, _) => el = el.child(self.render_label()),
190        }
191
192        if let Some(width) = self.width {
193            el = el.w(width).justify_center();
194        }
195
196        // if let Some(click_handler) = self.handlers.click.clone() {
197        //     el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| {
198        //         click_handler(view, cx);
199        //     });
200        // }
201
202        el
203    }
204}