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