1use std::ops::{Deref, DerefMut};
2
3use collections::HashMap;
4use gpui::{div, prelude::*, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
5use linkme::distributed_slice;
6use once_cell::sync::Lazy;
7use parking_lot::RwLock;
8use theme::ActiveTheme;
9
10pub trait Component {
11 fn scope() -> Option<&'static str>;
12 fn name() -> &'static str {
13 std::any::type_name::<Self>()
14 }
15 fn description() -> Option<&'static str> {
16 None
17 }
18}
19
20pub trait ComponentPreview: Component {
21 fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
22}
23
24#[distributed_slice]
25pub static __ALL_COMPONENTS: [fn()] = [..];
26
27#[distributed_slice]
28pub static __ALL_PREVIEWS: [fn()] = [..];
29
30pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
31 Lazy::new(|| RwLock::new(ComponentRegistry::new()));
32
33pub struct ComponentRegistry {
34 components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
35 previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
36}
37
38impl ComponentRegistry {
39 fn new() -> Self {
40 ComponentRegistry {
41 components: Vec::new(),
42 previews: HashMap::default(),
43 }
44 }
45}
46
47pub fn init() {
48 let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
49 let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
50
51 for f in component_fns {
52 f();
53 }
54 for f in preview_fns {
55 f();
56 }
57}
58
59pub fn register_component<T: Component>() {
60 let component_data = (T::scope(), T::name(), T::description());
61 COMPONENT_DATA.write().components.push(component_data);
62}
63
64pub fn register_preview<T: ComponentPreview>() {
65 let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
66 COMPONENT_DATA
67 .write()
68 .previews
69 .insert(preview_data.0, preview_data.1);
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73pub struct ComponentId(pub &'static str);
74
75#[derive(Clone)]
76pub struct ComponentMetadata {
77 name: SharedString,
78 scope: Option<SharedString>,
79 description: Option<SharedString>,
80 preview: Option<fn(&mut Window, &App) -> AnyElement>,
81}
82
83impl ComponentMetadata {
84 pub fn name(&self) -> SharedString {
85 self.name.clone()
86 }
87
88 pub fn scope(&self) -> Option<SharedString> {
89 self.scope.clone()
90 }
91
92 pub fn description(&self) -> Option<SharedString> {
93 self.description.clone()
94 }
95
96 pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
97 self.preview
98 }
99}
100
101pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
102
103impl AllComponents {
104 pub fn new() -> Self {
105 AllComponents(HashMap::default())
106 }
107
108 /// Returns all components with previews
109 pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
110 self.0.values().filter(|c| c.preview.is_some()).collect()
111 }
112
113 /// Returns all components with previews sorted by name
114 pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
115 let mut previews: Vec<ComponentMetadata> =
116 self.all_previews().into_iter().cloned().collect();
117 previews.sort_by_key(|a| a.name());
118 previews
119 }
120
121 /// Returns all components
122 pub fn all(&self) -> Vec<&ComponentMetadata> {
123 self.0.values().collect()
124 }
125
126 /// Returns all components sorted by name
127 pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
128 let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
129 components.sort_by_key(|a| a.name());
130 components
131 }
132}
133
134impl Deref for AllComponents {
135 type Target = HashMap<ComponentId, ComponentMetadata>;
136
137 fn deref(&self) -> &Self::Target {
138 &self.0
139 }
140}
141
142impl DerefMut for AllComponents {
143 fn deref_mut(&mut self) -> &mut Self::Target {
144 &mut self.0
145 }
146}
147
148pub fn components() -> AllComponents {
149 let data = COMPONENT_DATA.read();
150 let mut all_components = AllComponents::new();
151
152 for &(scope, name, description) in &data.components {
153 let scope = scope.map(Into::into);
154 let preview = data.previews.get(name).cloned();
155 all_components.insert(
156 ComponentId(name),
157 ComponentMetadata {
158 name: name.into(),
159 scope,
160 description: description.map(Into::into),
161 preview,
162 },
163 );
164 }
165
166 all_components
167}
168
169/// Which side of the preview to show labels on
170#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
171pub enum ExampleLabelSide {
172 /// Left side
173 Left,
174 /// Right side
175 Right,
176 #[default]
177 /// Top side
178 Top,
179 /// Bottom side
180 Bottom,
181}
182
183/// A single example of a component.
184#[derive(IntoElement)]
185pub struct ComponentExample {
186 variant_name: SharedString,
187 element: AnyElement,
188 label_side: ExampleLabelSide,
189 grow: bool,
190}
191
192impl RenderOnce for ComponentExample {
193 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
194 let base = div().flex();
195
196 let base = match self.label_side {
197 ExampleLabelSide::Right => base.flex_row(),
198 ExampleLabelSide::Left => base.flex_row_reverse(),
199 ExampleLabelSide::Bottom => base.flex_col(),
200 ExampleLabelSide::Top => base.flex_col_reverse(),
201 };
202
203 base.gap_1()
204 .text_xs()
205 .text_color(cx.theme().colors().text_muted)
206 .when(self.grow, |this| this.flex_1())
207 .child(self.element)
208 .child(self.variant_name)
209 .into_any_element()
210 }
211}
212
213impl ComponentExample {
214 /// Create a new example with the given variant name and example value.
215 pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
216 Self {
217 variant_name: variant_name.into(),
218 element,
219 label_side: ExampleLabelSide::default(),
220 grow: false,
221 }
222 }
223
224 /// Set the example to grow to fill the available horizontal space.
225 pub fn grow(mut self) -> Self {
226 self.grow = true;
227 self
228 }
229}
230
231/// A group of component examples.
232#[derive(IntoElement)]
233pub struct ComponentExampleGroup {
234 pub title: Option<SharedString>,
235 pub examples: Vec<ComponentExample>,
236 pub grow: bool,
237}
238
239impl RenderOnce for ComponentExampleGroup {
240 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
241 div()
242 .flex_col()
243 .text_sm()
244 .text_color(cx.theme().colors().text_muted)
245 .when(self.grow, |this| this.w_full().flex_1())
246 .when_some(self.title, |this, title| this.gap_4().child(title))
247 .child(
248 div()
249 .flex()
250 .items_start()
251 .w_full()
252 .gap_6()
253 .children(self.examples)
254 .into_any_element(),
255 )
256 .into_any_element()
257 }
258}
259
260impl ComponentExampleGroup {
261 /// Create a new group of examples with the given title.
262 pub fn new(examples: Vec<ComponentExample>) -> Self {
263 Self {
264 title: None,
265 examples,
266 grow: false,
267 }
268 }
269
270 /// Create a new group of examples with the given title.
271 pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
272 Self {
273 title: Some(title.into()),
274 examples,
275 grow: false,
276 }
277 }
278
279 /// Set the group to grow to fill the available horizontal space.
280 pub fn grow(mut self) -> Self {
281 self.grow = true;
282 self
283 }
284}
285
286/// Create a single example
287pub fn single_example(
288 variant_name: impl Into<SharedString>,
289 example: AnyElement,
290) -> ComponentExample {
291 ComponentExample::new(variant_name, example)
292}
293
294/// Create a group of examples without a title
295pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
296 ComponentExampleGroup::new(examples)
297}
298
299/// Create a group of examples with a title
300pub fn example_group_with_title(
301 title: impl Into<SharedString>,
302 examples: Vec<ComponentExample>,
303) -> ComponentExampleGroup {
304 ComponentExampleGroup::with_title(title, examples)
305}