theme_preview.rs

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