theme_testbench.rs

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