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