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