components.rs

  1use gpui::elements::StyleableComponent;
  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: StyleableComponent> {
 10    fn toggleable(self, active: bool) -> Toggle<C, ()>;
 11}
 12
 13impl<C: StyleableComponent> 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, StyleableComponent};
 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: StyleableComponent> 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: StyleableComponent> StyleableComponent 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: StyleableComponent> 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, MouseEventHandler, StyleableComponent, TooltipStyle,
 73        },
 74        platform::{CursorStyle, MouseButton},
 75        Action, Element, TypeTag, View,
 76    };
 77    use schemars::JsonSchema;
 78    use serde_derive::Deserialize;
 79
 80    use crate::Interactive;
 81
 82    pub struct ActionButton<C, S> {
 83        action: Box<dyn Action>,
 84        tooltip: Cow<'static, str>,
 85        tooltip_style: 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(
103            action: Box<dyn Action>,
104            tooltip: impl Into<Cow<'static, str>>,
105            tooltip_style: TooltipStyle,
106        ) -> Self {
107            Self {
108                contents: (),
109                tag: action.type_tag(),
110                style: Interactive::new_blank(),
111                tooltip: tooltip.into(),
112                tooltip_style,
113                action,
114            }
115        }
116
117        pub fn new<A: Action + Clone>(
118            action: A,
119            tooltip: impl Into<Cow<'static, str>>,
120            tooltip_style: TooltipStyle,
121        ) -> Self {
122            Self::new_dynamic(Box::new(action), tooltip, tooltip_style)
123        }
124
125        pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
126            ActionButton {
127                action: self.action,
128                tag: self.tag,
129                style: self.style,
130                tooltip: self.tooltip,
131                tooltip_style: self.tooltip_style,
132                contents,
133            }
134        }
135    }
136
137    impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
138        type Style = Interactive<ButtonStyle<C::Style>>;
139        type Output = ActionButton<C, ButtonStyle<C::Style>>;
140
141        fn with_style(self, style: Self::Style) -> Self::Output {
142            ActionButton {
143                action: self.action,
144                tag: self.tag,
145                contents: self.contents,
146                tooltip: self.tooltip,
147                tooltip_style: self.tooltip_style,
148                style,
149            }
150        }
151    }
152
153    impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
154        fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
155            MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
156                let style = self.style.style_for(state);
157                let mut contents = self
158                    .contents
159                    .with_style(style.contents.to_owned())
160                    .render(v, cx)
161                    .contained()
162                    .with_style(style.container)
163                    .constrained();
164
165                if let Some(height) = style.button_height {
166                    contents = contents.with_height(height);
167                }
168
169                if let Some(width) = style.button_width {
170                    contents = contents.with_width(width);
171                }
172
173                contents.into_any()
174            })
175            .on_click(MouseButton::Left, {
176                let action = self.action.boxed_clone();
177                move |_, _, cx| {
178                    cx.window()
179                        .dispatch_action(cx.view_id(), action.as_ref(), cx);
180                }
181            })
182            .with_cursor_style(CursorStyle::PointingHand)
183            .with_dynamic_tooltip(
184                self.tag,
185                0,
186                self.tooltip,
187                Some(self.action),
188                self.tooltip_style,
189                cx,
190            )
191            .into_any()
192        }
193    }
194}
195
196pub mod svg {
197    use std::borrow::Cow;
198
199    use gpui::{
200        elements::{GeneralComponent, StyleableComponent},
201        Element,
202    };
203    use schemars::JsonSchema;
204    use serde::Deserialize;
205
206    #[derive(Clone, Default, JsonSchema)]
207    pub struct SvgStyle {
208        icon_width: f32,
209        icon_height: f32,
210        color: gpui::color::Color,
211    }
212
213    impl<'de> Deserialize<'de> for SvgStyle {
214        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215        where
216            D: serde::Deserializer<'de>,
217        {
218            #[derive(Deserialize)]
219            #[serde(untagged)]
220            pub enum IconSize {
221                IconSize { icon_size: f32 },
222                Dimensions { width: f32, height: f32 },
223            }
224
225            #[derive(Deserialize)]
226            struct SvgStyleHelper {
227                #[serde(flatten)]
228                size: IconSize,
229                color: gpui::color::Color,
230            }
231
232            let json = SvgStyleHelper::deserialize(deserializer)?;
233            let color = json.color;
234
235            let result = match json.size {
236                IconSize::IconSize { icon_size } => SvgStyle {
237                    icon_width: icon_size,
238                    icon_height: icon_size,
239                    color,
240                },
241                IconSize::Dimensions { width, height } => SvgStyle {
242                    icon_width: width,
243                    icon_height: height,
244                    color,
245                },
246            };
247
248            Ok(result)
249        }
250    }
251
252    pub struct Svg<S> {
253        path: Cow<'static, str>,
254        style: S,
255    }
256
257    impl Svg<()> {
258        pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
259            Self {
260                path: path.into(),
261                style: (),
262            }
263        }
264    }
265
266    impl StyleableComponent for Svg<()> {
267        type Style = SvgStyle;
268
269        type Output = Svg<SvgStyle>;
270
271        fn with_style(self, style: Self::Style) -> Self::Output {
272            Svg {
273                path: self.path,
274                style,
275            }
276        }
277    }
278
279    impl GeneralComponent for Svg<SvgStyle> {
280        fn render<V: gpui::View>(
281            self,
282            _: &mut V,
283            _: &mut gpui::ViewContext<V>,
284        ) -> gpui::AnyElement<V> {
285            gpui::elements::Svg::new(self.path)
286                .with_color(self.style.color)
287                .constrained()
288                .with_width(self.style.icon_width)
289                .with_height(self.style.icon_height)
290                .into_any()
291        }
292    }
293}
294
295pub mod label {
296    use std::borrow::Cow;
297
298    use gpui::{
299        elements::{GeneralComponent, LabelStyle, StyleableComponent},
300        Element,
301    };
302
303    pub struct Label<S> {
304        text: Cow<'static, str>,
305        style: S,
306    }
307
308    impl Label<()> {
309        pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
310            Self {
311                text: text.into(),
312                style: (),
313            }
314        }
315    }
316
317    impl StyleableComponent for Label<()> {
318        type Style = LabelStyle;
319
320        type Output = Label<LabelStyle>;
321
322        fn with_style(self, style: Self::Style) -> Self::Output {
323            Label {
324                text: self.text,
325                style,
326            }
327        }
328    }
329
330    impl GeneralComponent for Label<LabelStyle> {
331        fn render<V: gpui::View>(
332            self,
333            _: &mut V,
334            _: &mut gpui::ViewContext<V>,
335        ) -> gpui::AnyElement<V> {
336            gpui::elements::Label::new(self.text, self.style).into_any()
337        }
338    }
339}