1#![allow(unused, dead_code)]
2use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, Hsla};
3use theme::all_theme_colors;
4use ui::{
5 prelude::*, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar,
6 AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, ElevationIndex, Facepile,
7 TintColor, Tooltip,
8};
9
10use crate::{Item, Workspace};
11
12actions!(debug, [OpenThemePreview]);
13
14pub fn init(cx: &mut AppContext) {
15 cx.observe_new_views(|workspace: &mut Workspace, _| {
16 workspace.register_action(|workspace, _: &OpenThemePreview, cx| {
17 let theme_preview = cx.new_view(ThemePreview::new);
18 workspace.add_item_to_active_pane(Box::new(theme_preview), None, true, cx)
19 });
20 })
21 .detach();
22}
23
24struct ThemePreview {
25 focus_handle: FocusHandle,
26}
27
28impl ThemePreview {
29 pub fn new(cx: &mut ViewContext<Self>) -> Self {
30 Self {
31 focus_handle: cx.focus_handle(),
32 }
33 }
34}
35
36impl EventEmitter<()> for ThemePreview {}
37
38impl FocusableView for ThemePreview {
39 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
40 self.focus_handle.clone()
41 }
42}
43impl ThemePreview {}
44
45impl Item for ThemePreview {
46 type Event = ();
47
48 fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
49
50 fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
51 let name = cx.theme().name.clone();
52 Some(format!("{} Preview", name).into())
53 }
54
55 fn telemetry_event_text(&self) -> Option<&'static str> {
56 None
57 }
58
59 fn clone_on_split(
60 &self,
61 _workspace_id: Option<crate::WorkspaceId>,
62 cx: &mut ViewContext<Self>,
63 ) -> Option<gpui::View<Self>>
64 where
65 Self: Sized,
66 {
67 Some(cx.new_view(Self::new))
68 }
69}
70
71const AVATAR_URL: &str = "https://avatars.githubusercontent.com/u/1714999?v=4";
72
73impl ThemePreview {
74 fn preview_bg(cx: &WindowContext) -> Hsla {
75 cx.theme().colors().editor_background
76 }
77
78 fn render_avatars(&self, cx: &ViewContext<Self>) -> impl IntoElement {
79 v_flex()
80 .gap_1()
81 .child(
82 Headline::new("Avatars")
83 .size(HeadlineSize::Small)
84 .color(Color::Muted),
85 )
86 .child(
87 h_flex()
88 .items_start()
89 .gap_4()
90 .child(Avatar::new(AVATAR_URL).size(px(24.)))
91 .child(Avatar::new(AVATAR_URL).size(px(24.)).grayscale(true))
92 .child(
93 Avatar::new(AVATAR_URL)
94 .size(px(24.))
95 .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)),
96 )
97 .child(
98 Avatar::new(AVATAR_URL)
99 .size(px(24.))
100 .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)),
101 )
102 .child(
103 Avatar::new(AVATAR_URL)
104 .size(px(24.))
105 .indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
106 )
107 .child(
108 Avatar::new(AVATAR_URL)
109 .size(px(24.))
110 .indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
111 )
112 .child(
113 Facepile::empty()
114 .child(
115 Avatar::new(AVATAR_URL)
116 .border_color(Self::preview_bg(cx))
117 .size(px(22.))
118 .into_any_element(),
119 )
120 .child(
121 Avatar::new(AVATAR_URL)
122 .border_color(Self::preview_bg(cx))
123 .size(px(22.))
124 .into_any_element(),
125 )
126 .child(
127 Avatar::new(AVATAR_URL)
128 .border_color(Self::preview_bg(cx))
129 .size(px(22.))
130 .into_any_element(),
131 )
132 .child(
133 Avatar::new(AVATAR_URL)
134 .border_color(Self::preview_bg(cx))
135 .size(px(22.))
136 .into_any_element(),
137 ),
138 ),
139 )
140 }
141
142 fn render_buttons(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
143 v_flex()
144 .gap_1()
145 .child(
146 Headline::new("Buttons")
147 .size(HeadlineSize::Small)
148 .color(Color::Muted),
149 )
150 .child(
151 h_flex()
152 .items_start()
153 .gap_px()
154 .child(
155 IconButton::new("icon_button_transparent", IconName::Check)
156 .style(ButtonStyle::Transparent),
157 )
158 .child(
159 IconButton::new("icon_button_subtle", IconName::Check)
160 .style(ButtonStyle::Subtle),
161 )
162 .child(
163 IconButton::new("icon_button_filled", IconName::Check)
164 .style(ButtonStyle::Filled),
165 )
166 .child(
167 IconButton::new("icon_button_selected_accent", IconName::Check)
168 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
169 .selected(true),
170 )
171 .child(IconButton::new("icon_button_selected", IconName::Check).selected(true))
172 .child(
173 IconButton::new("icon_button_positive", IconName::Check)
174 .style(ButtonStyle::Tinted(TintColor::Positive)),
175 )
176 .child(
177 IconButton::new("icon_button_warning", IconName::Check)
178 .style(ButtonStyle::Tinted(TintColor::Warning)),
179 )
180 .child(
181 IconButton::new("icon_button_negative", IconName::Check)
182 .style(ButtonStyle::Tinted(TintColor::Negative)),
183 ),
184 )
185 .child(
186 h_flex()
187 .gap_px()
188 .child(
189 Button::new("button_transparent", "Transparent")
190 .style(ButtonStyle::Transparent),
191 )
192 .child(Button::new("button_subtle", "Subtle").style(ButtonStyle::Subtle))
193 .child(Button::new("button_filled", "Filled").style(ButtonStyle::Filled))
194 .child(
195 Button::new("button_selected", "Selected")
196 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
197 .selected(true),
198 )
199 .child(
200 Button::new("button_selected_tinted", "Selected (Tinted)")
201 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
202 .selected(true),
203 )
204 .child(
205 Button::new("button_positive", "Tint::Positive")
206 .style(ButtonStyle::Tinted(TintColor::Positive)),
207 )
208 .child(
209 Button::new("button_warning", "Tint::Warning")
210 .style(ButtonStyle::Tinted(TintColor::Warning)),
211 )
212 .child(
213 Button::new("button_negative", "Tint::Negative")
214 .style(ButtonStyle::Tinted(TintColor::Negative)),
215 ),
216 )
217 }
218
219 fn render_text(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
220 let bg = layer.bg(cx);
221
222 let label_with_contrast = |label: &str, fg: Hsla| {
223 let contrast = calculate_contrast_ratio(fg, bg);
224 format!("{} ({:.2})", label, contrast)
225 };
226
227 v_flex()
228 .gap_1()
229 .child(Headline::new("Text").size(HeadlineSize::Small).color(Color::Muted))
230 .child(
231 h_flex()
232 .items_start()
233 .gap_4()
234 .child(
235 v_flex()
236 .gap_1()
237 .child(Headline::new("Headline Sizes").size(HeadlineSize::Small).color(Color::Muted))
238 .child(Headline::new("XLarge Headline").size(HeadlineSize::XLarge))
239 .child(Headline::new("Large Headline").size(HeadlineSize::Large))
240 .child(Headline::new("Medium Headline").size(HeadlineSize::Medium))
241 .child(Headline::new("Small Headline").size(HeadlineSize::Small))
242 .child(Headline::new("XSmall Headline").size(HeadlineSize::XSmall)),
243 )
244 .child(
245 v_flex()
246 .gap_1()
247 .child(Headline::new("Text Colors").size(HeadlineSize::Small).color(Color::Muted))
248 .child(
249 Label::new(label_with_contrast(
250 "Default Text",
251 Color::Default.color(cx),
252 ))
253 .color(Color::Default),
254 )
255 .child(
256 Label::new(label_with_contrast(
257 "Accent Text",
258 Color::Accent.color(cx),
259 ))
260 .color(Color::Accent),
261 )
262 .child(
263 Label::new(label_with_contrast(
264 "Conflict Text",
265 Color::Conflict.color(cx),
266 ))
267 .color(Color::Conflict),
268 )
269 .child(
270 Label::new(label_with_contrast(
271 "Created Text",
272 Color::Created.color(cx),
273 ))
274 .color(Color::Created),
275 )
276 .child(
277 Label::new(label_with_contrast(
278 "Deleted Text",
279 Color::Deleted.color(cx),
280 ))
281 .color(Color::Deleted),
282 )
283 .child(
284 Label::new(label_with_contrast(
285 "Disabled Text",
286 Color::Disabled.color(cx),
287 ))
288 .color(Color::Disabled),
289 )
290 .child(
291 Label::new(label_with_contrast(
292 "Error Text",
293 Color::Error.color(cx),
294 ))
295 .color(Color::Error),
296 )
297 .child(
298 Label::new(label_with_contrast(
299 "Hidden Text",
300 Color::Hidden.color(cx),
301 ))
302 .color(Color::Hidden),
303 )
304 .child(
305 Label::new(label_with_contrast(
306 "Hint Text",
307 Color::Hint.color(cx),
308 ))
309 .color(Color::Hint),
310 )
311 .child(
312 Label::new(label_with_contrast(
313 "Ignored Text",
314 Color::Ignored.color(cx),
315 ))
316 .color(Color::Ignored),
317 )
318 .child(
319 Label::new(label_with_contrast(
320 "Info Text",
321 Color::Info.color(cx),
322 ))
323 .color(Color::Info),
324 )
325 .child(
326 Label::new(label_with_contrast(
327 "Modified Text",
328 Color::Modified.color(cx),
329 ))
330 .color(Color::Modified),
331 )
332 .child(
333 Label::new(label_with_contrast(
334 "Muted Text",
335 Color::Muted.color(cx),
336 ))
337 .color(Color::Muted),
338 )
339 .child(
340 Label::new(label_with_contrast(
341 "Placeholder Text",
342 Color::Placeholder.color(cx),
343 ))
344 .color(Color::Placeholder),
345 )
346 .child(
347 Label::new(label_with_contrast(
348 "Selected Text",
349 Color::Selected.color(cx),
350 ))
351 .color(Color::Selected),
352 )
353 .child(
354 Label::new(label_with_contrast(
355 "Success Text",
356 Color::Success.color(cx),
357 ))
358 .color(Color::Success),
359 )
360 .child(
361 Label::new(label_with_contrast(
362 "Warning Text",
363 Color::Warning.color(cx),
364 ))
365 .color(Color::Warning),
366 )
367 )
368 .child(
369 v_flex()
370 .gap_1()
371 .child(Headline::new("Wrapping Text").size(HeadlineSize::Small).color(Color::Muted))
372 .child(
373 div().max_w(px(200.)).child(
374 "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."
375 ))
376 )
377 )
378 }
379
380 fn render_colors(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
381 let bg = layer.bg(cx);
382 let all_colors = all_theme_colors(cx);
383
384 v_flex()
385 .gap_1()
386 .child(
387 Headline::new("Colors")
388 .size(HeadlineSize::Small)
389 .color(Color::Muted),
390 )
391 .child(
392 h_flex()
393 .flex_wrap()
394 .gap_1()
395 .children(all_colors.into_iter().map(|(color, name)| {
396 let id = ElementId::Name(format!("{:?}-preview", color).into());
397 let name = name.clone();
398 div().size_8().flex_none().child(
399 ButtonLike::new(id)
400 .child(
401 div()
402 .size_8()
403 .bg(color)
404 .border_1()
405 .border_color(cx.theme().colors().border)
406 .overflow_hidden(),
407 )
408 .size(ButtonSize::None)
409 .style(ButtonStyle::Transparent)
410 .tooltip(move |cx| {
411 let name = name.clone();
412 Tooltip::with_meta(name, None, format!("{:?}", color), cx)
413 }),
414 )
415 })),
416 )
417 }
418
419 fn render_theme_layer(
420 &self,
421 layer: ElevationIndex,
422 cx: &ViewContext<Self>,
423 ) -> impl IntoElement {
424 v_flex()
425 .p_4()
426 .bg(layer.bg(cx))
427 .text_color(cx.theme().colors().text)
428 .gap_2()
429 .child(Headline::new(layer.clone().to_string()).size(HeadlineSize::Medium))
430 .child(self.render_avatars(cx))
431 .child(self.render_buttons(layer, cx))
432 .child(self.render_text(layer, cx))
433 .child(self.render_colors(layer, cx))
434 }
435}
436
437impl Render for ThemePreview {
438 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
439 v_flex()
440 .id("theme-preview")
441 .key_context("ThemePreview")
442 .overflow_scroll()
443 .size_full()
444 .max_h_full()
445 .p_4()
446 .track_focus(&self.focus_handle)
447 .bg(Self::preview_bg(cx))
448 .gap_4()
449 .child(self.render_theme_layer(ElevationIndex::Background, cx))
450 .child(self.render_theme_layer(ElevationIndex::Surface, cx))
451 .child(self.render_theme_layer(ElevationIndex::EditorSurface, cx))
452 .child(self.render_theme_layer(ElevationIndex::ElevatedSurface, cx))
453 }
454}