theme_testbench.rs

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