theme_preview.rs

  1#![allow(unused, dead_code)]
  2use gpui::{actions, hsla, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, Hsla};
  3use strum::IntoEnumIterator;
  4use theme::all_theme_colors;
  5use ui::{
  6    element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
  7    Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
  8    Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
  9    IconDecoration, Indicator, Switch, Table, TintColor, Tooltip,
 10};
 11
 12use crate::{Item, Workspace};
 13
 14actions!(debug, [OpenThemePreview]);
 15
 16pub fn init(cx: &mut AppContext) {
 17    cx.observe_new_views(|workspace: &mut Workspace, _| {
 18        workspace.register_action(|workspace, _: &OpenThemePreview, cx| {
 19            let theme_preview = cx.new_view(ThemePreview::new);
 20            workspace.add_item_to_active_pane(Box::new(theme_preview), None, true, cx)
 21        });
 22    })
 23    .detach();
 24}
 25
 26#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, strum::EnumIter)]
 27enum ThemePreviewPage {
 28    Overview,
 29    Typography,
 30    Components,
 31}
 32
 33impl ThemePreviewPage {
 34    pub fn name(&self) -> &'static str {
 35        match self {
 36            Self::Overview => "Overview",
 37            Self::Typography => "Typography",
 38            Self::Components => "Components",
 39        }
 40    }
 41}
 42
 43struct ThemePreview {
 44    current_page: ThemePreviewPage,
 45    focus_handle: FocusHandle,
 46}
 47
 48impl ThemePreview {
 49    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 50        Self {
 51            current_page: ThemePreviewPage::Overview,
 52            focus_handle: cx.focus_handle(),
 53        }
 54    }
 55
 56    pub fn view(
 57        &self,
 58        page: ThemePreviewPage,
 59        cx: &mut ViewContext<ThemePreview>,
 60    ) -> impl IntoElement {
 61        match page {
 62            ThemePreviewPage::Overview => self.render_overview_page(cx).into_any_element(),
 63            ThemePreviewPage::Typography => self.render_typography_page(cx).into_any_element(),
 64            ThemePreviewPage::Components => self.render_components_page(cx).into_any_element(),
 65        }
 66    }
 67}
 68
 69impl EventEmitter<()> for ThemePreview {}
 70
 71impl FocusableView for ThemePreview {
 72    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
 73        self.focus_handle.clone()
 74    }
 75}
 76impl ThemePreview {}
 77
 78impl Item for ThemePreview {
 79    type Event = ();
 80
 81    fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
 82
 83    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
 84        let name = cx.theme().name.clone();
 85        Some(format!("{} Preview", name).into())
 86    }
 87
 88    fn telemetry_event_text(&self) -> Option<&'static str> {
 89        None
 90    }
 91
 92    fn clone_on_split(
 93        &self,
 94        _workspace_id: Option<crate::WorkspaceId>,
 95        cx: &mut ViewContext<Self>,
 96    ) -> Option<gpui::View<Self>>
 97    where
 98        Self: Sized,
 99    {
100        Some(cx.new_view(Self::new))
101    }
102}
103
104const AVATAR_URL: &str = "https://avatars.githubusercontent.com/u/1714999?v=4";
105
106impl ThemePreview {
107    fn preview_bg(cx: &WindowContext) -> Hsla {
108        cx.theme().colors().editor_background
109    }
110
111    fn render_text(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
112        let bg = layer.bg(cx);
113
114        let label_with_contrast = |label: &str, fg: Hsla| {
115            let contrast = calculate_contrast_ratio(fg, bg);
116            format!("{} ({:.2})", label, contrast)
117        };
118
119        v_flex()
120            .gap_1()
121            .child(Headline::new("Text").size(HeadlineSize::Small).color(Color::Muted))
122            .child(
123                h_flex()
124                    .items_start()
125                    .gap_4()
126                    .child(
127                        v_flex()
128                            .gap_1()
129                            .child(Headline::new("Headline Sizes").size(HeadlineSize::Small).color(Color::Muted))
130                            .child(Headline::new("XLarge Headline").size(HeadlineSize::XLarge))
131                            .child(Headline::new("Large Headline").size(HeadlineSize::Large))
132                            .child(Headline::new("Medium Headline").size(HeadlineSize::Medium))
133                            .child(Headline::new("Small Headline").size(HeadlineSize::Small))
134                            .child(Headline::new("XSmall Headline").size(HeadlineSize::XSmall)),
135                    )
136                    .child(
137                        v_flex()
138                            .gap_1()
139                            .child(Headline::new("Text Colors").size(HeadlineSize::Small).color(Color::Muted))
140                            .child(
141                                Label::new(label_with_contrast(
142                                    "Default Text",
143                                    Color::Default.color(cx),
144                                ))
145                                .color(Color::Default),
146                            )
147                            .child(
148                                Label::new(label_with_contrast(
149                                    "Accent Text",
150                                    Color::Accent.color(cx),
151                                ))
152                                .color(Color::Accent),
153                            )
154                            .child(
155                                Label::new(label_with_contrast(
156                                    "Conflict Text",
157                                    Color::Conflict.color(cx),
158                                ))
159                                .color(Color::Conflict),
160                            )
161                            .child(
162                                Label::new(label_with_contrast(
163                                    "Created Text",
164                                    Color::Created.color(cx),
165                                ))
166                                .color(Color::Created),
167                            )
168                            .child(
169                                Label::new(label_with_contrast(
170                                    "Deleted Text",
171                                    Color::Deleted.color(cx),
172                                ))
173                                .color(Color::Deleted),
174                            )
175                            .child(
176                                Label::new(label_with_contrast(
177                                    "Disabled Text",
178                                    Color::Disabled.color(cx),
179                                ))
180                                .color(Color::Disabled),
181                            )
182                            .child(
183                                Label::new(label_with_contrast(
184                                    "Error Text",
185                                    Color::Error.color(cx),
186                                ))
187                                .color(Color::Error),
188                            )
189                            .child(
190                                Label::new(label_with_contrast(
191                                    "Hidden Text",
192                                    Color::Hidden.color(cx),
193                                ))
194                                .color(Color::Hidden),
195                            )
196                            .child(
197                                Label::new(label_with_contrast(
198                                    "Hint Text",
199                                    Color::Hint.color(cx),
200                                ))
201                                .color(Color::Hint),
202                            )
203                            .child(
204                                Label::new(label_with_contrast(
205                                    "Ignored Text",
206                                    Color::Ignored.color(cx),
207                                ))
208                                .color(Color::Ignored),
209                            )
210                            .child(
211                                Label::new(label_with_contrast(
212                                    "Info Text",
213                                    Color::Info.color(cx),
214                                ))
215                                .color(Color::Info),
216                            )
217                            .child(
218                                Label::new(label_with_contrast(
219                                    "Modified Text",
220                                    Color::Modified.color(cx),
221                                ))
222                                .color(Color::Modified),
223                            )
224                            .child(
225                                Label::new(label_with_contrast(
226                                    "Muted Text",
227                                    Color::Muted.color(cx),
228                                ))
229                                .color(Color::Muted),
230                            )
231                            .child(
232                                Label::new(label_with_contrast(
233                                    "Placeholder Text",
234                                    Color::Placeholder.color(cx),
235                                ))
236                                .color(Color::Placeholder),
237                            )
238                            .child(
239                                Label::new(label_with_contrast(
240                                    "Selected Text",
241                                    Color::Selected.color(cx),
242                                ))
243                                .color(Color::Selected),
244                            )
245                            .child(
246                                Label::new(label_with_contrast(
247                                    "Success Text",
248                                    Color::Success.color(cx),
249                                ))
250                                .color(Color::Success),
251                            )
252                            .child(
253                                Label::new(label_with_contrast(
254                                    "Warning Text",
255                                    Color::Warning.color(cx),
256                                ))
257                                .color(Color::Warning),
258                            )
259                    )
260                    .child(
261                        v_flex()
262                            .gap_1()
263                            .child(Headline::new("Wrapping Text").size(HeadlineSize::Small).color(Color::Muted))
264                            .child(
265                                div().max_w(px(200.)).child(
266                                "This is a longer piece of text that should wrap to multiple lines. It demonstrates how text behaves when it exceeds the width of its container."
267                            ))
268                    )
269            )
270    }
271
272    fn render_colors(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
273        let bg = layer.bg(cx);
274        let all_colors = all_theme_colors(cx);
275
276        v_flex()
277            .gap_1()
278            .child(
279                Headline::new("Colors")
280                    .size(HeadlineSize::Small)
281                    .color(Color::Muted),
282            )
283            .child(
284                h_flex()
285                    .flex_wrap()
286                    .gap_1()
287                    .children(all_colors.into_iter().map(|(color, name)| {
288                        let id = ElementId::Name(format!("{:?}-preview", color).into());
289                        let name = name.clone();
290                        div().size_8().flex_none().child(
291                            ButtonLike::new(id)
292                                .child(
293                                    div()
294                                        .size_8()
295                                        .bg(color)
296                                        .border_1()
297                                        .border_color(cx.theme().colors().border)
298                                        .overflow_hidden(),
299                                )
300                                .size(ButtonSize::None)
301                                .style(ButtonStyle::Transparent)
302                                .tooltip(move |cx| {
303                                    let name = name.clone();
304                                    Tooltip::with_meta(name, None, format!("{:?}", color), cx)
305                                }),
306                        )
307                    })),
308            )
309    }
310
311    fn render_theme_layer(
312        &self,
313        layer: ElevationIndex,
314        cx: &ViewContext<Self>,
315    ) -> impl IntoElement {
316        v_flex()
317            .p_4()
318            .bg(layer.bg(cx))
319            .text_color(cx.theme().colors().text)
320            .gap_2()
321            .child(Headline::new(layer.clone().to_string()).size(HeadlineSize::Medium))
322            .child(self.render_text(layer, cx))
323            .child(self.render_colors(layer, cx))
324    }
325
326    fn render_overview_page(&self, cx: &ViewContext<Self>) -> impl IntoElement {
327        v_flex()
328            .id("theme-preview-overview")
329            .overflow_scroll()
330            .size_full()
331            .child(
332                v_flex()
333                    .child(Headline::new("Theme Preview").size(HeadlineSize::Large))
334                    .child(div().w_full().text_color(cx.theme().colors().text_muted).child("This view lets you preview a range of UI elements across a theme. Use it for testing out changes to the theme."))
335                    )
336            .child(self.render_theme_layer(ElevationIndex::Background, cx))
337            .child(self.render_theme_layer(ElevationIndex::Surface, cx))
338            .child(self.render_theme_layer(ElevationIndex::EditorSurface, cx))
339            .child(self.render_theme_layer(ElevationIndex::ElevatedSurface, cx))
340    }
341
342    fn render_typography_page(&self, cx: &ViewContext<Self>) -> impl IntoElement {
343        v_flex()
344            .id("theme-preview-typography")
345            .overflow_scroll()
346            .size_full()
347            .child(v_flex()
348                .gap_4()
349                .child(Headline::new("Headline 1").size(HeadlineSize::XLarge))
350                .child(Label::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."))
351                .child(Headline::new("Headline 2").size(HeadlineSize::Large))
352                .child(Label::new("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."))
353                .child(Headline::new("Headline 3").size(HeadlineSize::Medium))
354                .child(Label::new("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."))
355                .child(Headline::new("Headline 4").size(HeadlineSize::Small))
356                .child(Label::new("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
357                .child(Headline::new("Headline 5").size(HeadlineSize::XSmall))
358                .child(Label::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."))
359                .child(Headline::new("Body Text").size(HeadlineSize::Small))
360                .child(Label::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
361            )
362    }
363
364    fn render_components_page(&self, cx: &mut WindowContext) -> impl IntoElement {
365        let layer = ElevationIndex::Surface;
366
367        v_flex()
368            .id("theme-preview-components")
369            .overflow_scroll()
370            .size_full()
371            .gap_2()
372            .child(Button::render_component_previews(cx))
373            .child(Checkbox::render_component_previews(cx))
374            .child(CheckboxWithLabel::render_component_previews(cx))
375            .child(ContentGroup::render_component_previews(cx))
376            .child(DecoratedIcon::render_component_previews(cx))
377            .child(Facepile::render_component_previews(cx))
378            .child(Icon::render_component_previews(cx))
379            .child(IconDecoration::render_component_previews(cx))
380            .child(Indicator::render_component_previews(cx))
381            .child(Switch::render_component_previews(cx))
382            .child(Table::render_component_previews(cx))
383    }
384
385    fn render_page_nav(&self, cx: &ViewContext<Self>) -> impl IntoElement {
386        h_flex()
387            .id("theme-preview-nav")
388            .items_center()
389            .gap_4()
390            .py_2()
391            .bg(Self::preview_bg(cx))
392            .children(ThemePreviewPage::iter().map(|p| {
393                Button::new(ElementId::Name(p.name().into()), p.name())
394                    .on_click(cx.listener(move |this, _, cx| {
395                        this.current_page = p;
396                        cx.notify();
397                    }))
398                    .toggle_state(p == self.current_page)
399                    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
400            }))
401    }
402}
403
404impl Render for ThemePreview {
405    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
406        v_flex()
407            .id("theme-preview")
408            .key_context("ThemePreview")
409            .items_start()
410            .overflow_hidden()
411            .size_full()
412            .max_h_full()
413            .track_focus(&self.focus_handle)
414            .px_2()
415            .bg(Self::preview_bg(cx))
416            .child(self.render_page_nav(cx))
417            .child(self.view(self.current_page, cx))
418    }
419}