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