theme_preview.rs

  1#![allow(unused, dead_code)]
  2use gpui::{
  3    AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, Task, actions, hsla,
  4};
  5use strum::IntoEnumIterator;
  6use theme::all_theme_colors;
  7use ui::{
  8    AudioStatus, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
  9    Checkbox, CheckboxWithLabel, CollaboratorAvailability, ContentGroup, DecoratedIcon,
 10    ElevationIndex, Facepile, IconDecoration, Indicator, KeybindingHint, Switch, TintColor,
 11    Tooltip, prelude::*, utils::calculate_contrast_ratio,
 12};
 13
 14use crate::{Item, Workspace};
 15
 16actions!(
 17    dev,
 18    [
 19        /// Opens the theme preview window.
 20        OpenThemePreview
 21    ]
 22);
 23
 24pub fn init(cx: &mut App) {
 25    cx.observe_new(|workspace: &mut Workspace, _, _| {
 26        workspace.register_action(|workspace, _: &OpenThemePreview, window, cx| {
 27            let theme_preview = cx.new(|cx| ThemePreview::new(window, cx));
 28            workspace.add_item_to_active_pane(Box::new(theme_preview), None, true, window, cx)
 29        });
 30    })
 31    .detach();
 32}
 33
 34#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, strum::EnumIter)]
 35enum ThemePreviewPage {
 36    Overview,
 37    Typography,
 38}
 39
 40impl ThemePreviewPage {
 41    pub fn name(&self) -> &'static str {
 42        match self {
 43            Self::Overview => "Overview",
 44            Self::Typography => "Typography",
 45        }
 46    }
 47}
 48
 49struct ThemePreview {
 50    current_page: ThemePreviewPage,
 51    focus_handle: FocusHandle,
 52}
 53
 54impl ThemePreview {
 55    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
 56        Self {
 57            current_page: ThemePreviewPage::Overview,
 58            focus_handle: cx.focus_handle(),
 59        }
 60    }
 61
 62    pub fn view(
 63        &self,
 64        page: ThemePreviewPage,
 65        window: &mut Window,
 66        cx: &mut Context<ThemePreview>,
 67    ) -> impl IntoElement {
 68        match page {
 69            ThemePreviewPage::Overview => self.render_overview_page(window, cx).into_any_element(),
 70            ThemePreviewPage::Typography => {
 71                self.render_typography_page(window, cx).into_any_element()
 72            }
 73        }
 74    }
 75}
 76
 77impl EventEmitter<()> for ThemePreview {}
 78
 79impl Focusable for ThemePreview {
 80    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
 81        self.focus_handle.clone()
 82    }
 83}
 84impl ThemePreview {}
 85
 86impl Item for ThemePreview {
 87    type Event = ();
 88
 89    fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
 90
 91    fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
 92        let name = cx.theme().name.clone();
 93        format!("{} Preview", name).into()
 94    }
 95
 96    fn telemetry_event_text(&self) -> Option<&'static str> {
 97        None
 98    }
 99
