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    AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task,
 10    View, ViewContext, ViewHandle, WeakViewHandle,
 11};
 12use project::Project;
 13use settings::Settings;
 14use theme::{ColorScheme, Layer, Style, StyleSet};
 15use workspace::{
 16    item::{Item, ItemEvent},
 17    register_deserializable_item, Pane, Workspace,
 18};
 19
 20actions!(theme, [DeployThemeTestbench]);
 21
 22pub fn init(cx: &mut MutableAppContext) {
 23    cx.add_action(ThemeTestbench::deploy);
 24
 25    register_deserializable_item::<ThemeTestbench>(cx)
 26}
 27
 28pub struct ThemeTestbench {}
 29
 30impl ThemeTestbench {
 31    pub fn deploy(
 32        workspace: &mut Workspace,
 33        _: &DeployThemeTestbench,
 34        cx: &mut ViewContext<Workspace>,
 35    ) {
 36        let view = cx.add_view(|_| ThemeTestbench {});
 37        workspace.add_item(Box::new(view), cx);
 38    }
 39
 40    fn render_ramps(color_scheme: &ColorScheme) -> Flex {
 41        fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
 42            Flex::row()
 43                .with_children(ramp.iter().cloned().map(|color| {
 44                    Canvas::new(move |bounds, _, cx| {
 45                        cx.scene.push_quad(Quad {
 46                            bounds,
 47                            background: Some(color),
 48                            ..Default::default()
 49                        });
 50                    })
 51                    .flex(1.0, false)
 52                    .boxed()
 53                }))
 54                .flex(1.0, false)
 55                .boxed()
 56        }
 57
 58        Flex::column()
 59            .with_child(display_ramp(&color_scheme.ramps.neutral))
 60            .with_child(display_ramp(&color_scheme.ramps.red))
 61            .with_child(display_ramp(&color_scheme.ramps.orange))
 62            .with_child(display_ramp(&color_scheme.ramps.yellow))
 63            .with_child(display_ramp(&color_scheme.ramps.green))
 64            .with_child(display_ramp(&color_scheme.ramps.cyan))
 65            .with_child(display_ramp(&color_scheme.ramps.blue))
 66            .with_child(display_ramp(&color_scheme.ramps.violet))
 67            .with_child(display_ramp(&color_scheme.ramps.magenta))
 68    }
 69
 70    fn render_layer(
 71        layer_index: usize,
 72        layer: &Layer,
 73        cx: &mut RenderContext<'_, Self>,
 74    ) -> Container {
 75        Flex::column()
 76            .with_child(
 77                Self::render_button_set(0, layer_index, "base", &layer.base, cx)
 78                    .flex(1., false)
 79                    .boxed(),
 80            )
 81            .with_child(
 82                Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
 83                    .flex(1., false)
 84                    .boxed(),
 85            )
 86            .with_child(
 87                Self::render_button_set(2, layer_index, "on", &layer.on, cx)
 88                    .flex(1., false)
 89                    .boxed(),
 90            )
 91            .with_child(
 92                Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
 93                    .flex(1., false)
 94                    .boxed(),
 95            )
 96            .with_child(
 97                Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
 98                    .flex(1., false)
 99                    .boxed(),
100            )
101            .with_child(
102                Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
103                    .flex(1., false)
104                    .boxed(),
105            )
106            .with_child(
107                Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
108                    .flex(1., false)
109                    .boxed(),
110            )
111            .contained()
112            .with_style(ContainerStyle {
113                margin: Margin {
114                    top: 10.,
115                    bottom: 10.,
116                    left: 10.,
117                    right: 10.,
118                },
119                background_color: Some(layer.base.default.background),
120                ..Default::default()
121            })
122    }
123
124    fn render_button_set(
125        set_index: usize,
126        layer_index: usize,
127        set_name: &'static str,
128        style_set: &StyleSet,
129        cx: &mut RenderContext<'_, Self>,
130    ) -> Flex {
131        Flex::row()
132            .with_child(Self::render_button(
133                set_index * 6,
134                layer_index,
135                set_name,
136                &style_set,
137                None,
138                cx,
139            ))
140            .with_child(Self::render_button(
141                set_index * 6 + 1,
142                layer_index,
143                "hovered",
144                &style_set,
145                Some(|style_set| &style_set.hovered),
146                cx,
147            ))
148            .with_child(Self::render_button(
149                set_index * 6 + 2,
150                layer_index,
151                "pressed",
152                &style_set,
153                Some(|style_set| &style_set.pressed),
154                cx,
155            ))
156            .with_child(Self::render_button(
157                set_index * 6 + 3,
158                layer_index,
159                "active",
160                &style_set,
161                Some(|style_set| &style_set.active),
162                cx,
163            ))
164            .with_child(Self::render_button(
165                set_index * 6 + 4,
166                layer_index,
167                "disabled",
168                &style_set,
169                Some(|style_set| &style_set.disabled),
170                cx,
171            ))
172            .with_child(Self::render_button(
173                set_index * 6 + 5,
174                layer_index,
175                "inverted",
176                &style_set,
177                Some(|style_set| &style_set.inverted),
178                cx,
179            ))
180    }
181
182    fn render_button(
183        button_index: usize,
184        layer_index: usize,
185        text: &'static str,
186        style_set: &StyleSet,
187        style_override: Option<fn(&StyleSet) -> &Style>,
188        cx: &mut RenderContext<'_, Self>,
189    ) -> ElementBox {
190        enum TestBenchButton {}
191        MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
192            let style = if let Some(style_override) = style_override {
193                style_override(&style_set)
194            } else if state.clicked().is_some() {
195                &style_set.pressed
196            } else if state.hovered() {
197                &style_set.hovered
198            } else {
199                &style_set.default
200            };
201
202            Self::render_label(text.to_string(), style, cx)
203                .contained()
204                .with_style(ContainerStyle {
205                    margin: Margin {
206                        top: 4.,
207                        bottom: 4.,
208                        left: 4.,
209                        right: 4.,
210                    },
211                    padding: Padding {
212                        top: 4.,
213                        bottom: 4.,
214                        left: 4.,
215                        right: 4.,
216                    },
217                    background_color: Some(style.background),
218                    border: Border {
219                        width: 1.,
220                        color: style.border,
221                        overlay: false,
222                        top: true,
223                        bottom: true,
224                        left: true,
225                        right: true,
226                    },
227                    corner_radius: 2.,
228                    ..Default::default()
229                })
230                .boxed()
231        })
232        .flex(1., true)
233        .boxed()
234    }
235
236    fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
237        let settings = cx.global::<Settings>();
238        let font_cache = cx.font_cache();
239        let family_id = settings.buffer_font_family;
240        let font_size = settings.buffer_font_size;
241        let font_id = font_cache
242            .select_font(family_id, &Default::default())
243            .unwrap();
244
245        let text_style = TextStyle {
246            color: style.foreground,
247            font_family_id: family_id,
248            font_family_name: font_cache.family_name(family_id).unwrap(),
249            font_id,
250            font_size,
251            font_properties: Default::default(),
252            underline: Default::default(),
253        };
254
255        Label::new(text, text_style)
256    }
257}
258
259impl Entity for ThemeTestbench {
260    type Event = ();
261}
262
263impl View for ThemeTestbench {
264    fn ui_name() -> &'static str {
265        "ThemeTestbench"
266    }
267
268    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
269        let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
270
271        Flex::row()
272            .with_child(
273                Self::render_ramps(color_scheme)
274                    .contained()
275                    .with_margin_right(10.)
276                    .flex(0.1, false)
277                    .boxed(),
278            )
279            .with_child(
280                Flex::column()
281                    .with_child(
282                        Self::render_layer(100, &color_scheme.lowest, cx)
283                            .flex(1., true)
284                            .boxed(),
285                    )
286                    .with_child(
287                        Self::render_layer(200, &color_scheme.middle, cx)
288                            .flex(1., true)
289                            .boxed(),
290                    )
291                    .with_child(
292                        Self::render_layer(300, &color_scheme.highest, cx)
293                            .flex(1., true)
294                            .boxed(),
295                    )
296                    .flex(1., false)
297                    .boxed(),
298            )
299            .boxed()
300    }
301}
302
303impl Item for ThemeTestbench {
304    fn tab_content(
305        &self,
306        _: Option<usize>,
307        style: &theme::Tab,
308        _: &AppContext,
309    ) -> gpui::ElementBox {
310        Label::new("Theme Testbench".into(), style.label.clone())
311            .aligned()
312            .contained()
313            .boxed()
314    }
315
316    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
317
318    fn is_singleton(&self, _: &AppContext) -> bool {
319        false
320    }
321
322    fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
323
324    fn can_save(&self, _: &AppContext) -> bool {
325        false
326    }
327
328    fn save(
329        &mut self,
330        _: gpui::ModelHandle<Project>,
331        _: &mut ViewContext<Self>,
332    ) -> gpui::Task<gpui::anyhow::Result<()>> {
333        unreachable!("save should not have been called");
334    }
335
336    fn save_as(
337        &mut self,
338        _: gpui::ModelHandle<Project>,
339        _: std::path::PathBuf,
340        _: &mut ViewContext<Self>,
341    ) -> gpui::Task<gpui::anyhow::Result<()>> {
342        unreachable!("save_as should not have been called");
343    }
344
345    fn reload(
346        &mut self,
347        _: gpui::ModelHandle<Project>,
348        _: &mut ViewContext<Self>,
349    ) -> gpui::Task<gpui::anyhow::Result<()>> {
350        gpui::Task::ready(Ok(()))
351    }
352
353    fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
354        Vec::new()
355    }
356
357    fn serialized_item_kind() -> Option<&'static str> {
358        Some("ThemeTestBench")
359    }
360
361    fn deserialize(
362        _project: ModelHandle<Project>,
363        _workspace: WeakViewHandle<Workspace>,
364        _workspace_id: workspace::WorkspaceId,
365        _item_id: workspace::ItemId,
366        cx: &mut ViewContext<Pane>,
367    ) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
368        Task::ready(Ok(cx.add_view(|_| Self {})))
369    }
370}