1use gpui::prelude::*;
14use gpui::{div, px, AnyElement, App, FontWeight, IntoElement, SharedString, Window};
15
16use crate::devtools::Probed;
17use crate::theme::{theme, Size};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum BuildKind {
22 Released,
25 #[default]
27 Development,
28}
29
30impl BuildKind {
31 pub fn line(self, date: &str) -> String {
36 match (self, date.trim()) {
37 (BuildKind::Released, "") | (BuildKind::Released, "unknown") => "Released build".to_string(),
38 (BuildKind::Released, date) => format!("Released {date}"),
39 (BuildKind::Development, "") | (BuildKind::Development, "unknown") => {
40 "Development build".to_string()
41 }
42 (BuildKind::Development, date) => format!("Development build · {date}"),
43 }
44 }
45}
46
47#[derive(IntoElement)]
49pub struct About {
50 name: SharedString,
51 version: Option<SharedString>,
52 icon: Option<AnyElement>,
53 build_date: Option<SharedString>,
54 kind: BuildKind,
55 tagline: Option<SharedString>,
56 credits: Option<SharedString>,
57 links: Vec<AnyElement>,
58}
59
60impl About {
61 pub fn new(name: impl Into<SharedString>) -> Self {
62 About {
63 name: name.into(),
64 version: None,
65 icon: None,
66 build_date: None,
67 kind: BuildKind::default(),
68 tagline: None,
69 credits: None,
70 links: Vec::new(),
71 }
72 }
73
74 pub fn version(mut self, version: impl Into<SharedString>) -> Self {
77 self.version = Some(version.into());
78 self
79 }
80
81 pub fn icon(mut self, icon: impl IntoElement) -> Self {
83 self.icon = Some(icon.into_any_element());
84 self
85 }
86
87 pub fn build(mut self, kind: BuildKind, date: impl Into<SharedString>) -> Self {
90 self.kind = kind;
91 self.build_date = Some(date.into());
92 self
93 }
94
95 pub fn tagline(mut self, tagline: impl Into<SharedString>) -> Self {
97 self.tagline = Some(tagline.into());
98 self
99 }
100
101 pub fn credits(mut self, credits: impl Into<SharedString>) -> Self {
103 self.credits = Some(credits.into());
104 self
105 }
106
107 pub fn link(mut self, link: impl IntoElement) -> Self {
109 self.links.push(link.into_any_element());
110 self
111 }
112}
113
114impl RenderOnce for About {
115 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
116 let t = theme(cx);
117 let body = t.body().hsla();
118 let text = t.text().hsla();
119 let dimmed = t.dimmed().hsla();
120 let font_lg = t.font_size(Size::Lg);
121 let font_sm = t.font_size(Size::Sm);
122 let font_xs = t.font_size(Size::Xs);
123 let gap = t.spacing(Size::Sm);
124
125 let mut card = div()
126 .flex()
127 .flex_col()
128 .items_center()
129 .size_full()
130 .px(px(28.0))
131 .py(px(32.0))
132 .gap(px(gap))
133 .bg(body)
134 .text_color(text)
135 .children(self.icon)
136 .child(
137 div()
138 .text_size(px(font_lg))
139 .font_weight(FontWeight::SEMIBOLD)
140 .child(self.name.clone()),
141 );
142
143 if let Some(tagline) = self.tagline {
144 card = card.child(
145 div()
146 .text_size(px(font_sm))
147 .text_color(dimmed)
148 .child(tagline),
149 );
150 }
151 if let Some(version) = self.version {
152 card = card.child(
153 div()
154 .text_size(px(font_sm))
155 .text_color(dimmed)
156 .child(SharedString::from(format!("Version {version}"))),
157 );
158 }
159 if let Some(date) = self.build_date {
160 card = card.child(
161 div()
162 .text_size(px(font_xs))
163 .text_color(dimmed)
164 .child(SharedString::from(self.kind.line(date.as_ref()))),
165 );
166 }
167 if !self.links.is_empty() {
168 card = card.child(
169 div()
170 .flex()
171 .items_center()
172 .gap(px(gap))
173 .pt(px(gap))
174 .children(self.links),
175 );
176 }
177
178 card = card.child(div().flex_1());
179
180 if let Some(credits) = self.credits {
181 card = card.child(
182 div()
183 .text_size(px(font_xs))
184 .text_color(dimmed)
185 .child(credits),
186 );
187 }
188
189 card.probe("About").attr("name", self.name)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn only_a_released_build_may_call_its_date_a_release_date() {
199 assert_eq!(
200 BuildKind::Released.line("2026-08-18"),
201 "Released 2026-08-18"
202 );
203 assert_eq!(
204 BuildKind::Development.line("2026-08-18"),
205 "Development build · 2026-08-18"
206 );
207 }
208
209 #[test]
210 fn an_unknown_date_reads_as_the_build_kind_alone() {
211 assert_eq!(BuildKind::Development.line("unknown"), "Development build");
212 assert_eq!(BuildKind::Development.line(""), "Development build");
213 assert_eq!(BuildKind::Released.line("unknown"), "Released build");
214 }
215
216 #[test]
217 fn whitespace_is_not_a_date() {
218 assert_eq!(BuildKind::Development.line(" "), "Development build");
219 }
220
221 #[test]
222 fn a_build_is_a_development_build_until_proven_otherwise() {
223 assert_eq!(BuildKind::default(), BuildKind::Development);
224 }
225}