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, "accent", &layer.accent, 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            .with_child(Self::render_button(
191                set_index * 6 + 5,
192                layer_index,
193                "inverted",
194                &style_set,
195                Some(|style_set| &style_set.inverted),
196                cx,
197            ))
198    }
199
200    fn render_button(
201        button_index: usize,
202        layer_index: usize,
203        text: &'static str,
204        style_set: &StyleSet,
205        style_override: Option<fn(&StyleSet) -> &Style>,
206        cx: &mut RenderContext<'_, Self>,
207    ) -> ElementBox {
208        enum TestBenchButton {}
209        MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
210            let style = if let Some(style_override) = style_override {
211                style_override(&style_set)
212            } else if state.clicked().is_some() {
213                &style_set.pressed
214            } else if state.hovered() {
215                &style_set.hovered
216            } else {
217                &style_set.default
218            };
219
220            Self::render_label(text.to_string(), style, cx)
221                .contained()
222                .with_style(ContainerStyle {
223                    margin: Margin {
224                        top: 4.,
225                        bottom: 4.,
226                        left: 4.,
227                        right: 4.,
228                    },
229                    padding: Padding {
230                        top: 4.,
231                        bottom: 4.,
232                        left: 4.,
233                        right: 4.,
234                    },
235                    background_color: Some(style.background),
236                    border: Border {
237                        width: 1.,
238                        color: style.border,
239                        overlay: false,
240                        top: true,
241                        bottom: true,
242                        left: true,
243                        right: true,
244                    },
245                    corner_radius: 2.,
246                    ..Default::default()
247                })
248                .boxed()
249        })
250        .flex(1., true)
251        .boxed()
252    }
253
254    fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
255        let settings = cx.global::<Settings>();
256        let font_cache = cx.font_cache();
257        let family_id = settings.buffer_font_family;
258        let font_size = settings.buffer_font_size;
259        let font_id = font_cache
260            .select_font(family_id, &Default::default())
261            .unwrap();
262
263        let text_style = TextStyle {
264            color: style.foreground,
265            font_family_id: family_id,
266            font_family_name: font_cache.family_name(family_id).unwrap(),
267            font_id,
268            font_size,
269            font_properties: Default::default(),
270            underline: Default::default(),
271        };
272
273        Label::new(text, text_style)
274    }
275
276    fn elevation_style(elevation: &Elevation) -> ContainerStyle {
277        let style = ContainerStyle {
278            margin: Margin {
279                top: 10.,
280                bottom: 10.,
281                left: 10.,
282                right: 10.,
283            },
284            background_color: Some(elevation.bottom.base.default.background),
285            ..Default::default()
286        };
287
288        if elevation.shadow.is_some() {
289            ContainerStyle {
290                padding: Padding {
291                    top: 10.,
292                    bottom: 10.,
293                    left: 10.,
294                    right: 10.,
295                },
296                border: Border {
297                    width: 1.,
298                    color: elevation.bottom.base.default.border,
299                    overlay: false,
300                    top: true,
301                    bottom: true,
302                    left: true,
303                    right: true,
304                },
305                corner_radius: 32.,
306                shadow: elevation.shadow,
307                ..style
308            }
309        } else {
310            style
311        }
312    }
313}
314
315impl Entity for ThemeTestbench {
316    type Event = ();
317}
318
319impl View for ThemeTestbench {
320    fn ui_name() -> &'static str {
321        "ThemeTestbench"
322    }
323
324    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
325        let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
326
327        Flex::row()
328            .with_child(
329                Self::render_ramps(color_scheme)
330                    .contained()
331                    .with_margin_right(10.)
332                    .flex(0.2, false)
333                    .boxed(),
334            )
335            .with_child(
336                Self::render_elevation(0, &color_scheme.lowest, cx)
337                    .flex(1., true)
338                    .boxed(),
339            )
340            .with_child(
341                Flex::row()
342                    .with_child(
343                        Self::render_elevation(1, &color_scheme.middle, cx)
344                            .flex(1., true)
345                            .boxed(),
346                    )
347                    .with_child(
348                        Container::new(
349                            Self::render_elevation(2, &color_scheme.highest, cx).boxed(),
350                        )
351                        .with_style(Self::elevation_style(&color_scheme.highest))
352                        .flex(1., true)
353                        .boxed(),
354                    )
355                    .contained()
356                    .with_style(Self::elevation_style(&color_scheme.middle))
357                    .flex(2., true)
358                    .boxed(),
359            )
360            .contained()
361            .with_style(Self::elevation_style(&color_scheme.lowest))
362            .boxed()
363    }
364}
365
366impl Item for ThemeTestbench {
367    fn tab_content(
368        &self,
369        _: Option<usize>,
370        style: &theme::Tab,
371        _: &gpui::AppContext,
372    ) -> gpui::ElementBox {
373        Label::new("Theme Testbench".into(), style.label.clone())
374            .aligned()
375            .contained()
376            .boxed()
377    }
378
379    fn project_path(&self, _: &gpui::AppContext) -> Option<ProjectPath> {
380        None
381    }
382
383    fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[ProjectEntryId; 3]> {
384        SmallVec::new()
385    }
386
387    fn is_singleton(&self, _: &gpui::AppContext) -> bool {
388        false
389    }
390
391    fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
392
393    fn can_save(&self, _: &gpui::AppContext) -> bool {
394        false
395    }
396
397    fn save(
398        &mut self,
399        _: gpui::ModelHandle<Project>,
400        _: &mut ViewContext<Self>,
401    ) -> gpui::Task<gpui::anyhow::Result<()>> {
402        unreachable!("save should not have been called");
403    }
404
405    fn save_as(
406        &mut self,
407        _: gpui::ModelHandle<Project>,
408        _: std::path::PathBuf,
409        _: &mut ViewContext<Self>,
410    ) -> gpui::Task<gpui::anyhow::Result<()>> {
411        unreachable!("save_as should not have been called");
412    }
413
414    fn reload(
415        &mut self,
416        _: gpui::ModelHandle<Project>,
417        _: &mut ViewContext<Self>,
418    ) -> gpui::Task<gpui::anyhow::Result<()>> {
419        gpui::Task::ready(Ok(()))
420    }
421
422    fn to_item_events(_: &Self::Event) -> Vec<workspace::ItemEvent> {
423        Vec::new()
424    }
425}