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}