theme_testbench.rs

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