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