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}