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