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