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                    let window = cx.window();
179                    let view = cx.view_id();
180                    let action = action.boxed_clone();
181                    cx.spawn(|_, mut cx| async move {
182                        window.dispatch_action(view, action.as_ref(), &mut cx)
183                    })
184                    .detach();
185                }
186            })
187            .with_cursor_style(CursorStyle::PointingHand)
188            .with_dynamic_tooltip(
189                self.tag,
190                0,
191                self.tooltip,
192                Some(self.action),
193                self.tooltip_style,
194                cx,
195            )
196            .into_any()
197        }
198    }
199}
200
201pub mod svg {
202    use std::borrow::Cow;
203
204    use gpui::{
205        elements::{GeneralComponent, StyleableComponent},
206        Element,
207    };
208    use schemars::JsonSchema;
209    use serde::Deserialize;
210
211    #[derive(Clone, Default, JsonSchema)]
212    pub struct SvgStyle {
213        icon_width: f32,
214        icon_height: f32,
215        color: gpui::color::Color,
216    }
217
218    impl<'de> Deserialize<'de> for SvgStyle {
219        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220        where
221            D: serde::Deserializer<'de>,
222        {
223            #[derive(Deserialize)]
224            #[serde(untagged)]
225            pub enum IconSize {
226                IconSize { icon_size: f32 },
227                Dimensions { width: f32, height: f32 },
228            }
229
230            #[derive(Deserialize)]
231            struct SvgStyleHelper {
232                #[serde(flatten)]
233                size: IconSize,
234                color: gpui::color::Color,
235            }
236
237            let json = SvgStyleHelper::deserialize(deserializer)?;
238            let color = json.color;
239
240            let result = match json.size {
241                IconSize::IconSize { icon_size } => SvgStyle {
242                    icon_width: icon_size,
243                    icon_height: icon_size,
244                    color,
245                },
246                IconSize::Dimensions { width, height } => SvgStyle {
247                    icon_width: width,
248                    icon_height: height,
249                    color,
250                },
251            };
252
253            Ok(result)
254        }
255    }
256
257    pub struct Svg<S> {
258        path: Cow<'static, str>,
259        style: S,
260    }
261
262    impl Svg<()> {
263        pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
264            Self {
265                path: path.into(),
266                style: (),
267            }
268        }
269    }
270
271    impl StyleableComponent for Svg<()> {
272        type Style = SvgStyle;
273
274        type Output = Svg<SvgStyle>;
275
276        fn with_style(self, style: Self::Style) -> Self::Output {
277            Svg {
278                path: self.path,
279                style,
280            }
281        }
282    }
283
284    impl GeneralComponent for Svg<SvgStyle> {
285        fn render<V: gpui::View>(
286            self,
287            _: &mut V,
288            _: &mut gpui::ViewContext<V>,
289        ) -> gpui::AnyElement<V> {
290            gpui::elements::Svg::new(self.path)
291                .with_color(self.style.color)
292                .constrained()
293                .with_width(self.style.icon_width)
294                .with_height(self.style.icon_height)
295                .into_any()
296        }
297    }
298}
299
300pub mod label {
301    use std::borrow::Cow;
302
303    use gpui::{
304        elements::{GeneralComponent, LabelStyle, StyleableComponent},
305        Element,
306    };
307
308    pub struct Label<S> {
309        text: Cow<'static, str>,
310        style: S,
311    }
312
313    impl Label<()> {
314        pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
315            Self {
316                text: text.into(),
317                style: (),
318            }
319        }
320    }
321
322    impl StyleableComponent for Label<()> {
323        type Style = LabelStyle;
324
325        type Output = Label<LabelStyle>;
326
327        fn with_style(self, style: Self::Style) -> Self::Output {
328            Label {
329                text: self.text,
330                style,
331            }
332        }
333    }
334
335    impl GeneralComponent for Label<LabelStyle> {
336        fn render<V: gpui::View>(
337            self,
338            _: &mut V,
339            _: &mut gpui::ViewContext<V>,
340        ) -> gpui::AnyElement<V> {
341            gpui::elements::Label::new(self.text, self.style).into_any()
342        }
343    }
344}