1use std::borrow::Cow;
5use std::rc::Rc;
6
7use crate::hir::Type;
8
9#[derive(Debug)]
11pub struct Element {
12 pub name: &'static str,
13 pub kind: ElementKind,
14 pub params: &'static [Param],
15 pub block: TrailingBlock,
16 pub places: &'static [Place],
18 pub summary: &'static str,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Place {
24 TopLevel,
26 Ui,
28 Menu,
30}
31
32const IN_UI: &[Place] = &[Place::Ui];
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum ElementKind {
37 Window,
38 Column,
39 Row,
40 Scroll,
41 Label,
42 Button,
43 TextField,
44 Checkbox,
45 Switch,
46 Slider,
47 Picker,
48 NavItem,
49 Image,
50 Progress,
51 Spacer,
52 Divider,
53 ContextMenu,
54 DropTarget,
55 Menu,
56 MenuItem,
57}
58
59#[derive(Debug)]
60pub struct Param {
61 pub name: &'static str,
62 pub positional: bool,
64 pub required: bool,
65 pub kind: ParamKind,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum ParamKind {
70 Text,
72 FixedText,
74 Number,
76 Condition,
78 TextList,
80 Points,
82 FixedNumber,
84 Shortcut,
86 Window,
88 Flag,
90 Binding(BoundType),
92 Option(Options),
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum BoundType {
98 String,
99 Bool,
100 Int,
101 Strings,
103}
104
105impl BoundType {
106 #[must_use]
107 pub fn ty(self) -> Type {
108 match self {
109 Self::String => Type::String,
110 Self::Bool => Type::Bool,
111 Self::Int => Type::Int,
112 Self::Strings => Type::List(Rc::new(Type::String)),
113 }
114 }
115
116 #[must_use]
118 pub fn name(self) -> Cow<'static, str> {
119 match self.ty() {
120 Type::List(item) => Cow::Owned(format!("[{}]", primitive_name(&item))),
121 primitive => Cow::Borrowed(primitive_name(&primitive)),
122 }
123 }
124}
125
126fn primitive_name(ty: &Type) -> &'static str {
127 match ty.primitive_name() {
128 Some(name) => name,
129 None => unreachable!("bindings are to primitive types or lists of them"),
130 }
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub enum Options {
136 Alignment,
137 Justification,
138 TextStyle,
139 TextColor,
140 TextAlignment,
141 ButtonStyle,
142 Surface,
143 Icon,
144 Backdrop,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub enum TrailingBlock {
150 None,
151 Children,
153 Action,
155 OptionalAction,
157 Menu,
159}
160
161impl ParamKind {
162 #[must_use]
164 pub fn describe(self) -> Cow<'static, str> {
165 Cow::Borrowed(match self {
166 Self::Text => "string",
167 Self::FixedText => "fixed string",
168 Self::Number => "int",
169 Self::TextList => "[string]",
170 Self::Points => "number",
171 Self::FixedNumber => "fixed int",
172 Self::Shortcut => "shortcut",
173 Self::Window => "window name",
174 Self::Flag | Self::Condition => "bool",
175 Self::Option(Options::Alignment) => "alignment",
176 Self::Option(Options::Justification) => "justification",
177 Self::Option(Options::TextStyle) => "text style",
178 Self::Option(Options::TextColor) => "text color",
179 Self::Option(Options::TextAlignment) => "text alignment",
180 Self::Option(Options::ButtonStyle) => "button style",
181 Self::Option(Options::Surface) => "surface",
182 Self::Option(Options::Icon) => "icon",
183 Self::Option(Options::Backdrop) => "backdrop",
184 Self::Binding(bound) => return Cow::Owned(format!("${}", bound.name())),
185 })
186 }
187}
188
189const STACK_PARAMS: &[Param] = &[
190 named("spacing", ParamKind::Points),
191 named("padding", ParamKind::Points),
192 named("align", ParamKind::Option(Options::Alignment)),
193 named("justify", ParamKind::Option(Options::Justification)),
194 named("surface", ParamKind::Option(Options::Surface)),
195 named("width", ParamKind::Points),
196 named("max_width", ParamKind::Points),
197 named("grow", ParamKind::Flag),
198];
199
200const SCROLL_PARAMS: &[Param] = match STACK_PARAMS.split_last() {
203 Some((_grow, rest)) => rest,
204 None => STACK_PARAMS,
205};
206
207pub const ELEMENTS: &[Element] = &[
208 Element {
209 name: WINDOW,
210 kind: ElementKind::Window,
211 params: &[
212 positional("title", ParamKind::FixedText),
213 named("width", ParamKind::Points),
214 named("height", ParamKind::Points),
215 named("backdrop", ParamKind::Option(Options::Backdrop)),
216 named(OPEN, ParamKind::Flag),
217 ],
218 block: TrailingBlock::Children,
219 places: &[Place::TopLevel],
220 summary: "A window; only at the top level. Name it to open and close it from code.",
221 },
222 Element {
223 name: "column",
224 kind: ElementKind::Column,
225 params: STACK_PARAMS,
226 block: TrailingBlock::Children,
227 places: IN_UI,
228 summary: "Stacks its children top to bottom.",
229 },
230 Element {
231 name: "row",
232 kind: ElementKind::Row,
233 params: STACK_PARAMS,
234 block: TrailingBlock::Children,
235 places: IN_UI,
236 summary: "Stacks its children left to right.",
237 },
238 Element {
239 name: "scroll",
240 kind: ElementKind::Scroll,
241 params: SCROLL_PARAMS,
242 block: TrailingBlock::Children,
243 places: IN_UI,
244 summary: "Stacks its children top to bottom in the space left, scrolling them when they're too tall.",
245 },
246 Element {
247 name: "label",
248 kind: ElementKind::Label,
249 params: &[
250 positional("text", ParamKind::Text),
251 named("style", ParamKind::Option(Options::TextStyle)),
252 named("color", ParamKind::Option(Options::TextColor)),
253 named("align", ParamKind::Option(Options::TextAlignment)),
254 named("selectable", ParamKind::Flag),
255 ],
256 block: TrailingBlock::None,
257 places: IN_UI,
258 summary: "Shows text.",
259 },
260 Element {
261 name: "button",
262 kind: ElementKind::Button,
263 params: &[
264 positional("title", ParamKind::Text),
265 named("style", ParamKind::Option(Options::ButtonStyle)),
266 named("icon", ParamKind::Option(Options::Icon)),
267 named("selected", ParamKind::Condition),
268 ],
269 block: TrailingBlock::Action,
270 places: IN_UI,
271 summary: "Runs its block when clicked.",
272 },
273 Element {
274 name: "text_field",
275 kind: ElementKind::TextField,
276 params: &[
277 positional("value", ParamKind::Binding(BoundType::String)),
278 named("placeholder", ParamKind::FixedText),
279 named("lines", ParamKind::FixedNumber),
280 ],
281 block: TrailingBlock::OptionalAction,
282 places: IN_UI,
283 summary: "Edits bound text, on one line or several; its block runs on Return.",
284 },
285 Element {
286 name: "checkbox",
287 kind: ElementKind::Checkbox,
288 params: &[
289 required("checked", ParamKind::Binding(BoundType::Bool)),
290 named("title", ParamKind::Text),
291 ],
292 block: TrailingBlock::None,
293 places: IN_UI,
294 summary: "Toggles a bound `bool`.",
295 },
296 Element {
297 name: "switch",
298 kind: ElementKind::Switch,
299 params: &[
300 required("on", ParamKind::Binding(BoundType::Bool)),
301 named("title", ParamKind::Text),
302 ],
303 block: TrailingBlock::None,
304 places: IN_UI,
305 summary: "Turns a bound `bool` on and off, with a switch at the end of its title.",
306 },
307 Element {
308 name: "slider",
309 kind: ElementKind::Slider,
310 params: &[
311 positional("value", ParamKind::Binding(BoundType::Int)),
312 named("min", ParamKind::FixedNumber),
313 named("max", ParamKind::FixedNumber),
314 ],
315 block: TrailingBlock::None,
316 places: IN_UI,
317 summary: "Picks a bound `int` between `min` and `max` by dragging.",
318 },
319 Element {
320 name: "picker",
321 kind: ElementKind::Picker,
322 params: &[
323 positional("selection", ParamKind::Binding(BoundType::String)),
324 required("options", ParamKind::TextList),
325 ],
326 block: TrailingBlock::None,
327 places: IN_UI,
328 summary: "Picks one of a list of strings from a pop-up menu, into a bound `string`.",
329 },
330 Element {
331 name: "nav_item",
332 kind: ElementKind::NavItem,
333 params: &[
334 positional("title", ParamKind::Text),
335 required("value", ParamKind::Text),
336 required("selection", ParamKind::Binding(BoundType::String)),
337 named("icon", ParamKind::Option(Options::Icon)),
338 named("badge", ParamKind::Text),
339 ],
340 block: TrailingBlock::None,
341 places: IN_UI,
342 summary: "A sidebar row, selected while its binding holds its value.",
343 },
344 Element {
345 name: "image",
346 kind: ElementKind::Image,
347 params: &[
348 positional("path", ParamKind::Text),
349 named("width", ParamKind::Points),
350 named("height", ParamKind::Points),
351 ],
352 block: TrailingBlock::None,
353 places: IN_UI,
354 summary: "Shows the image in a file, keeping its shape.",
355 },
356 Element {
357 name: "progress",
358 kind: ElementKind::Progress,
359 params: &[named("value", ParamKind::Number)],
360 block: TrailingBlock::None,
361 places: IN_UI,
362 summary: "Shows how far along work is, as a percentage; without `value`, that it's busy.",
363 },
364 Element {
365 name: "spacer",
366 kind: ElementKind::Spacer,
367 params: &[],
368 block: TrailingBlock::None,
369 places: IN_UI,
370 summary: "Takes the remaining space.",
371 },
372 Element {
373 name: "divider",
374 kind: ElementKind::Divider,
375 params: &[],
376 block: TrailingBlock::None,
377 places: &[Place::Ui, Place::Menu],
378 summary: "A line between elements, or between a menu's items.",
379 },
380 Element {
381 name: "context_menu",
382 kind: ElementKind::ContextMenu,
383 params: &[],
384 block: TrailingBlock::Menu,
385 places: IN_UI,
386 summary: "A menu that shows when the stack it's in is right-clicked.",
387 },
388 Element {
389 name: "drop_target",
390 kind: ElementKind::DropTarget,
391 params: &[positional(
392 "dropped",
393 ParamKind::Binding(BoundType::Strings),
394 )],
395 block: TrailingBlock::OptionalAction,
396 places: IN_UI,
397 summary: "Takes files and links dropped on the stack it's in, writing their paths and addresses into the bound list, then runs its block.",
398 },
399 Element {
400 name: MENU,
401 kind: ElementKind::Menu,
402 params: &[positional("title", ParamKind::FixedText)],
403 block: TrailingBlock::Menu,
404 places: &[Place::TopLevel, Place::Menu],
405 summary: "A menu of the app's menu bar, or a submenu in one.",
406 },
407 Element {
408 name: "menu_item",
409 kind: ElementKind::MenuItem,
410 params: &[
411 positional("title", ParamKind::FixedText),
412 named("shortcut", ParamKind::Shortcut),
413 ],
414 block: TrailingBlock::Action,
415 places: &[Place::Menu],
416 summary: "A menu's item, which runs its block when chosen or its shortcut is pressed.",
417 },
418];
419
420#[must_use]
422pub fn element(name: &str) -> Option<&'static Element> {
423 ELEMENTS.iter().find(|element| element.name == name)
424}
425
426pub const WINDOW: &str = "window";
428pub const OPEN: &str = "open";
430pub const MENU: &str = "menu";
432
433pub(crate) const fn positional(name: &'static str, kind: ParamKind) -> Param {
434 Param {
435 name,
436 positional: true,
437 required: true,
438 kind,
439 }
440}
441
442pub(crate) const fn named(name: &'static str, kind: ParamKind) -> Param {
443 Param {
444 name,
445 positional: false,
446 required: false,
447 kind,
448 }
449}
450
451pub(crate) const fn required(name: &'static str, kind: ParamKind) -> Param {
452 Param {
453 required: true,
454 ..named(name, kind)
455 }
456}