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, 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.ramps.neutral))
55 .with_child(display_ramp(&color_scheme.ramps.red))
56 .with_child(display_ramp(&color_scheme.ramps.orange))
57 .with_child(display_ramp(&color_scheme.ramps.yellow))
58 .with_child(display_ramp(&color_scheme.ramps.green))
59 .with_child(display_ramp(&color_scheme.ramps.cyan))
60 .with_child(display_ramp(&color_scheme.ramps.blue))
61 .with_child(display_ramp(&color_scheme.ramps.violet))
62 .with_child(display_ramp(&color_scheme.ramps.magenta))
63 }
64
65 fn render_layer(
66 layer_index: usize,
67 layer: &Layer,
68 cx: &mut RenderContext<'_, Self>,
69 ) -> Container {
70 Flex::column()
71 .with_child(
72 Self::render_button_set(0, layer_index, "base", &layer.base, cx)
73 .flex(1., false)
74 .boxed(),
75 )
76 .with_child(
77 Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
78 .flex(1., false)
79 .boxed(),
80 )
81 .with_child(
82 Self::render_button_set(2, layer_index, "on", &layer.on, cx)
83 .flex(1., false)
84 .boxed(),
85 )
86 .with_child(
87 Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
88 .flex(1., false)
89 .boxed(),
90 )
91 .with_child(
92 Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
93 .flex(1., false)
94 .boxed(),
95 )
96 .with_child(
97 Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
98 .flex(1., false)
99 .boxed(),
100 )
101 .with_child(
102 Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
103 .flex(1., false)
104 .boxed(),
105 )
106 .contained()
107 .with_style(ContainerStyle {
108 margin: Margin {
109 top: 10.,
110 bottom: 10.,
111 left: 10.,
112 right: 10.,
113 },
114 background_color: Some(layer.base.default.background),
115 ..Default::default()
116 })
117 }
118
119 fn render_button_set(
120 set_index: usize,
121 layer_index: usize,
122 set_name: &'static str,
123 style_set: &StyleSet,
124 cx: &mut RenderContext<'_, Self>,
125 ) -> Flex {
126 Flex::row()
127 .with_child(Self::render_button(
128 set_index * 6,
129 layer_index,
130 set_name,
131 &style_set,
132 None,
133 cx,
134 ))
135 .with_child(Self::render_button(
136 set_index * 6 + 1,
137 layer_index,
138 "hovered",
139 &style_set,
140 Some(|style_set| &style_set.hovered),
141 cx,
142 ))
143 .with_child(Self::render_button(
144 set_index * 6 + 2,
145 layer_index,
146 "pressed",
147 &style_set,
148 Some(|style_set| &style_set.pressed),
149 cx,
150 ))
151 .with_child(Self::render_button(
152 set_index * 6 + 3,
153 layer_index,
154 "active",
155 &style_set,
156 Some(|style_set| &style_set.active),
157 cx,
158 ))
159 .with_child(Self::render_button(
160 set_index * 6 + 4,
161 layer_index,
162 "disabled",
163 &style_set,
164 Some(|style_set| &style_set.disabled),
165 cx,
166 ))
167 .with_child(Self::render_button(
168 set_index * 6 + 5,
169 layer_index,
170 "inverted",
171 &style_set,
172 Some(|style_set| &style_set.inverted),
173 cx,
174 ))
175 }
176
177 fn render_button(
178 button_index: usize,
179 layer_index: usize,
180 text: &'static str,
181 style_set: &StyleSet,
182 style_override: Option<fn(&StyleSet) -> &Style>,
183 cx: &mut RenderContext<'_, Self>,
184 ) -> ElementBox {
185 enum TestBenchButton {}
186 MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
187 let style = if let Some(style_override) = style_override {
188 style_override(&style_set)
189 } else if state.clicked().is_some() {
190 &style_set.pressed
191 } else if state.hovered() {
192 &style_set.hovered
193 } else {
194 &style_set.default
195 };
196
197 Self::render_label(text.to_string(), style, cx)
198 .contained()
199 .with_style(ContainerStyle {
200 margin: Margin {
201 top: 4.,
202 bottom: 4.,
203 left: 4.,
204 right: 4.,
205 },
206 padding: Padding {
207 top: 4.,
208 bottom: 4.,
209 left: 4.,
210 right: 4.,
211 },
212 background_color: Some(style.background),
213 border: Border {
214 width: 1.,
215 color: style.border,
216 overlay: false,
217 top: true,
218 bottom: true,
219 left: true,
220 right: true,
221 },
222 corner_radius: 2.,
223 ..Default::default()
224 })
225 .boxed()
226 })
227 .flex(1., true)
228 .boxed()
229 }
230
231 fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
232 let settings = cx.global::<Settings>();
233 let font_cache = cx.font_cache();
234 let family_id = settings.buffer_font_family;
235 let font_size = settings.buffer_font_size;
236 let font_id = font_cache
237 .select_font(family_id, &Default::default())
238 .unwrap();
239
240 let text_style = TextStyle {
241 color: style.foreground,
242 font_family_id: family_id,
243 font_family_name: font_cache.family_name(family_id).unwrap(),
244 font_id,
245 font_size,
246 font_properties: Default::default(),
247 underline: Default::default(),
248 };
249
250 Label::new(text, text_style)
251 }
252}
253
254impl Entity for ThemeTestbench {
255 type Event = ();
256}
257
258impl View for ThemeTestbench {
259 fn ui_name() -> &'static str {
260 "ThemeTestbench"
261 }
262
263 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
264 let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
265
266 Flex::row()
267 .with_child(
268 Self::render_ramps(color_scheme)
269 .contained()
270 .with_margin_right(10.)
271 .flex(0.1, false)
272 .boxed(),
273 )
274 .with_child(
275 Flex::column()
276 .with_child(
277 Self::render_layer(100, &color_scheme.lowest, cx)
278 .flex(1., true)
279 .boxed(),
280 )
281 .with_child(
282 Self::render_layer(200, &color_scheme.middle, cx)
283 .flex(1., true)
284 .boxed(),
285 )
286 .with_child(
287 Self::render_layer(300, &color_scheme.highest, cx)
288 .flex(1., true)
289 .boxed(),
290 )
291 .flex(1., false)
292 .boxed(),
293 )
294 .boxed()
295 }
296}
297
298impl Item for ThemeTestbench {
299 fn tab_content(
300 &self,
301 _: Option<usize>,
302 style: &theme::Tab,
303 _: &gpui::AppContext,
304 ) -> gpui::ElementBox {
305 Label::new("Theme Testbench".into(), style.label.clone())
306 .aligned()
307 .contained()
308 .boxed()
309 }
310
311 fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
312 None
313 }
314
315 fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
316 SmallVec::new()
317 }
318
319 fn is_singleton(&self, _: &gpui::AppContext) -> bool {
320 false
321 }
322
323 fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
324
325 fn can_save(&self, _: &gpui::AppContext) -> bool {
326 false
327 }
328
329 fn save(
330 &mut self,
331 _: gpui::ModelHandle<Project>,
332 _: &mut ViewContext<Self>,
333 ) -> gpui::Task<gpui::anyhow::Result<()>> {
334 unreachable!("save should not have been called");
335 }
336
337 fn save_as(
338 &mut self,
339 _: gpui::ModelHandle<Project>,
340 _: std::path::PathBuf,
341 _: &mut ViewContext<Self>,
342 ) -> gpui::Task<gpui::anyhow::Result<()>> {
343 unreachable!("save_as should not have been called");
344 }
345
346 fn reload(
347 &mut self,
348 _: gpui::ModelHandle<Project>,
349 _: &mut ViewContext<Self>,
350 ) -> gpui::Task<gpui::anyhow::Result<()>> {
351 gpui::Task::ready(Ok(()))
352 }
353
354 fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
355 Vec::new()
356 }
357}