1use gpui::{
2 actions,
3 color::Color,
4 elements::{
5 Canvas, ConstrainedBox, Container, ContainerStyle, ElementBox, Flex, Label, Margin,
6 MouseEventHandler, Padding, ParentElement,
7 },
8 fonts::TextStyle,
9 Border, Element, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext,
10};
11use project::{Project, ProjectEntryId, ProjectPath};
12use settings::Settings;
13use smallvec::SmallVec;
14use theme::{ColorScheme, Elevation, Layer, Style, StyleSet};
15use workspace::{Item, Workspace};
16
17actions!(theme, [DeployThemeTestbench]);
18
19pub fn init(cx: &mut MutableAppContext) {
20 cx.add_action(ThemeTestbench::deploy);
21}
22
23pub struct ThemeTestbench {}
24
25impl ThemeTestbench {
26 pub fn deploy(
27 workspace: &mut Workspace,
28 _: &DeployThemeTestbench,
29 cx: &mut ViewContext<Workspace>,
30 ) {
31 let view = cx.add_view(|_| ThemeTestbench {});
32 workspace.add_item(Box::new(view), cx);
33 }
34
35 fn render_ramps(color_scheme: &ColorScheme) -> Flex {
36 fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
37 Flex::row()
38 .with_children(ramp.iter().cloned().map(|color| {
39 Canvas::new(move |bounds, _, cx| {
40 cx.scene.push_quad(Quad {
41 bounds,
42 background: Some(color),
43 ..Default::default()
44 });
45 })
46 .flex(1.0, false)
47 .boxed()
48 }))
49 .flex(1.0, false)
50 .boxed()
51 }
52
53 Flex::column()
54 .with_child(display_ramp(&color_scheme.lowest.ramps.neutral))
55 .with_child(display_ramp(&color_scheme.lowest.ramps.red))
56 .with_child(display_ramp(&color_scheme.lowest.ramps.orange))
57 .with_child(display_ramp(&color_scheme.lowest.ramps.yellow))
58 .with_child(display_ramp(&color_scheme.lowest.ramps.green))
59 .with_child(display_ramp(&color_scheme.lowest.ramps.cyan))
60 .with_child(display_ramp(&color_scheme.lowest.ramps.blue))
61 .with_child(display_ramp(&color_scheme.lowest.ramps.violet))
62 .with_child(display_ramp(&color_scheme.lowest.ramps.magenta))
63 }
64
65 fn render_elevation(
66 elevation_index: usize,
67 elevation: &Elevation,
68 cx: &mut RenderContext<'_, Self>,
69 ) -> Flex {
70 Flex::column()
71 .with_child(
72 Self::render_layer(elevation_index * 1000 + 100, &elevation.bottom, cx)
73 .flex(1., true)
74 .boxed(),
75 )
76 .with_child(
77 Self::render_layer(elevation_index * 1000 + 200, &elevation.middle, cx)
78 .flex(1., true)
79 .boxed(),
80 )
81 .with_child(
82 Self::render_layer(elevation_index * 1000 + 300, &elevation.top, cx)
83 .flex(1., true)
84 .boxed(),
85 )
86 }
87
88 fn render_layer(
89 layer_index: usize,
90 layer: &Layer,
91 cx: &mut RenderContext<'_, Self>,
92 ) -> Container {
93 Flex::column()
94 .with_child(
95 Flex::row()
96 .with_child(Self::render_button(
97 0,
98 layer_index,
99 "base",
100 &layer.base,
101 None,
102 cx,
103 ))
104 .with_child(Self::render_button(
105 1,
106 layer_index,
107 "active",
108 &layer.base,
109 Some(|style_set| &style_set.active),
110 cx,
111 ))
112 .with_child(Self::render_button(
113 2,
114 layer_index,
115 "disabled",
116 &layer.base,
117 Some(|style_set| &style_set.disabled),
118 cx,
119 ))
120 .flex(1., false)
121 .boxed(),
122 )
123 .with_child(
124 Flex::row()
125 .with_child(Self::render_button(
126 3,
127 layer_index,
128 "on",
129 &layer.on,
130 None,
131 cx,
132 ))
133 .with_child(Self::render_button(
134 4,
135 layer_index,
136 "active",
137 &layer.on,
138 Some(|style_set| &style_set.active),
139 cx,
140 ))
141 .with_child(Self::render_button(
142 5,
143 layer_index,
144 "disabled",
145 &layer.on,
146 Some(|style_set| &style_set.disabled),
147 cx,
148 ))
149 .flex(1., false)
150 .boxed(),
151 )
152 .with_child(
153 Flex::row()
154 .with_child(Self::render_button(
155 6,
156 layer_index,
157 "info",
158 &layer.info,
159 None,
160 cx,
161 ))
162 .with_child(Self::render_button(
163 7,
164 layer_index,
165 "active",
166 &layer.info,
167 Some(|style_set| &style_set.active),
168 cx,
169 ))
170 .with_child(Self::render_button(
171 8,
172 layer_index,
173 "disabled",
174 &layer.info,
175 Some(|style_set| &style_set.disabled),
176 cx,
177 ))
178 .flex(1., false)
179 .boxed(),
180 )
181 .with_child(
182 Flex::row()
183 .with_child(Self::render_button(
184 9,
185 layer_index,
186 "positive",
187 &layer.positive,
188 None,
189 cx,
190 ))
191 .with_child(Self::render_button(
192 10,
193 layer_index,
194 "active",
195 &layer.positive,
196 Some(|style_set| &style_set.active),
197 cx,
198 ))
199 .with_child(Self::render_button(
200 11,
201 layer_index,
202 "disabled",
203 &layer.positive,
204 Some(|style_set| &style_set.disabled),
205 cx,
206 ))
207 .flex(1., false)
208 .boxed(),
209 )
210 .with_child(
211 Flex::row()
212 .with_child(Self::render_button(
213 12,
214 layer_index,
215 "warning",
216 &layer.warning,
217 None,
218 cx,
219 ))
220 .with_child(Self::render_button(
221 13,
222 layer_index,
223 "active",
224 &layer.warning,
225 Some(|style_set| &style_set.active),
226 cx,
227 ))
228 .with_child(Self::render_button(
229 14,
230 layer_index,
231 "disabled",
232 &layer.warning,
233 Some(|style_set| &style_set.disabled),
234 cx,
235 ))
236 .flex(1., false)
237 .boxed(),
238 )
239 .with_child(
240 Flex::row()
241 .with_child(Self::render_button(
242 15,
243 layer_index,
244 "negative",
245 &layer.negative,
246 None,
247 cx,
248 ))
249 .with_child(Self::render_button(
250 16,
251 layer_index,
252 "active",
253 &layer.negative,
254 Some(|style_set| &style_set.active),
255 cx,
256 ))
257 .with_child(Self::render_button(
258 17,
259 layer_index,
260 "disabled",
261 &layer.negative,
262 Some(|style_set| &style_set.disabled),
263 cx,
264 ))
265 .flex(1., false)
266 .boxed(),
267 )
268 .contained()
269 .with_style(ContainerStyle {
270 margin: Margin {
271 top: 10.,
272 bottom: 10.,
273 left: 10.,
274 right: 10.,
275 },
276 background_color: Some(layer.base.default.background),
277 ..Default::default()
278 })
279 }
280
281 fn render_button(
282 button_index: usize,
283 layer_index: usize,
284 text: &'static str,
285 style_set: &StyleSet,
286 style_override: Option<fn(&StyleSet) -> &Style>,
287 cx: &mut RenderContext<'_, Self>,
288 ) -> ElementBox {
289 enum TestBenchButton {}
290 MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
291 let style = if let Some(style_override) = style_override {
292 style_override(&style_set)
293 } else if state.clicked.is_some() {
294 &style_set.pressed
295 } else if state.hovered {
296 &style_set.hovered
297 } else {
298 &style_set.default
299 };
300
301 Self::render_label(text.to_string(), style, cx)
302 .contained()
303 .with_style(ContainerStyle {
304 margin: Margin {
305 top: 4.,
306 bottom: 4.,
307 left: 4.,
308 right: 4.,
309 },
310 padding: Padding {
311 top: 4.,
312 bottom: 4.,
313 left: 4.,
314 right: 4.,
315 },
316 background_color: Some(style.background),
317 border: Border {
318 width: 1.,
319 color: style.border,
320 overlay: false,
321 top: true,
322 bottom: true,
323 left: true,
324 right: true,
325 },
326 corner_radius: 2.,
327 ..Default::default()
328 })
329 .boxed()
330 })
331 .flex(1., true)
332 .boxed()
333 }
334
335 fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
336 let settings = cx.global::<Settings>();
337 let font_cache = cx.font_cache();
338 let family_id = settings.buffer_font_family;
339 let font_size = settings.buffer_font_size;
340 let font_id = font_cache
341 .select_font(family_id, &Default::default())
342 .unwrap();
343
344 let text_style = TextStyle {
345 color: style.foreground,
346 font_family_id: family_id,
347 font_family_name: font_cache.family_name(family_id).unwrap(),
348 font_id,
349 font_size,
350 font_properties: Default::default(),
351 underline: Default::default(),
352 };
353
354 Label::new(text, text_style)
355 }
356
357 fn elevation_style(elevation: &Elevation) -> ContainerStyle {
358 let style = ContainerStyle {
359 margin: Margin {
360 top: 10.,
361 bottom: 10.,
362 left: 10.,
363 right: 10.,
364 },
365 background_color: Some(elevation.bottom.base.default.background),
366 ..Default::default()
367 };
368
369 if elevation.shadow.is_some() {
370 ContainerStyle {
371 padding: Padding {
372 top: 10.,
373 bottom: 10.,
374 left: 10.,
375 right: 10.,
376 },
377 border: Border {
378 width: 1.,
379 color: elevation.bottom.base.default.border,
380 overlay: false,
381 top: true,
382 bottom: true,
383 left: true,
384 right: true,
385 },
386 corner_radius: 32.,
387 shadow: elevation.shadow,
388 ..style
389 }
390 } else {
391 style
392 }
393 }
394}
395
396impl Entity for ThemeTestbench {
397 type Event = ();
398}
399
400impl View for ThemeTestbench {
401 fn ui_name() -> &'static str {
402 "ThemeTestbench"
403 }
404
405 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
406 let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
407
408 Flex::row()
409 .with_child(
410 Self::render_ramps(color_scheme)
411 .contained()
412 .with_margin_right(10.)
413 .flex(0.2, false)
414 .boxed(),
415 )
416 .with_child(
417 Self::render_elevation(0, &color_scheme.lowest, cx)
418 .flex(1., true)
419 .boxed(),
420 )
421 .with_child(
422 Flex::row()
423 .with_child(
424 Self::render_elevation(1, &color_scheme.middle, cx)
425 .flex(1., true)
426 .boxed(),
427 )
428 .with_child(
429 Container::new(
430 Self::render_elevation(2, &color_scheme.highest, cx).boxed(),
431 )
432 .with_style(Self::elevation_style(&color_scheme.highest))
433 .flex(1., true)
434 .boxed(),
435 )
436 .contained()
437 .with_style(Self::elevation_style(&color_scheme.middle))
438 .flex(2., true)
439 .boxed(),
440 )
441 .contained()
442 .with_style(Self::elevation_style(&color_scheme.lowest))
443 .boxed()
444 }
445}
446
447impl Item for ThemeTestbench {
448 fn tab_content(
449 &self,
450 _: Option<usize>,
451 style: &theme::Tab,
452 _: &gpui::AppContext,
453 ) -> gpui::ElementBox {
454 Label::new("Theme Testbench".into(), style.label.clone())
455 .aligned()
456 .contained()
457 .boxed()
458 }
459
460 fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
461 None
462 }
463
464 fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
465 SmallVec::new()
466 }
467
468 fn is_singleton(&self, _: &gpui::AppContext) -> bool {
469 false
470 }
471
472 fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
473
474 fn can_save(&self, _: &gpui::AppContext) -> bool {
475 false
476 }
477
478 fn save(
479 &mut self,
480 _: gpui::ModelHandle<Project>,
481 _: &mut ViewContext<Self>,
482 ) -> gpui::Task<gpui::anyhow::Result<()>> {
483 unreachable!("save should not have been called");
484 }
485
486 fn save_as(
487 &mut self,
488 _: gpui::ModelHandle<Project>,
489 _: std::path::PathBuf,
490 _: &mut ViewContext<Self>,
491 ) -> gpui::Task<gpui::anyhow::Result<()>> {
492 unreachable!("save_as should not have been called");
493 }
494
495 fn reload(
496 &mut self,
497 _: gpui::ModelHandle<Project>,
498 _: &mut ViewContext<Self>,
499 ) -> gpui::Task<gpui::anyhow::Result<()>> {
500 gpui::Task::ready(Ok(()))
501 }
502
503 fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
504 Vec::new()
505 }
506}