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, "info", &layer.info, 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 }
191
192 fn render_button(
193 button_index: usize,
194 layer_index: usize,
195 text: &'static str,
196 style_set: &StyleSet,
197 style_override: Option<fn(&StyleSet) -> &Style>,
198 cx: &mut RenderContext<'_, Self>,
199 ) -> ElementBox {
200 enum TestBenchButton {}
201 MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
202 let style = if let Some(style_override) = style_override {
203 style_override(&style_set)
204 } else if state.clicked.is_some() {
205 &style_set.pressed
206 } else if state.hovered {
207 &style_set.hovered
208 } else {
209 &style_set.default
210 };
211
212 Self::render_label(text.to_string(), style, cx)
213 .contained()
214 .with_style(ContainerStyle {
215 margin: Margin {
216 top: 4.,
217 bottom: 4.,
218 left: 4.,
219 right: 4.,
220 },
221 padding: Padding {
222 top: 4.,
223 bottom: 4.,
224 left: 4.,
225 right: 4.,
226 },
227 background_color: Some(style.background),
228 border: Border {
229 width: 1.,
230 color: style.border,
231 overlay: false,
232 top: true,
233 bottom: true,
234 left: true,
235 right: true,
236 },
237 corner_radius: 2.,
238 ..Default::default()
239 })
240 .boxed()
241 })
242 .flex(1., true)
243 .boxed()
244 }
245
246 fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
247 let settings = cx.global::<Settings>();
248 let font_cache = cx.font_cache();
249 let family_id = settings.buffer_font_family;
250 let font_size = settings.buffer_font_size;
251 let font_id = font_cache
252 .select_font(family_id, &Default::default())
253 .unwrap();
254
255 let text_style = TextStyle {
256 color: style.foreground,
257 font_family_id: family_id,
258 font_family_name: font_cache.family_name(family_id).unwrap(),
259 font_id,
260 font_size,
261 font_properties: Default::default(),
262 underline: Default::default(),
263 };
264
265 Label::new(text, text_style)
266 }
267
268 fn elevation_style(elevation: &Elevation) -> ContainerStyle {
269 let style = ContainerStyle {
270 margin: Margin {
271 top: 10.,
272 bottom: 10.,
273 left: 10.,
274 right: 10.,
275 },
276 background_color: Some(elevation.bottom.base.default.background),
277 ..Default::default()
278 };
279
280 if elevation.shadow.is_some() {
281 ContainerStyle {
282 padding: Padding {
283 top: 10.,
284 bottom: 10.,
285 left: 10.,
286 right: 10.,
287 },
288 border: Border {
289 width: 1.,
290 color: elevation.bottom.base.default.border,
291 overlay: false,
292 top: true,
293 bottom: true,
294 left: true,
295 right: true,
296 },
297 corner_radius: 32.,
298 shadow: elevation.shadow,
299 ..style
300 }
301 } else {
302 style
303 }
304 }
305}
306
307impl Entity for ThemeTestbench {
308 type Event = ();
309}
310
311impl View for ThemeTestbench {
312 fn ui_name() -> &'static str {
313 "ThemeTestbench"
314 }
315
316 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
317 let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
318
319 Flex::row()
320 .with_child(
321 Self::render_ramps(color_scheme)
322 .contained()
323 .with_margin_right(10.)
324 .flex(0.2, false)
325 .boxed(),
326 )
327 .with_child(
328 Self::render_elevation(0, &color_scheme.lowest, cx)
329 .flex(1., true)
330 .boxed(),
331 )
332 .with_child(
333 Flex::row()
334 .with_child(
335 Self::render_elevation(1, &color_scheme.middle, cx)
336 .flex(1., true)
337 .boxed(),
338 )
339 .with_child(
340 Container::new(
341 Self::render_elevation(2, &color_scheme.highest, cx).boxed(),
342 )
343 .with_style(Self::elevation_style(&color_scheme.highest))
344 .flex(1., true)
345 .boxed(),
346 )
347 .contained()
348 .with_style(Self::elevation_style(&color_scheme.middle))
349 .flex(2., true)
350 .boxed(),
351 )
352 .contained()
353 .with_style(Self::elevation_style(&color_scheme.lowest))
354 .boxed()
355 }
356}
357
358impl Item for ThemeTestbench {
359 fn tab_content(
360 &self,
361 _: Option<usize>,
362 style: &theme::Tab,
363 _: &gpui::AppContext,
364 ) -> gpui::ElementBox {
365 Label::new("Theme Testbench".into(), style.label.clone())
366 .aligned()
367 .contained()
368 .boxed()
369 }
370
371 fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
372 None
373 }
374
375 fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
376 SmallVec::new()
377 }
378
379 fn is_singleton(&self, _: &gpui::AppContext) -> bool {
380 false
381 }
382
383 fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
384
385 fn can_save(&self, _: &gpui::AppContext) -> bool {
386 false
387 }
388
389 fn save(
390 &mut self,
391 _: gpui::ModelHandle<Project>,
392 _: &mut ViewContext<Self>,
393 ) -> gpui::Task<gpui::anyhow::Result<()>> {
394 unreachable!("save should not have been called");
395 }
396
397 fn save_as(
398 &mut self,
399 _: gpui::ModelHandle<Project>,
400 _: std::path::PathBuf,
401 _: &mut ViewContext<Self>,
402 ) -> gpui::Task<gpui::anyhow::Result<()>> {
403 unreachable!("save_as should not have been called");
404 }
405
406 fn reload(
407 &mut self,
408 _: gpui::ModelHandle<Project>,
409 _: &mut ViewContext<Self>,
410 ) -> gpui::Task<gpui::anyhow::Result<()>> {
411 gpui::Task::ready(Ok(()))
412 }
413
414 fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
415 Vec::new()
416 }
417}