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