1#![allow(unused, dead_code)]
2use gpui::{AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, actions, hsla};
3use strum::IntoEnumIterator;
4use ui::{
5 AudioStatus, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
6 Checkbox, CheckboxWithLabel, CollaboratorAvailability, ContentGroup, DecoratedIcon,
7 ElevationIndex, Facepile, IconDecoration, Indicator, KeybindingHint, Switch, TintColor,
8 Tooltip, prelude::*, utils::calculate_contrast_ratio,
9};
10
11use crate::{Item, Workspace};
12
13actions!(
14 dev,
15 [
16 /// Opens the theme preview window.
17 OpenThemePreview
18 ]
19);
20
21pub fn init(cx: &mut App) {
22 cx.observe_new(|workspace: &mut Workspace, _, _| {
23 workspace.register_action(|workspace, _: &OpenThemePreview, window, cx| {
24 let theme_preview = cx.new(|cx| ThemePreview::new(window, cx));
25 workspace.add_item_to_active_pane(Box::new(theme_preview), None, true, window, cx)
26 });
27 })
28 .detach();
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, strum::EnumIter)]
32enum ThemePreviewPage {
33 Overview,
34 Typography,
35}
36
37impl ThemePreviewPage {
38 pub fn name(&self) -> &'static str {
39 match self {
40 Self::Overview => "Overview",
41 Self::Typography => "Typography",
42 }
43 }
44}
45
46struct ThemePreview {
47 current_page: ThemePreviewPage,
48 focus_handle: FocusHandle,
49}
50
51impl ThemePreview {
52 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
53 Self {
54 current_page: ThemePreviewPage::Overview,
55 focus_handle: cx.focus_handle(),
56 }
57 }
58
59 pub fn view(
60 &self,
61 page: ThemePreviewPage,
62 window: &mut Window,
63 cx: &mut Context<ThemePreview>,
64 ) -> impl IntoElement {
65 match page {
66 ThemePreviewPage::Overview => self.render_overview_page(window, cx).into_any_element(),
67 ThemePreviewPage::Typography => {
68 self.render_typography_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, _detail: usize, cx: &App) -> SharedString {
89 let name = cx.theme().name.clone();
90 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_theme_layer(
284 &self,
285 layer: ElevationIndex,
286 window: &mut Window,
287 cx: &mut Context<Self>,
288 ) -> impl IntoElement {
289 v_flex()
290 .p_4()
291 .bg(layer.bg(cx))
292 .text_color(cx.theme().colors().text)
293 .gap_2()
294 .child(Headline::new(layer.clone().to_string()).size(HeadlineSize::Medium))
295 .child(self.render_text(layer, window, cx))
296 }
297
298 fn render_overview_page(
299 &self,
300 window: &mut Window,
301 cx: &mut Context<Self>,
302 ) -> impl IntoElement {
303 v_flex()
304 .id("theme-preview-overview")
305 .overflow_scroll()
306 .size_full()
307 .child(
308 v_flex()
309 .child(Headline::new("Theme Preview").size(HeadlineSize::Large))
310 .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."))
311 )
312 .child(self.render_theme_layer(ElevationIndex::Background, window, cx))
313 .child(self.render_theme_layer(ElevationIndex::Surface, window, cx))
314 .child(self.render_theme_layer(ElevationIndex::EditorSurface, window, cx))
315 .child(self.render_theme_layer(ElevationIndex::ElevatedSurface, window, cx))
316 }
317
318 fn render_typography_page(
319 &self,
320 window: &mut Window,
321 cx: &mut Context<Self>,
322 ) -> impl IntoElement {
323 v_flex()
324 .id("theme-preview-typography")
325 .overflow_scroll()
326 .size_full()
327 .child(v_flex()
328 .gap_4()
329 .child(Headline::new("Headline 1").size(HeadlineSize::XLarge))
330 .child(Label::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."))
331 .child(Headline::new("Headline 2").size(HeadlineSize::Large))
332 .child(Label::new("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."))
333 .child(Headline::new("Headline 3").size(HeadlineSize::Medium))
334 .child(Label::new("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."))
335 .child(Headline::new("Headline 4").size(HeadlineSize::Small))
336 .child(Label::new("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
337 .child(Headline::new("Headline 5").size(HeadlineSize::XSmall))
338 .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."))
339 .child(Headline::new("Body Text").size(HeadlineSize::Small))
340 .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."))
341 )
342 }
343
344 fn render_page_nav(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
345 h_flex()
346 .id("theme-preview-nav")
347 .items_center()
348 .gap_4()
349 .py_2()
350 .bg(Self::preview_bg(window, cx))
351 .children(ThemePreviewPage::iter().map(|p| {
352 Button::new(ElementId::Name(p.name().into()), p.name())
353 .on_click(cx.listener(move |this, _, window, cx| {
354 this.current_page = p;
355 cx.notify();
356 }))
357 .toggle_state(p == self.current_page)
358 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
359 }))
360 }
361}
362
363impl Render for ThemePreview {
364 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
365 v_flex()
366 .id("theme-preview")
367 .key_context("ThemePreview")
368 .items_start()
369 .overflow_hidden()
370 .size_full()
371 .max_h_full()
372 .track_focus(&self.focus_handle)
373 .px_2()
374 .bg(Self::preview_bg(window, cx))
375 .child(self.render_page_nav(window, cx))
376 .child(self.view(self.current_page, window, cx))
377 }
378}