1use std::ops::{Deref, DerefMut};
2use std::sync::LazyLock;
3
4use collections::HashMap;
5use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
6use linkme::distributed_slice;
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: LazyLock<RwLock<ComponentRegistry>> =
31 LazyLock::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 /// Top side
177 Top,
178 #[default]
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_2()
204 .p_2()
205 .text_size(px(10.))
206 .text_color(cx.theme().colors().text_muted)
207 .when(self.grow, |this| this.flex_1())
208 .child(self.element)
209 .child(self.variant_name)
210 .into_any_element()
211 }
212}
213
214impl ComponentExample {
215 /// Create a new example with the given variant name and example value.
216 pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
217 Self {
218 variant_name: variant_name.into(),
219 element,
220 label_side: ExampleLabelSide::default(),
221 grow: false,
222 }
223 }
224
225 /// Set the example to grow to fill the available horizontal space.
226 pub fn grow(mut self) -> Self {
227 self.grow = true;
228 self
229 }
230}
231
232/// A group of component examples.
233#[derive(IntoElement)]
234pub struct ComponentExampleGroup {
235 pub title: Option<SharedString>,
236 pub examples: Vec<ComponentExample>,
237 pub grow: bool,
238}
239
240impl RenderOnce for ComponentExampleGroup {
241 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
242 div()
243 .flex_col()
244 .text_sm()
245 .text_color(cx.theme().colors().text_muted)
246 .when(self.grow, |this| this.w_full().flex_1())
247 .when_some(self.title, |this, title| {
248 this.gap_4().child(
249 div()
250 .flex()
251 .items_center()
252 .gap_3()
253 .pb_1()
254 .child(div().h_px().w_4().bg(cx.theme().colors().border))
255 .child(
256 div()
257 .flex_none()
258 .text_size(px(10.))
259 .child(title.to_uppercase()),
260 )
261 .child(
262 div()
263 .h_px()
264 .w_full()
265 .flex_1()
266 .bg(cx.theme().colors().border),
267 ),
268 )
269 })
270 .child(
271 div()
272 .flex()
273 .items_start()
274 .w_full()
275 .gap_6()
276 .children(self.examples)
277 .into_any_element(),
278 )
279 .into_any_element()
280 }
281}
282
283impl ComponentExampleGroup {
284 /// Create a new group of examples with the given title.
285 pub fn new(examples: Vec<ComponentExample>) -> Self {
286 Self {
287 title: None,
288 examples,
289 grow: false,
290 }
291 }
292
293 /// Create a new group of examples with the given title.
294 pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
295 Self {
296 title: Some(title.into()),
297 examples,
298 grow: false,
299 }
300 }
301
302 /// Set the group to grow to fill the available horizontal space.
303 pub fn grow(mut self) -> Self {
304 self.grow = true;
305 self
306 }
307}
308
309/// Create a single example
310pub fn single_example(
311 variant_name: impl Into<SharedString>,
312 example: AnyElement,
313) -> ComponentExample {
314 ComponentExample::new(variant_name, example)
315}
316
317/// Create a group of examples without a title
318pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
319 ComponentExampleGroup::new(examples)
320}
321
322/// Create a group of examples with a title
323pub fn example_group_with_title(
324 title: impl Into<SharedString>,
325 examples: Vec<ComponentExample>,
326) -> ComponentExampleGroup {
327 ComponentExampleGroup::with_title(title, examples)
328}