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