component.rs

  1use std::fmt::Display;
  2use std::ops::{Deref, DerefMut};
  3use std::sync::LazyLock;
  4
  5use collections::HashMap;
  6use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
  7use linkme::distributed_slice;
  8use parking_lot::RwLock;
  9use theme::ActiveTheme;
 10
 11pub trait Component {
 12    fn scope() -> Option<ComponentScope>;
 13    fn name() -> &'static str {
 14        std::any::type_name::<Self>()
 15    }
 16    fn description() -> Option<&'static str> {
 17        None
 18    }
 19}
 20
 21pub trait ComponentPreview: Component {
 22    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement;
 23}
 24
 25#[distributed_slice]
 26pub static __ALL_COMPONENTS: [fn()] = [..];
 27
 28#[distributed_slice]
 29pub static __ALL_PREVIEWS: [fn()] = [..];
 30
 31pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
 32    LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
 33
 34pub struct ComponentRegistry {
 35    components: Vec<(Option<ComponentScope>, &'static str, Option<&'static str>)>,
 36    previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>,
 37}
 38
 39impl ComponentRegistry {
 40    fn new() -> Self {
 41        ComponentRegistry {
 42            components: Vec::new(),
 43            previews: HashMap::default(),
 44        }
 45    }
 46}
 47
 48pub fn init() {
 49    let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
 50    let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
 51
 52    for f in component_fns {
 53        f();
 54    }
 55    for f in preview_fns {
 56        f();
 57    }
 58}
 59
 60pub fn register_component<T: Component>() {
 61    let component_data = (T::scope(), T::name(), T::description());
 62    COMPONENT_DATA.write().components.push(component_data);
 63}
 64
 65pub fn register_preview<T: ComponentPreview>() {
 66    let preview_data = (
 67        T::name(),
 68        T::preview as fn(&mut Window, &mut App) -> AnyElement,
 69    );
 70    COMPONENT_DATA
 71        .write()
 72        .previews
 73        .insert(preview_data.0, preview_data.1);
 74}
 75
 76#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 77pub struct ComponentId(pub &'static str);
 78
 79#[derive(Clone)]
 80pub struct ComponentMetadata {
 81    name: SharedString,
 82    scope: Option<ComponentScope>,
 83    description: Option<SharedString>,
 84    preview: Option<fn(&mut Window, &mut App) -> AnyElement>,
 85}
 86
 87impl ComponentMetadata {
 88    pub fn name(&self) -> SharedString {
 89        self.name.clone()
 90    }
 91
 92    pub fn scope(&self) -> Option<ComponentScope> {
 93        self.scope.clone()
 94    }
 95
 96    pub fn description(&self) -> Option<SharedString> {
 97        self.description.clone()
 98    }
 99
100    pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> {
101        self.preview
102    }
103}
104
105pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
106
107impl AllComponents {
108    pub fn new() -> Self {
109        AllComponents(HashMap::default())
110    }
111
112    /// Returns all components with previews
113    pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
114        self.0.values().filter(|c| c.preview.is_some()).collect()
115    }
116
117    /// Returns all components with previews sorted by name
118    pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
119        let mut previews: Vec<ComponentMetadata> =
120            self.all_previews().into_iter().cloned().collect();
121        previews.sort_by_key(|a| a.name());
122        previews
123    }
124
125    /// Returns all components
126    pub fn all(&self) -> Vec<&ComponentMetadata> {
127        self.0.values().collect()
128    }
129
130    /// Returns all components sorted by name
131    pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
132        let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
133        components.sort_by_key(|a| a.name());
134        components
135    }
136}
137
138impl Deref for AllComponents {
139    type Target = HashMap<ComponentId, ComponentMetadata>;
140
141    fn deref(&self) -> &Self::Target {
142        &self.0
143    }
144}
145
146impl DerefMut for AllComponents {
147    fn deref_mut(&mut self) -> &mut Self::Target {
148        &mut self.0
149    }
150}
151
152pub fn components() -> AllComponents {
153    let data = COMPONENT_DATA.read();
154    let mut all_components = AllComponents::new();
155
156    for (ref scope, name, description) in &data.components {
157        let preview = data.previews.get(name).cloned();
158        let component_name = SharedString::new_static(name);
159        all_components.insert(
160            ComponentId(name),
161            ComponentMetadata {
162                name: component_name,
163                scope: scope.clone(),
164                description: description.map(Into::into),
165                preview,
166            },
167        );
168    }
169
170    all_components
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub enum ComponentScope {
175    Layout,
176    Input,
177    Notification,
178    Editor,
179    Collaboration,
180    VersionControl,
181    Unknown(SharedString),
182}
183
184impl Display for ComponentScope {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        match self {
187            ComponentScope::Layout => write!(f, "Layout"),
188            ComponentScope::Input => write!(f, "Input"),
189            ComponentScope::Notification => write!(f, "Notification"),
190            ComponentScope::Editor => write!(f, "Editor"),
191            ComponentScope::Collaboration => write!(f, "Collaboration"),
192            ComponentScope::VersionControl => write!(f, "Version Control"),
193            ComponentScope::Unknown(name) => write!(f, "Unknown: {}", name),
194        }
195    }
196}
197
198impl From<&str> for ComponentScope {
199    fn from(value: &str) -> Self {
200        match value {
201            "Layout" => ComponentScope::Layout,
202            "Input" => ComponentScope::Input,
203            "Notification" => ComponentScope::Notification,
204            "Editor" => ComponentScope::Editor,
205            "Collaboration" => ComponentScope::Collaboration,
206            "Version Control" | "VersionControl" => ComponentScope::VersionControl,
207            _ => ComponentScope::Unknown(SharedString::new(value)),
208        }
209    }
210}
211
212impl From<String> for ComponentScope {
213    fn from(value: String) -> Self {
214        match value.as_str() {
215            "Layout" => ComponentScope::Layout,
216            "Input" => ComponentScope::Input,
217            "Notification" => ComponentScope::Notification,
218            "Editor" => ComponentScope::Editor,
219            "Collaboration" => ComponentScope::Collaboration,
220            "Version Control" | "VersionControl" => ComponentScope::VersionControl,
221            _ => ComponentScope::Unknown(SharedString::new(value)),
222        }
223    }
224}
225
226/// Which side of the preview to show labels on
227#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
228pub enum ExampleLabelSide {
229    /// Left side
230    Left,
231    /// Right side
232    Right,
233    /// Top side
234    #[default]
235    Top,
236    /// Bottom side
237    Bottom,
238}
239
240/// A single example of a component.
241#[derive(IntoElement)]
242pub struct ComponentExample {
243    variant_name: SharedString,
244    element: AnyElement,
245    label_side: ExampleLabelSide,
246    grow: bool,
247}
248
249impl RenderOnce for ComponentExample {
250    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
251        let base = div().flex();
252
253        let base = match self.label_side {
254            ExampleLabelSide::Right => base.flex_row(),
255            ExampleLabelSide::Left => base.flex_row_reverse(),
256            ExampleLabelSide::Bottom => base.flex_col(),
257            ExampleLabelSide::Top => base.flex_col_reverse(),
258        };
259
260        base.gap_2()
261            .p_2()
262            .text_size(px(10.))
263            .text_color(cx.theme().colors().text_muted)
264            .when(self.grow, |this| this.flex_1())
265            .when(!self.grow, |this| this.flex_none())
266            .child(self.element)
267            .child(self.variant_name)
268            .into_any_element()
269    }
270}
271
272impl ComponentExample {
273    /// Create a new example with the given variant name and example value.
274    pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
275        Self {
276            variant_name: variant_name.into(),
277            element,
278            label_side: ExampleLabelSide::default(),
279            grow: false,
280        }
281    }
282
283    /// Set the example to grow to fill the available horizontal space.
284    pub fn grow(mut self) -> Self {
285        self.grow = true;
286        self
287    }
288}
289
290/// A group of component examples.
291#[derive(IntoElement)]
292pub struct ComponentExampleGroup {
293    pub title: Option<SharedString>,
294    pub examples: Vec<ComponentExample>,
295    pub grow: bool,
296    pub vertical: bool,
297}
298
299impl RenderOnce for ComponentExampleGroup {
300    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
301        div()
302            .flex_col()
303            .text_sm()
304            .text_color(cx.theme().colors().text_muted)
305            .when(self.grow, |this| this.w_full().flex_1())
306            .when_some(self.title, |this, title| {
307                this.gap_4().child(
308                    div()
309                        .flex()
310                        .items_center()
311                        .gap_3()
312                        .pb_1()
313                        .child(div().h_px().w_4().bg(cx.theme().colors().border))
314                        .child(
315                            div()
316                                .flex_none()
317                                .text_size(px(10.))
318                                .child(title.to_uppercase()),
319                        )
320                        .child(
321                            div()
322                                .h_px()
323                                .w_full()
324                                .flex_1()
325                                .bg(cx.theme().colors().border),
326                        ),
327                )
328            })
329            .child(
330                div()
331                    .flex()
332                    .when(self.vertical, |this| this.flex_col())
333                    .items_start()
334                    .w_full()
335                    .gap_6()
336                    .children(self.examples)
337                    .into_any_element(),
338            )
339            .into_any_element()
340    }
341}
342
343impl ComponentExampleGroup {
344    /// Create a new group of examples with the given title.
345    pub fn new(examples: Vec<ComponentExample>) -> Self {
346        Self {
347            title: None,
348            examples,
349            grow: false,
350            vertical: false,
351        }
352    }
353
354    /// Create a new group of examples with the given title.
355    pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
356        Self {
357            title: Some(title.into()),
358            examples,
359            grow: false,
360            vertical: false,
361        }
362    }
363
364    /// Set the group to grow to fill the available horizontal space.
365    pub fn grow(mut self) -> Self {
366        self.grow = true;
367        self
368    }
369
370    /// Lay the group out vertically.
371    pub fn vertical(mut self) -> Self {
372        self.vertical = true;
373        self
374    }
375}
376
377/// Create a single example
378pub fn single_example(
379    variant_name: impl Into<SharedString>,
380    example: AnyElement,
381) -> ComponentExample {
382    ComponentExample::new(variant_name, example)
383}
384
385/// Create a group of examples without a title
386pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
387    ComponentExampleGroup::new(examples)
388}
389
390/// Create a group of examples with a title
391pub fn example_group_with_title(
392    title: impl Into<SharedString>,
393    examples: Vec<ComponentExample>,
394) -> ComponentExampleGroup {
395    ComponentExampleGroup::with_title(title, examples)
396}