1use gpui::{
2 actions,
3 color::Color,
4 elements::{
5 Canvas, Container, ContainerStyle, ElementBox, Flex, Label, Margin, MouseEventHandler,
6 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 Self::render_button_set(0, layer_index, "base", &layer.base, cx)
96 .flex(1., false)
97 .boxed(),
98 )
99 .with_child(
100 Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
101 .flex(1., false)
102 .boxed(),
103 )
104 .with_child(
105 Self::render_button_set(2, layer_index, "on", &layer.on, cx)
106 .flex(1., false)
107 .boxed(),
108 )
109 .with_child(
110 Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
111 .flex(1., false)
112 .boxed(),
113 )
114 .with_child(
115 Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
116 .flex(1., false)
117 .boxed(),
118 )
119 .with_child(
120 Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
121 .flex(1., false)
122 .boxed(),
123 )
124 .with_child(
125 Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
126 .flex(1., false)
127 .boxed(),
128 )
129 .contained()
130 .with_style(ContainerStyle {
131 margin: Margin {
132 top: 10.,
133 bottom: 10.,
134 left: 10.,
135 right: 10.,
136 },
137 background_color: Some(layer.base.default.background),
138 ..Default::default()
139 })
140 }
141
142 fn render_button_set(
143 set_index: usize,
144 layer_index: usize,
145 set_name: &'static str,
146 style_set: &StyleSet,
147 cx: &mut RenderContext<'_, Self>,
148 ) -> Flex {
149 Flex::row()
150 .with_child(Self::render_button(
151 set_index * 6,
152 layer_index,
153 set_name,
154 &style_set,
155 None,
156 cx,
157 ))
158 .with_child(Self::render_button(
159 set_index * 6 + 1,
160 layer_index,
161 "hovered",
162 &style_set,
163 Some(|style_set| &style_set.hovered),
164 cx,
165 ))
166 .with_child(Self::render_button(
167 set_index * 6 + 2,
168 layer_index,
169 "pressed",
170 &style_set,
171 Some(|style_set| &style_set.pressed),
172 cx,
173 ))
174 .with_child(Self::render_button(
175 set_index * 6 + 3,
176 layer_index,
177 "active",
178 &style_set,
179 Some(|style_set| &style_set.active),
180 cx,
181 ))
182 .with_child(Self::render_button(
183 set_index * 6 + 4,
184 layer_index,
185 "disabled",
186 &style_set,
187 Some(|style_set| &style_set.disabled),
188 cx,
189 ))
190 .with_child(Self::render_button(
191 set_index * 6 + 5,
192 layer_index,
193 "inverted",
194 &style_set,
195 Some(|style_set| &style_set.inverted),
196 cx,
197 ))
198 }
199
200 fn render_button(
201 button_index: usize,
202 layer_index: usize,
203 text: &'static str,
204 style_set: &StyleSet,
205 style_override: Option<fn(&StyleSet) -> &Style>,
206 cx: &mut RenderContext<'_, Self>,
207 ) -> ElementBox {
208 enum TestBenchButton {}
209 MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
210 let style = if let Some(style_override) = style_override {
211 style_override(&style_set)
212 } else if state.clicked().is_some() {
213 &style_set.pressed
214 } else if state.hovered() {
215 &style_set.hovered
216 } else {
217 &style_set.default
218 };
219
220 Self::render_label(text.to_string(), style, cx)
221 .contained()
222 .with_style(ContainerStyle {
223 margin: Margin {
224 top: 4.,
225 bottom: 4.,
226 left: 4.,
227 right: 4.,
228 },
229 padding: Padding {
230 top: 4.,
231 bottom: 4.,
232 left: 4.,
233 right: 4.,
234 },
235 background_color: Some(style.background),
236 border: Border {
237 width: 1.,
238 color: style.border,
239 overlay: false,
240 top: true,
241 bottom: true,
242 left: true,
243 right: true,
244 },
245 corner_radius: 2.,
246 ..Default::default()
247 })
248 .boxed()
249 })
250 .flex(1., true)
251 .boxed()
252 }
253
254 fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
255 let settings = cx.global::<Settings>();
256 let font_cache = cx.font_cache();
257 let family_id = settings.buffer_font_family;
258 let font_size = settings.buffer_font_size;
259 let font_id = font_cache
260 .select_font(family_id, &Default::default())
261 .unwrap();
262
263 let text_style = TextStyle {
264 color: style.foreground,
265 font_family_id: family_id,
266 font_family_name: font_cache.family_name(family_id).unwrap(),
267 font_id,
268 font_size,
269 font_properties: Default::default(),
270 underline: Default::default(),
271 };
272
273 Label::new(text, text_style)
274 }
275
276 fn elevation_style(elevation: &Elevation) -> ContainerStyle {
277 let style = ContainerStyle {
278 margin: Margin {
279 top: 10.,
280 bottom: 10.,
281 left: 10.,
282 right: 10.,
283 },
284 background_color: Some(elevation.bottom.base.default.background),
285 ..Default::default()
286 };
287
288 if elevation.shadow.is_some() {
289 ContainerStyle {
290 padding: Padding {
291 top: 10.,
292 bottom: 10.,
293 left: 10.,
294 right: 10.,
295 },
296 border: Border {
297 width: 1.,
298 color: elevation.bottom.base.default.border,
299 overlay: false,
300 top: true,
301 bottom: true,
302 left: true,
303 right: true,
304 },
305 corner_radius: 32.,
306 shadow: elevation.shadow,
307 ..style
308 }
309 } else {
310 style
311 }
312 }
313}
314
315impl Entity for ThemeTestbench {
316 type Event = ();
317}
318
319impl View for ThemeTestbench {
320 fn ui_name() -> &'static str {
321 "ThemeTestbench"
322 }
323
324 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
325 let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
326
327 Flex::row()
328 .with_child(
329 Self::render_ramps(color_scheme)
330 .contained()
331 .with_margin_right(10.)
332 .flex(0.2, false)
333 .boxed(),
334 )
335 .with_child(
336 Self::render_elevation(0, &color_scheme.lowest, cx)
337 .flex(1., true)
338 .boxed(),
339 )
340 .with_child(
341 Flex::row()
342 .with_child(
343 Self::render_elevation(1, &color_scheme.middle, cx)
344 .flex(1., true)
345 .boxed(),
346 )
347 .with_child(
348 Container::new(
349 Self::render_elevation(2, &color_scheme.highest, cx).boxed(),
350 )
351 .with_style(Self::elevation_style(&color_scheme.highest))
352 .flex(1., true)
353 .boxed(),
354 )
355 .contained()
356 .with_style(Self::elevation_style(&color_scheme.middle))
357 .flex(2., true)
358 .boxed(),
359 )
360 .contained()
361 .with_style(Self::elevation_style(&color_scheme.lowest))
362 .boxed()
363 }
364}
365
366impl Item for ThemeTestbench {
367 fn tab_content(
368 &self,
369 _: Option<usize>,
370 style: &theme::Tab,
371 _: &gpui::AppContext,
372 ) -> gpui::ElementBox {
373 Label::new("Theme Testbench".into(), style.label.clone())
374 .aligned()
375 .contained()
376 .boxed()
377 }
378
379 fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
380 None
381 }
382
383 fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
384 SmallVec::new()
385 }
386
387 fn is_singleton(&self, _: &gpui::AppContext) -> bool {
388 false
389 }
390
391 fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
392
393 fn can_save(&self, _: &gpui::AppContext) -> bool {
394 false
395 }
396
397 fn save(
398 &mut self,
399 _: gpui::ModelHandle<Project>,
400 _: &mut ViewContext<Self>,
401 ) -> gpui::Task<gpui::anyhow::Result<()>> {
402 unreachable!("save should not have been called");
403 }
404
405 fn save_as(
406 &mut self,
407 _: gpui::ModelHandle<Project>,
408 _: std::path::PathBuf,
409 _: &mut ViewContext<Self>,
410 ) -> gpui::Task<gpui::anyhow::Result<()>> {
411 unreachable!("save_as should not have been called");
412 }
413
414 fn reload(
415 &mut self,
416 _: gpui::ModelHandle<Project>,
417 _: &mut ViewContext<Self>,
418 ) -> gpui::Task<gpui::anyhow::Result<()>> {
419 gpui::Task::ready(Ok(()))
420 }
421
422 fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
423 Vec::new()
424 }
425}