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