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 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_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}