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