theme_preview.rs

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