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