1use gpui::{
2 actions,
3 color::Color,
4 elements::{
5 Canvas, Container, ContainerStyle, ElementBox, Flex, Label, Margin, MouseEventHandler,
6 Padding, ParentElement,
7 },
8 fonts::TextStyle,
9 AppContext, Border, Element, Entity, ModelHandle, MutableAppContext, Quad, RenderContext, Task,
10 View, ViewContext, ViewHandle, WeakViewHandle,
11};
12use project::Project;
13use settings::Settings;
14use theme::{ColorScheme, Layer, Style, StyleSet};
15use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
16
17actions!(theme, [DeployThemeTestbench]);
18
19pub fn init(cx: &mut MutableAppContext) {
20 cx.add_action(ThemeTestbench::deploy);
21
22 register_deserializable_item::<ThemeTestbench>(cx)
23}
24
25pub struct ThemeTestbench {}
26
27impl ThemeTestbench {
28 pub fn deploy(
29 workspace: &mut Workspace,
30 _: &DeployThemeTestbench,
31 cx: &mut ViewContext<Workspace>,
32 ) {
33 let view = cx.add_view(|_| ThemeTestbench {});
34 workspace.add_item(Box::new(view), cx);
35 }
36
37 fn render_ramps(color_scheme: &ColorScheme) -> Flex {
38 fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
39 Flex::row()
40 .with_children(ramp.iter().cloned().map(|color| {
41 Canvas::new(move |bounds, _, cx| {
42 cx.scene.push_quad(Quad {
43 bounds,
44 background: Some(color),
45 ..Default::default()
46 });
47 })
48 .flex(1.0, false)
49 .boxed()
50 }))
51 .flex(1.0, false)
52 .boxed()
53 }
54
55 Flex::column()
56 .with_child(display_ramp(&color_scheme.ramps.neutral))
57 .with_child(display_ramp(&color_scheme.ramps.red))
58 .with_child(display_ramp(&color_scheme.ramps.orange))
59 .with_child(display_ramp(&color_scheme.ramps.yellow))
60 .with_child(display_ramp(&color_scheme.ramps.green))
61 .with_child(display_ramp(&color_scheme.ramps.cyan))
62 .with_child(display_ramp(&color_scheme.ramps.blue))
63 .with_child(display_ramp(&color_scheme.ramps.violet))
64 .with_child(display_ramp(&color_scheme.ramps.magenta))
65 }
66
67 fn render_layer(
68 layer_index: usize,
69 layer: &Layer,
70 cx: &mut RenderContext<'_, Self>,
71 ) -> Container {
72 Flex::column()
73 .with_child(
74 Self::render_button_set(0, layer_index, "base", &layer.base, cx)
75 .flex(1., false)
76 .boxed(),
77 )
78 .with_child(
79 Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
80 .flex(1., false)
81 .boxed(),
82 )
83 .with_child(
84 Self::render_button_set(2, layer_index, "on", &layer.on, cx)
85 .flex(1., false)
86 .boxed(),
87 )
88 .with_child(
89 Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
90 .flex(1., false)
91 .boxed(),
92 )
93 .with_child(
94 Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
95 .flex(1., false)
96 .boxed(),
97 )
98 .with_child(
99 Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
100 .flex(1., false)
101 .boxed(),
102 )
103 .with_child(
104 Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
105 .flex(1., false)
106 .boxed(),
107 )
108 .contained()
109 .with_style(ContainerStyle {
110 margin: Margin {
111 top: 10.,
112 bottom: 10.,
113 left: 10.,
114 right: 10.,
115 },
116 background_color: Some(layer.base.default.background),
117 ..Default::default()
118 })
119 }
120
121 fn render_button_set(
122 set_index: usize,
123 layer_index: usize,
124 set_name: &'static str,
125 style_set: &StyleSet,
126 cx: &mut RenderContext<'_, Self>,
127 ) -> Flex {
128 Flex::row()
129 .with_child(Self::render_button(
130 set_index * 6,
131 layer_index,
132 set_name,
133 &style_set,
134 None,
135 cx,
136 ))
137 .with_child(Self::render_button(
138 set_index * 6 + 1,
139 layer_index,
140 "hovered",
141 &style_set,
142 Some(|style_set| &style_set.hovered),
143 cx,
144 ))
145 .with_child(Self::render_button(
146 set_index * 6 + 2,
147 layer_index,
148 "pressed",
149 &style_set,
150 Some(|style_set| &style_set.pressed),
151 cx,
152 ))
153 .with_child(Self::render_button(
154 set_index * 6 + 3,
155 layer_index,
156 "active",
157 &style_set,
158 Some(|style_set| &style_set.active),
159 cx,
160 ))
161 .with_child(Self::render_button(
162 set_index * 6 + 4,
163 layer_index,
164 "disabled",
165 &style_set,
166 Some(|style_set| &style_set.disabled),
167 cx,
168 ))
169 .with_child(Self::render_button(
170 set_index * 6 + 5,
171 layer_index,
172 "inverted",
173 &style_set,
174 Some(|style_set| &style_set.inverted),
175 cx,
176 ))
177 }
178
179 fn render_button(
180 button_index: usize,
181 layer_index: usize,
182 text: &'static str,
183 style_set: &StyleSet,
184 style_override: Option<fn(&StyleSet) -> &Style>,
185 cx: &mut RenderContext<'_, Self>,
186 ) -> ElementBox {
187 enum TestBenchButton {}
188 MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
189 let style = if let Some(style_override) = style_override {
190 style_override(&style_set)
191 } else if state.clicked().is_some() {
192 &style_set.pressed
193 } else if state.hovered() {
194 &style_set.hovered
195 } else {
196 &style_set.default
197 };
198
199 Self::render_label(text.to_string(), style, cx)
200 .contained()
201 .with_style(ContainerStyle {
202 margin: Margin {
203 top: 4.,
204 bottom: 4.,
205 left: 4.,
206 right: 4.,
207 },
208 padding: Padding {
209 top: 4.,
210 bottom: 4.,
211 left: 4.,
212 right: 4.,
213 },
214 background_color: Some(style.background),
215 border: Border {
216 width: 1.,
217 color: style.border,
218 overlay: false,
219 top: true,
220 bottom: true,
221 left: true,
222 right: true,
223 },
224 corner_radius: 2.,
225 ..Default::default()
226 })
227 .boxed()
228 })
229 .flex(1., true)
230 .boxed()
231 }
232
233 fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
234 let settings = cx.global::<Settings>();
235 let font_cache = cx.font_cache();
236 let family_id = settings.buffer_font_family;
237 let font_size = settings.buffer_font_size;
238 let font_id = font_cache
239 .select_font(family_id, &Default::default())
240 .unwrap();
241
242 let text_style = TextStyle {
243 color: style.foreground,
244 font_family_id: family_id,
245 font_family_name: font_cache.family_name(family_id).unwrap(),
246 font_id,
247 font_size,
248 font_properties: Default::default(),
249 underline: Default::default(),
250 };
251
252 Label::new(text, text_style)
253 }
254}
255
256impl Entity for ThemeTestbench {
257 type Event = ();
258}
259
260impl View for ThemeTestbench {
261 fn ui_name() -> &'static str {
262 "ThemeTestbench"
263 }
264
265 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
266 let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
267
268 Flex::row()
269 .with_child(
270 Self::render_ramps(color_scheme)
271 .contained()
272 .with_margin_right(10.)
273 .flex(0.1, false)
274 .boxed(),
275 )
276 .with_child(
277 Flex::column()
278 .with_child(
279 Self::render_layer(100, &color_scheme.lowest, cx)
280 .flex(1., true)
281 .boxed(),
282 )
283 .with_child(
284 Self::render_layer(200, &color_scheme.middle, cx)
285 .flex(1., true)
286 .boxed(),
287 )
288 .with_child(
289 Self::render_layer(300, &color_scheme.highest, cx)
290 .flex(1., true)
291 .boxed(),
292 )
293 .flex(1., false)
294 .boxed(),
295 )
296 .boxed()
297 }
298}
299
300impl Item for ThemeTestbench {
301 fn tab_content(
302 &self,
303 _: Option<usize>,
304 style: &theme::Tab,
305 _: &AppContext,
306 ) -> gpui::ElementBox {
307 Label::new("Theme Testbench", style.label.clone())
308 .aligned()
309 .contained()
310 .boxed()
311 }
312
313 fn serialized_item_kind() -> Option<&'static str> {
314 Some("ThemeTestBench")
315 }
316
317 fn deserialize(
318 _project: ModelHandle<Project>,
319 _workspace: WeakViewHandle<Workspace>,
320 _workspace_id: workspace::WorkspaceId,
321 _item_id: workspace::ItemId,
322 cx: &mut ViewContext<Pane>,
323 ) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
324 Task::ready(Ok(cx.add_view(|_| Self {})))
325 }
326}