100    fn can_split(&self) -> bool {
101        true
102    }
103
104    fn clone_on_split(
105        &self,
106        _workspace_id: Option<crate::WorkspaceId>,
107        window: &mut Window,
108        cx: &mut Context<Self>,
109    ) -> Task<Option<Entity<Self>>>
110    where
111        Self: Sized,
112    {
113        Task::ready(Some(cx.new(|cx| Self::new(window, cx))))
114    }
115}
116
117const AVATAR_URL: &str = "https://avatars.githubusercontent.com/u/1714999?v=4";
118
119impl ThemePreview {
120    fn preview_bg(window: &mut Window, cx: &mut App) -> Hsla {
121        cx.theme().colors().editor_background
122    }
123
124    fn render_text(
125        &self,
126        layer: ElevationIndex,
127        window: &mut Window,
128        cx: &mut Context<Self>,
129    ) -> impl IntoElement {
130        let bg = layer.bg(cx);
131
132        let label_with_contrast = |label: &str, fg: Hsla| {
133            let contrast = calculate_contrast_ratio(fg, bg);
134            format!("{} ({:.2})", label, contrast)
135        };
136
137        v_flex()
138            .gap_1()
139            .child(Headline::new("Text").size(HeadlineSize::Small).color(Color::Muted))
140            .child(
141                h_flex()
142                    .items_start()
143                    .gap_4()
144                    .child(
145                        v_flex()
146                            .gap_1()
147                            .child(Headline::new("Headline Sizes").size(HeadlineSize::Small).color(Color::Muted))
148                            .child(Headline::new("XLarge Headline").size(HeadlineSize::XLarge))
149                            .child(Headline::new("Large Headline").size(HeadlineSize::Large))
150                            .child(Headline::new("Medium Headline").size(HeadlineSize::Medium))
151                            .child(Headline::new("Small Headline").size(HeadlineSize::Small))
152                            .child(Headline::new("XSmall Headline").size(HeadlineSize::XSmall)),
153                    )
154                    .child(
155                        v_flex()
156                            .gap_1()
157                            .child(Headline::new("Text Colors").size(HeadlineSize::Small).color(Color::Muted))
158                            .child(
159                                Label::new(label_with_contrast(
160                                    "Default Text",
161                                    Color::Default.color(cx),
162                                ))
163                                .color(Color::Default),
164                            )
165                            .child(
166                                Label::new(label_with_contrast(
167                                    "Accent Text",
168                                    Color::Accent.color(cx),
169                                ))
170                                .color(Color::Accent),
171                            )
172                            .child(
173                                Label::new(label_with_contrast(
174                                    "Conflict Text",
175                                    Color::Conflict.color(cx),
176                                ))
177                                .color(Color::Conflict),
178                            )
179                            .child(
180                                Label::new(label_with_contrast(
181                                    "Created Text",
182                                    Color::Created.color(cx),
183                                ))
184                                .color(Color::Created),
185                            )
186                            .child(
187                                Label::new(label_with_contrast(
188                                    "Deleted Text",
189                                    Color::Deleted.color(cx),
190                                ))
191                                .color(Color::Deleted),
192                            )
193                            .child(
194                                Label::new(label_with_contrast(
195                                    "Disabled Text",
196                                    Color::Disabled.color(cx),
197                                ))
198                                .color(Color::Disabled),
199                            )
200                            .child(
201                                Label::new(label_with_contrast(
202                                    "Error Text",
203                                    Color::Error.color(cx),
204                                ))
205                                .color(Color::Error),
206                            )
207                            .child(
208                                Label::new(label_with_contrast(
209                                    "Hidden Text",
210                                    Color::Hidden.color(cx),
211                                ))
212                                .color(Color::Hidden),
213                            )
214                            .child(
215                                Label::new(label_with_contrast(
216                                    "Hint Text",
217                                    Color::Hint.color(cx),
218                                ))
219                                .color(Color::Hint),
220                            )
221                            .child(
222                                Label::new(label_with_contrast(
223                                    "Ignored Text",
224                                    Color::Ignored.color(cx),
225                                ))
226                                .color(Color::Ignored),
227                            )
228                            .child(
229                                Label::new(label_with_contrast(
230                                    "Info Text",
231                                    Color::Info.color(cx),
232                                ))
233                                .color(Color::Info),
234                            )
235                            .child(
236                                Label::new(label_with_contrast(
237                                    "Modified Text",
238                                    Color::Modified.color(cx),
239                                ))
240                                .color(Color::Modified),
241                            )
242                            .child(
243                                Label::new(label_with_contrast(
244                                    "Muted Text",
245                                    Color::Muted.color(cx),
246                                ))
247                                .color(Color::Muted),
248                            )
249                            .child(
250                                Label::new(label_with_contrast(
251                                    "Placeholder Text",
252                                    Color::Placeholder.color(cx),
253                                ))
254                                .color(Color::Placeholder),
255                            )
256                            .child(
257                                Label::new(label_with_contrast(
258                                    "Selected Text",
259                                    Color::Selected.color(cx),
260                                ))
261                                .color(Color::Selected),
262                            )
263                            .child(
264                                Label::new(label_with_contrast(
265                                    "Success Text",
266                                    Color::Success.color(cx),
267                                ))
268                                .color(Color::Success),
269                            )
270                            .child(
271                                Label::new(label_with_contrast(
272                                    "Warning Text",
273                                    Color::Warning.color(cx),
274                                ))
275                                .color(Color::Warning),
276                            )
277                    )
278                    .child(
279                        v_flex()
280                            .gap_1()
281                            .child(Headline::new("Wrapping Text").size(HeadlineSize::Small).color(Color::Muted))
282                            .child(
283                                div().max_w(px(200.)).child(
284                                "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."
285                            ))
286                    )
287            )
288    }
289
290    fn render_colors(
291        &self,
292        layer: ElevationIndex,
293        window: &mut Window,
294        cx: &mut Context<Self>,
295    ) -> impl IntoElement {
296        let bg = layer.bg(cx);
297        let all_colors = all_theme_colors(cx);
298
299        v_flex()
300            .gap_1()
301            .child(
302                Headline::new("Colors")
303                    .size(HeadlineSize::Small)
304                    .color(Color::Muted),
305            )
306            .child(
307                h_flex()
308                    .flex_wrap()
309                    .gap_1()
310                    .children(all_colors.into_iter().map(|(color, name)| {
311                        let id = ElementId::Name(format!("{:?}-preview", color).into());
312                        div().size_8().flex_none().child(
313                            ButtonLike::new(id)
314                                .child(
315                                    div()
316                                        .size_8()
317                                        .bg(color)
318                                        .border_1()
319                                        .border_color(cx.theme().colors().border)
320                                        .overflow_hidden(),
321                                )
322                                .size(ButtonSize::None)
323                                .style(ButtonStyle::Transparent)
324                                .tooltip(move |window, cx| {
325                                    let name = name.clone();
326                                    Tooltip::with_meta(name, None, format!("{:?}", color), cx)
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_page_nav(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
396        h_flex()
397            .id("theme-preview-nav")
398            .items_center()
399            .gap_4()
400            .py_2()
401            .bg(Self::preview_bg(window, cx))
402            .children(ThemePreviewPage::iter().map(|p| {
403                Button::new(ElementId::Name(p.name().into()), p.name())
404                    .on_click(cx.listener(move |this, _, window, cx| {
405                        this.current_page = p;
406                        cx.notify();
407                    }))
408                    .toggle_state(p == self.current_page)
409                    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
410            }))
411    }
412}
413
414impl Render for ThemePreview {
415    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
416        v_flex()
417            .id("theme-preview")
418            .key_context("ThemePreview")
419            .items_start()
420            .overflow_hidden()
421            .size_full()
422            .max_h_full()
423            .track_focus(&self.focus_handle)
424            .px_2()
425            .bg(Self::preview_bg(window, cx))
426            .child(self.render_page_nav(window, cx))
427            .child(self.view(self.current_page, window, cx))
428    }
429}