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