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