components.rs

  1use gpui::elements::GeneralStyleableComponent;
  2
  3use crate::{Interactive, Toggleable};
  4
  5use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle};
  6
  7pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
  8
  9pub trait ComponentExt<C: GeneralStyleableComponent> {
 10    fn toggleable(self, active: bool) -> Toggle<C, ()>;
 11}
 12
 13impl<C: GeneralStyleableComponent> ComponentExt<C> for C {
 14    fn toggleable(self, active: bool) -> Toggle<C, ()> {
 15        Toggle::new(self, active)
 16    }
 17}
 18
 19pub mod toggle {
 20    use gpui::elements::{GeneralComponent, GeneralStyleableComponent};
 21
 22    use crate::Toggleable;
 23
 24    pub struct Toggle<C, S> {
 25        style: S,
 26        active: bool,
 27        component: C,
 28    }
 29
 30    impl<C: GeneralStyleableComponent> Toggle<C, ()> {
 31        pub fn new(component: C, active: bool) -> Self {
 32            Toggle {
 33                active,
 34                component,
 35                style: (),
 36            }
 37        }
 38    }
 39
 40    impl<C: GeneralStyleableComponent> GeneralStyleableComponent for Toggle<C, ()> {
 41        type Style = Toggleable<C::Style>;
 42
 43        type Output = Toggle<C, Self::Style>;
 44
 45        fn with_style(self, style: Self::Style) -> Self::Output {
 46            Toggle {
 47                active: self.active,
 48                component: self.component,
 49                style,
 50            }
 51        }
 52    }
 53
 54    impl<C: GeneralStyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
 55        fn render<V: gpui::View>(
 56            self,
 57            v: &mut V,
 58            cx: &mut gpui::ViewContext<V>,
 59        ) -> gpui::AnyElement<V> {
 60            self.component
 61                .with_style(self.style.in_state(self.active).clone())
 62                .render(v, cx)
 63        }
 64    }
 65}
 66
 67pub mod action_button {
 68    use std::borrow::Cow;
 69
 70    use gpui::{
 71        elements::{
 72            ContainerStyle, GeneralComponent, GeneralStyleableComponent, MouseEventHandler,
 73            TooltipStyle,
 74        },
 75        platform::{CursorStyle, MouseButton},
 76        Action, Element, TypeTag, View,
 77    };
 78    use schemars::JsonSchema;
 79    use serde_derive::Deserialize;
 80
 81    use crate::Interactive;
 82
 83    pub struct ActionButton<C, S> {
 84        action: Box<dyn Action>,
 85        tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
 86        tag: TypeTag,
 87        contents: C,
 88        style: Interactive<S>,
 89    }
 90
 91    #[derive(Clone, Deserialize, Default, JsonSchema)]
 92    pub struct ButtonStyle<C> {
 93        #[serde(flatten)]
 94        container: ContainerStyle,
 95        button_width: Option<f32>,
 96        button_height: Option<f32>,
 97        #[serde(flatten)]
 98        contents: C,
 99    }
100
101    impl ActionButton<(), ()> {
102        pub fn new_dynamic(action: Box<dyn Action>) -> Self {
103            Self {
104                contents: (),
105                tag: action.type_tag(),
106                style: Interactive::new_blank(),
107                tooltip: None,
108                action,
109            }
110        }
111
112        pub fn new<A: Action + Clone>(action: A) -> Self {
113            Self::new_dynamic(Box::new(action))
114        }
115
116        pub fn with_tooltip(
117            mut self,
118            tooltip: impl Into<Cow<'static, str>>,
119            tooltip_style: TooltipStyle,
120        ) -> Self {
121            self.tooltip = Some((tooltip.into(), tooltip_style));
122            self
123        }
124
125        pub fn with_contents<C: GeneralStyleableComponent>(
126            self,
127            contents: C,
128        ) -> ActionButton<C, ()> {
129            ActionButton {
130                action: self.action,
131                tag: self.tag,
132                style: self.style,
133                tooltip: self.tooltip,
134                contents,
135            }
136        }
137    }
138
139    impl<C: GeneralStyleableComponent> GeneralStyleableComponent for ActionButton<C, ()> {
140        type Style = Interactive<ButtonStyle<C::Style>>;
141        type Output = ActionButton<C, ButtonStyle<C::Style>>;
142
143        fn with_style(self, style: Self::Style) -> Self::Output {
144            ActionButton {
145                action: self.action,
146                tag: self.tag,
147                contents: self.contents,
148                tooltip: self.tooltip,
149
150                style,
151            }
152        }
153    }
154
155    impl<C: GeneralStyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
156        fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
157            let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
158                let style = self.style.style_for(state);
159                let mut contents = self
160                    .contents
161                    .with_style(style.contents.to_owned())
162                    .render(v, cx)
163                    .contained()
164                    .with_style(style.container)
165                    .constrained();
166
167                if let Some(height) = style.button_height {
168                    contents = contents.with_height(height);
169                }
170
171                if let Some(width) = style.button_width {
172                    contents = contents.with_width(width);
173                }
174
175                contents.into_any()
176            })
177            .on_click(MouseButton::Left, {
178                let action = self.action.boxed_clone();
179                move |_, _, cx| {
180                    cx.window()
181                        .dispatch_action(cx.view_id(), action.as_ref(), cx);
182                }
183            })
184            .with_cursor_style(CursorStyle::PointingHand)
185            .into_any();
186
187            if let Some((tooltip, style)) = self.tooltip {
188                button = button
189                    .with_dynamic_tooltip(self.tag, 0, tooltip, Some(self.action), style, cx)
190                    .into_any()
191            }
192
193            button
194        }
195    }
196}
197
198pub mod svg {
199    use std::borrow::Cow;
200
201    use gpui::{
202        elements::{GeneralComponent, GeneralStyleableComponent},
203        Element,
204    };
205    use schemars::JsonSchema;
206    use serde::Deserialize;
207
208    #[derive(Clone, Default, JsonSchema)]
209    pub struct SvgStyle {
210        icon_width: f32,
211        icon_height: f32,
212        color: gpui::color::Color,
213    }
214
215    impl<'de> Deserialize<'de> for SvgStyle {
216        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
217        where
218            D: serde::Deserializer<'de>,
219        {
220            #[derive(Deserialize)]
221            #[serde(untagged)]
222            pub enum IconSize {
223                IconSize { icon_size: f32 },
224                Dimensions { width: f32, height: f32 },
225            }
226
227            #[derive(Deserialize)]
228            struct SvgStyleHelper {
229                #[serde(flatten)]
230                size: IconSize,
231                color: gpui::color::Color,
232            }
233
234            let json = SvgStyleHelper::deserialize(deserializer)?;
235            let color = json.color;
236
237            let result = match json.size {
238                IconSize::IconSize { icon_size } => SvgStyle {
239                    icon_width: icon_size,
240                    icon_height: icon_size,
241                    color,
242                },
243                IconSize::Dimensions { width, height } => SvgStyle {
244                    icon_width: width,
245                    icon_height: height,
246                    color,
247                },
248            };
249
250            Ok(result)
251        }
252    }
253
254    pub struct Svg<S> {
255        path: Cow<'static, str>,
256        style: S,
257    }
258
259    impl Svg<()> {
260        pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
261            Self {
262                path: path.into(),
263                style: (),
264            }
265        }
266    }
267
268    impl GeneralStyleableComponent for Svg<()> {
269        type Style = SvgStyle;
270
271        type Output = Svg<SvgStyle>;
272
273        fn with_style(self, style: Self::Style) -> Self::Output {
274            Svg {
275                path: self.path,
276                style,
277            }
278        }
279    }
280
281    impl GeneralComponent for Svg<SvgStyle> {
282        fn render<V: gpui::View>(
283            self,
284            _: &mut V,
285            _: &mut gpui::ViewContext<V>,
286        ) -> gpui::AnyElement<V> {
287            gpui::elements::Svg::new(self.path)
288                .with_color(self.style.color)
289                .constrained()
290                .with_width(self.style.icon_width)
291                .with_height(self.style.icon_height)
292                .into_any()
293        }
294    }
295}
296
297pub mod label {
298    use std::borrow::Cow;
299
300    use gpui::{
301        elements::{GeneralComponent, GeneralStyleableComponent, LabelStyle},
302        Element,
303    };
304
305    pub struct Label<S> {
306        text: Cow<'static, str>,
307        style: S,
308    }
309
310    impl Label<()> {
311        pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
312            Self {
313                text: text.into(),
314                style: (),
315            }
316        }
317    }
318
319    impl GeneralStyleableComponent for Label<()> {
320        type Style = LabelStyle;
321
322        type Output = Label<LabelStyle>;
323
324        fn with_style(self, style: Self::Style) -> Self::Output {
325            Label {
326                text: self.text,
327                style,
328            }
329        }
330    }
331
332    impl GeneralComponent for Label<LabelStyle> {
333        fn render<V: gpui::View>(
334            self,
335            _: &mut V,
336            _: &mut gpui::ViewContext<V>,
337        ) -> gpui::AnyElement<V> {
338            gpui::elements::Label::new(self.text, self.style).into_any()
339        }
340    }
341}