theme_testbench.rs

  1use gpui::{
  2    actions,
  3    color::Color,
  4    elements::{
  5        Canvas, Container, ContainerStyle, Element, Flex, Label, Margin, MouseEventHandler,
  6        Padding, ParentElement,
  7    },
  8    fonts::TextStyle,
  9    AppContext, Border, Drawable, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
 10    WeakViewHandle,
 11};
 12use project::Project;
 13use settings::Settings;
 14use theme::{ColorScheme, Layer, Style, StyleSet};
 15use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
 16
 17actions!(theme, [DeployThemeTestbench]);
 18
 19pub fn init(cx: &mut AppContext) {
 20    cx.add_action(ThemeTestbench::deploy);
 21
 22    register_deserializable_item::<ThemeTestbench>(cx)
 23}
 24
 25pub struct ThemeTestbench {}
 26
 27impl ThemeTestbench {
 28    pub fn deploy(
 29        workspace: &mut Workspace,
 30        _: &DeployThemeTestbench,
 31        cx: &mut ViewContext<Workspace>,
 32    ) {
 33        let view = cx.add_view(|_| ThemeTestbench {});
 34        workspace.add_item(Box::new(view), cx);
 35    }
 36
 37    fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
 38        fn display_ramp(ramp: &Vec<Color>) -> Element<ThemeTestbench> {
 39            Flex::row()
 40                .with_children(ramp.iter().cloned().map(|color| {
 41                    Canvas::new(move |scene, bounds, _, _, _| {
 42                        scene.push_quad(Quad {
 43                            bounds,
 44                            background: Some(color),
 45                            ..Default::default()
 46                        });
 47                    })
 48                    .flex(1.0, false)
 49                    .boxed()
 50                }))
 51                .flex(1.0, false)
 52                .boxed()
 53        }
 54
 55        Flex::column()
 56            .with_child(display_ramp(&color_scheme.ramps.neutral))
 57            .with_child(display_ramp(&color_scheme.ramps.red))
 58            .with_child(display_ramp(&color_scheme.ramps.orange))
 59            .with_child(display_ramp(&color_scheme.ramps.yellow))
 60            .with_child(display_ramp(&color_scheme.ramps.green))
 61            .with_child(display_ramp(&color_scheme.ramps.cyan))
 62            .with_child(display_ramp(&color_scheme.ramps.blue))
 63            .with_child(display_ramp(&color_scheme.ramps.violet))
 64            .with_child(display_ramp(&color_scheme.ramps.magenta))
 65    }
 66
 67    fn render_layer(
 68        layer_index: usize,
 69        layer: &Layer,
 70        cx: &mut ViewContext<Self>,
 71    ) -> Container<Self> {
 72        Flex::column()
 73            .with_child(
 74                Self::render_button_set(0, layer_index, "base", &layer.base, cx)
 75                    .flex(1., false)
 76                    .boxed(),
 77            )
 78            .with_child(
 79                Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
 80                    .flex(1., false)
 81                    .boxed(),
 82            )
 83            .with_child(
 84                Self::render_button_set(2, layer_index, "on", &layer.on, cx)
 85                    .flex(1., false)
 86                    .boxed(),
 87            )
 88            .with_child(
 89                Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
 90                    .flex(1., false)
 91                    .boxed(),
 92            )
 93            .with_child(
 94                Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
 95                    .flex(1., false)
 96                    .boxed(),
 97            )
 98            .with_child(
 99                Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
100                    .flex(1., false)
101                    .boxed(),
102            )
103            .with_child(
104                Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
105                    .flex(1., false)
106                    .boxed(),
107            )
108            .contained()
109            .with_style(ContainerStyle {
110                margin: Margin {
111                    top: 10.,
112                    bottom: 10.,
113                    left: 10.,
114                    right: 10.,
115                },
116                background_color: Some(layer.base.default.background),
117                ..Default::default()
118            })
119    }
120
121    fn render_button_set(
122        set_index: usize,
123        layer_index: usize,
124        set_name: &'static str,
125        style_set: &StyleSet,
126        cx: &mut ViewContext<Self>,
127    ) -> Flex<Self> {
128        Flex::row()
129            .with_child(Self::render_button(
130                set_index * 6,
131                layer_index,
132                set_name,
133                &style_set,
134                None,
135                cx,
136            ))
137            .with_child(Self::render_button(
138                set_index * 6 + 1,
139                layer_index,
140                "hovered",
141                &style_set,
142                Some(|style_set| &style_set.hovered),
143                cx,
144            ))
145            .with_child(Self::render_button(
146                set_index * 6 + 2,
147                layer_index,
148                "pressed",
149                &style_set,
150                Some(|style_set| &style_set.pressed),
151                cx,
152            ))
153            .with_child(Self::render_button(
154                set_index * 6 + 3,
155                layer_index,
156                "active",
157                &style_set,
158                Some(|style_set| &style_set.active),
159                cx,
160            ))
161            .with_child(Self::render_button(
162                set_index * 6 + 4,
163                layer_index,
164                "disabled",
165                &style_set,
166                Some(|style_set| &style_set.disabled),
167                cx,
168            ))
169            .with_child(Self::render_button(
170                set_index * 6 + 5,
171                layer_index,
172                "inverted",
173                &style_set,
174                Some(|style_set| &style_set.inverted),
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 ViewContext<Self>,
186    ) -> Element<Self> {
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 ViewContext<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
256impl Entity for ThemeTestbench {
257    type Event = ();
258}
259
260impl View for ThemeTestbench {
261    fn ui_name() -> &'static str {
262        "ThemeTestbench"
263    }
264
265    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
266        let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
267
268        Flex::row()
269            .with_child(
270                Self::render_ramps(color_scheme)
271                    .contained()
272                    .with_margin_right(10.)
273                    .flex(0.1, false)
274                    .boxed(),
275            )
276            .with_child(
277                Flex::column()
278                    .with_child(
279                        Self::render_layer(100, &color_scheme.lowest, cx)
280                            .flex(1., true)
281                            .boxed(),
282                    )
283                    .with_child(
284                        Self::render_layer(200, &color_scheme.middle, cx)
285                            .flex(1., true)
286                            .boxed(),
287                    )
288                    .with_child(
289                        Self::render_layer(300, &color_scheme.highest, cx)
290                            .flex(1., true)
291                            .boxed(),
292                    )
293                    .flex(1., false)
294                    .boxed(),
295            )
296            .boxed()
297    }
298}
299
300impl Item for ThemeTestbench {
301    fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> Element<Pane> {
302        Label::new("Theme Testbench", style.label.clone())
303            .aligned()
304            .contained()
305            .boxed()
306    }
307
308    fn serialized_item_kind() -> Option<&'static str> {
309        Some("ThemeTestBench")
310    }
311
312    fn deserialize(
313        _project: ModelHandle<Project>,
314        _workspace: WeakViewHandle<Workspace>,
315        _workspace_id: workspace::WorkspaceId,
316        _item_id: workspace::ItemId,
317        cx: &mut ViewContext<Pane>,
318    ) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
319        Task::ready(Ok(cx.add_view(|_| Self {})))
320    }
321}