Skip to main content

tern_sdk/ui/
types.rs

1//! Typed prop values: styled text, enumerations (each with an escape hatch
2//! for values newer than the SDK), sizes and actions.
3
4use serde::{Serialize, Serializer};
5
6use super::data::MenuEntry;
7
8/// Defines a string enumeration with an `Other` escape hatch.
9macro_rules! string_enum {
10	($(#[$m:meta])* $name:ident { $($(#[$vm:meta])* $var:ident = $s:literal),* $(,)? }) => {
11		$(#[$m])*
12		#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13		pub enum $name {
14			$($(#[$vm])* $var,)*
15			/// A value this SDK doesn't name.
16			Other(String),
17		}
18
19		impl $name {
20			/// The value on the wire.
21			pub fn as_str(&self) -> &str {
22				match self {
23					$(Self::$var => $s,)*
24					Self::Other(s) => s,
25				}
26			}
27		}
28
29		impl From<&str> for $name {
30			fn from(s: &str) -> Self {
31				match s {
32					$($s => Self::$var,)*
33					_ => Self::Other(s.to_owned()),
34				}
35			}
36		}
37
38		impl From<String> for $name {
39			fn from(s: String) -> Self {
40				Self::from(s.as_str())
41			}
42		}
43
44		impl ::std::fmt::Display for $name {
45			fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
46				f.write_str(self.as_str())
47			}
48		}
49
50		impl ::serde::Serialize for $name {
51			fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
52				s.serialize_str(self.as_str())
53			}
54		}
55	};
56}
57
58pub(crate) use string_enum;
59
60string_enum!(
61	/// The semantic color of a node's chrome.
62	Tone {
63		/// Gray.
64		Neutral = "neutral",
65		/// The accent.
66		Accent = "accent",
67		/// Informational.
68		Info = "info",
69		/// Success.
70		Success = "success",
71		/// Warning.
72		Warning = "warning",
73		/// Error.
74		Error = "error",
75		/// Pending.
76		Pending = "pending",
77		/// Quiet.
78		Muted = "muted",
79		/// The user's own.
80		User = "user",
81	}
82);
83
84string_enum!(
85	/// The state of a card or tool.
86	Status {
87		/// Not started.
88		Pending = "pending",
89		/// In progress.
90		Running = "running",
91		/// Finished.
92		Done = "done",
93		/// Failed.
94		Error = "error",
95		/// Stopped.
96		Cancelled = "cancelled",
97		/// A tool waiting for approval: Tern draws its `confirm` panel.
98		Ask = "ask",
99	}
100);
101
102string_enum!(
103	/// The state of an agent.
104	AgentStatus {
105		/// Not started.
106		Pending = "pending",
107		/// In progress.
108		Running = "running",
109		/// Finished.
110		Done = "done",
111		/// Failed.
112		Failed = "failed",
113		/// Stopped.
114		Aborted = "aborted",
115		/// Waiting for work.
116		Idle = "idle",
117		/// Parked.
118		Parked = "parked",
119	}
120);
121
122string_enum!(
123	/// A transient selection mark drawn over a node.
124	Mark {
125		/// An accent bar at the node's left.
126		Pick = "pick",
127		/// Dims the node.
128		Drop = "drop",
129	}
130);
131
132string_enum!(
133	/// Space between children, or a spacer's height.
134	Gap {
135		/// None.
136		None = "none",
137		/// 2px.
138		Xs = "xs",
139		/// 6px.
140		Sm = "sm",
141		/// 10px (12px for a spacer).
142		Md = "md",
143		/// 16px (20px for a spacer).
144		Lg = "lg",
145	}
146);
147
148string_enum!(
149	/// Cross-axis alignment of a row's or column's children.
150	Align {
151		/// Start.
152		Start = "start",
153		/// Center.
154		Center = "center",
155		/// End.
156		End = "end",
157		/// Text baselines.
158		Baseline = "baseline",
159		/// Stretch.
160		Stretch = "stretch",
161	}
162);
163
164string_enum!(
165	/// Main-axis distribution of a row's or column's children.
166	Justify {
167		/// Space between.
168		Between = "between",
169		/// Packed at the end.
170		End = "end",
171	}
172);
173
174string_enum!(
175	/// How text wraps.
176	Wrap {
177		/// Between words.
178		Word = "word",
179		/// Anywhere.
180		Char = "char",
181		/// Never.
182		None = "none",
183	}
184);
185
186string_enum!(
187	/// Where overflowing text is cut.
188	Truncate {
189		/// Keep the start.
190		End = "end",
191		/// Keep the end.
192		Start = "start",
193		/// Keep both ends.
194		Middle = "middle",
195	}
196);
197
198string_enum!(
199	/// Text alignment in a table column.
200	TextAlign {
201		/// Start.
202		Start = "start",
203		/// Center.
204		Center = "center",
205		/// End (numbers).
206		End = "end",
207	}
208);
209
210string_enum!(
211	/// A span effect.
212	Fx {
213		/// A highlight sweeping across.
214		Shimmer = "shimmer",
215		/// Fades in and out.
216		Pulse = "pulse",
217		/// None.
218		None = "none",
219	}
220);
221
222string_enum!(
223	/// A span style token (several join with spaces; palette tokens through
224	/// `Other`).
225	Token {
226		/// Quiet text.
227		Muted = "muted",
228		/// Quieter text.
229		Dim = "dim",
230		/// Weight 600.
231		Strong = "strong",
232		/// Italic.
233		Em = "em",
234		/// The accent color.
235		Accent = "accent",
236		/// Success color.
237		Success = "success",
238		/// Warning color.
239		Warning = "warning",
240		/// Error color.
241		Error = "error",
242		/// Info color.
243		Info = "info",
244		/// The `pending` tone's color: waiting on someone.
245		Pending = "pending",
246		/// An inline code chip.
247		Code = "code",
248		/// Tabular figures.
249		Mono = "mono",
250		/// A file path.
251		Path = "path",
252		/// A keycap.
253		Key = "key",
254		/// Link color.
255		Link = "link",
256		/// A number.
257		Num = "num",
258		/// Inserted text.
259		Ins = "ins",
260		/// Deleted text.
261		Del = "del",
262		/// A highlight.
263		Mark = "mark",
264		/// A misspelled word.
265		Typo = "typo",
266		/// A Nerd Font glyph.
267		Icon = "icon",
268		/// Not drawn.
269		Hide = "hide",
270		/// A page's title in its head row (Geist 15px, weight 550).
271		Title = "title",
272	}
273);
274
275string_enum!(
276	/// A spinner's indicator.
277	SpinnerStyle {
278		/// Braille dots.
279		Braille = "braille",
280		/// Three pulsing dots.
281		Dots = "dots",
282		/// omp's thinking glyph.
283		Starburst = "starburst",
284		/// A dot circling a ring.
285		Orbit = "orbit",
286	}
287);
288
289string_enum!(
290	/// How a shimmer sweeps.
291	ShimmerMode {
292		/// Left to right, restarting.
293		Classic = "classic",
294		/// Back and forth.
295		Kitt = "kitt",
296	}
297);
298
299string_enum!(
300	/// How an elapsed timer reads.
301	ElapsedFormat {
302		/// `1m 5s`.
303		Short = "short",
304		/// `1:05`.
305		Clock = "clock",
306	}
307);
308
309string_enum!(
310	/// A meter's shape.
311	MeterStyle {
312		/// A rounded track.
313		Bar = "bar",
314		/// A ring.
315		Ring = "ring",
316		/// A grid of squares.
317		Blocks = "blocks",
318	}
319);
320
321string_enum!(
322	/// Small, medium or large.
323	Size {
324		/// Small.
325		Sm = "sm",
326		/// Medium.
327		Md = "md",
328		/// Large.
329		Lg = "lg",
330	}
331);
332
333string_enum!(
334	/// Which chart.
335	ChartKind {
336		/// A heatmap of cells.
337		Heatmap = "heatmap",
338		/// Bars.
339		Bars = "bars",
340		/// A sparkline.
341		Spark = "spark",
342		/// A line over a value domain, one per series.
343		Line = "line",
344		/// A line with its area filled.
345		Area = "area",
346		/// A ring of segments with the total in its middle.
347		Donut = "donut",
348	}
349);
350
351string_enum!(
352	/// A thinking-effort rung.
353	EffortLevel {
354		/// 0/6.
355		Off = "off",
356		/// 1/6.
357		Minimal = "minimal",
358		/// 2/6.
359		Low = "low",
360		/// 3/6.
361		Medium = "medium",
362		/// 4/6.
363		High = "high",
364		/// 5/6.
365		Xhigh = "xhigh",
366		/// Full, on fire.
367		Max = "max",
368	}
369);
370
371string_enum!(
372	/// How a diff lays out.
373	DiffMode {
374		/// One column.
375		Unified = "unified",
376		/// Side by side.
377		Split = "split",
378		/// The user's setting.
379		Auto = "auto",
380	}
381);
382
383string_enum!(
384	/// How a `kv` lays out.
385	KvLayout {
386		/// Aligned rows.
387		Grid = "grid",
388		/// One wrapping line.
389		Inline = "inline",
390	}
391);
392
393string_enum!(
394	/// A text's line-length cap.
395	Measure {
396		/// 80 cells.
397		Prose = "prose",
398	}
399);
400
401string_enum!(
402	/// A card variant.
403	CardVariant {
404		/// No ring, fill or rounding.
405		Bare = "bare",
406	}
407);
408
409string_enum!(
410	/// A `button`'s chrome.
411	ButtonVariant {
412		/// A quiet chip with a hairline ring (the default).
413		Default = "default",
414		/// The one filled action of a view.
415		Primary = "primary",
416		/// No fill or ring until hovered.
417		Quiet = "quiet",
418		/// A destructive action, tinted `--bad`.
419		Danger = "danger",
420	}
421);
422
423string_enum!(
424	/// A `button`'s height.
425	ButtonSize {
426		/// 22px: inline, in rows.
427		Sm = "sm",
428		/// 28px: bars, heads, forms (the default).
429		Md = "md",
430	}
431);
432
433string_enum!(
434	/// A `tabs` strip's look.
435	TabsVariant {
436		/// An underline under the active tab (the default).
437		Underline = "underline",
438		/// A segmented well, sized to its tabs.
439		Segmented = "segmented",
440	}
441);
442
443/// Whether something is disabled: `true`, or a reason shown as its
444/// tooltip.
445#[derive(Clone, Debug, PartialEq, Eq)]
446pub enum Disabled {
447	/// Enabled or disabled, without a reason.
448	Flag(bool),
449	/// Disabled, with the reason as tooltip.
450	Reason(String),
451}
452
453impl Serialize for Disabled {
454	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
455		match self {
456			Self::Flag(b) => s.serialize_bool(*b),
457			Self::Reason(r) => s.serialize_str(r),
458		}
459	}
460}
461
462impl From<bool> for Disabled {
463	fn from(b: bool) -> Self {
464		Self::Flag(b)
465	}
466}
467
468impl From<&str> for Disabled {
469	fn from(r: &str) -> Self {
470		Self::Reason(r.to_owned())
471	}
472}
473
474impl From<String> for Disabled {
475	fn from(r: String) -> Self {
476		Self::Reason(r)
477	}
478}
479
480string_enum!(
481	/// A picker sheet's size.
482	PickerSize {
483		/// Medium.
484		Md = "md",
485		/// Large.
486		Lg = "lg",
487		/// The whole pane.
488		Screen = "screen",
489	}
490);
491
492string_enum!(
493	/// A picker's row template.
494	PickerLayout {
495		/// Rows.
496		Rows = "rows",
497		/// Cards (detail on its own line).
498		Cards = "cards",
499		/// A node per row.
500		Timeline = "timeline",
501		/// Depth guides and chevrons.
502		Tree = "tree",
503	}
504);
505
506string_enum!(
507	/// Where a picker's preview sits.
508	PickerPreview {
509		/// Beside the list.
510		Side = "side",
511		/// Under the list.
512		Below = "below",
513		/// No preview.
514		None = "none",
515	}
516);
517
518string_enum!(
519	/// Which part of a picker the keys drive.
520	PickerFocus {
521		/// The list.
522		List = "list",
523		/// The scope column.
524		Scopes = "scopes",
525		/// The tab row.
526		Tabs = "tabs",
527		/// The chip strip.
528		Strip = "strip",
529		/// The preview.
530		Preview = "preview",
531	}
532);
533
534string_enum!(
535	/// A picker's state.
536	PickerState {
537		/// Rows shown.
538		Ready = "ready",
539		/// Skeleton rows.
540		Loading = "loading",
541		/// An error card.
542		Error = "error",
543	}
544);
545
546string_enum!(
547	/// How a tool's string target is drawn.
548	TargetKind {
549		/// A shell line.
550		Command = "command",
551		/// A file path.
552		Path = "path",
553		/// A pattern in quotes.
554		Pattern = "pattern",
555		/// A query in quotes.
556		Query = "query",
557		/// Text.
558		Text = "text",
559	}
560);
561
562string_enum!(
563	/// A tool's frame.
564	ToolFrame {
565		/// A ring.
566		Card = "card",
567		/// No ring.
568		Inline = "inline",
569	}
570);
571
572string_enum!(
573	/// A checklist's presentation.
574	ChecklistMode {
575		/// The whole list.
576		Full = "full",
577		/// A pill.
578		Hud = "hud",
579		/// A reminder line.
580		Reminder = "reminder",
581	}
582);
583
584string_enum!(
585	/// A checklist item's state.
586	ItemStatus {
587		/// Not started.
588		Pending = "pending",
589		/// In progress.
590		Active = "active",
591		/// Finished.
592		Done = "done",
593		/// Dropped.
594		Dropped = "dropped",
595		/// Blocked.
596		Blocked = "blocked",
597	}
598);
599
600string_enum!(
601	/// An overlay's size.
602	OverlaySize {
603		/// Small.
604		Sm = "sm",
605		/// Medium.
606		Md = "md",
607		/// Large.
608		Lg = "lg",
609		/// A full sheet.
610		Full = "full",
611	}
612);
613
614string_enum!(
615	/// Which group of a status strip a segment sits in.
616	Side {
617		/// The right group.
618		Right = "right",
619	}
620);
621
622string_enum!(
623	/// A picker column's format.
624	ColumnFormat {
625		/// Text.
626		Text = "text",
627		/// A number.
628		Num = "num",
629		/// A price.
630		Price = "price",
631		/// A meter.
632		Bar = "bar",
633		/// A time.
634		Time = "time",
635		/// A duration.
636		Elapsed = "elapsed",
637		/// Dim text.
638		Dim = "dim",
639	}
640);
641
642string_enum!(
643	/// How a `prefs` choice draws.
644	ChoiceStyle {
645		/// Segments when few and short, else a menu.
646		Auto = "auto",
647		/// Segments.
648		Segmented = "segmented",
649		/// A popup menu.
650		Menu = "menu",
651	}
652);
653
654string_enum!(
655	/// The type of an `el` `input`.
656	InputType {
657		/// A checkbox.
658		Checkbox = "checkbox",
659		/// A radio.
660		Radio = "radio",
661	}
662);
663
664string_enum!(
665	/// An `input`'s or `editor`'s look.
666	InputVariant {
667		/// The tinted one-line well (the default without addons).
668		Well = "well",
669		/// Kit's framed field (the default with an addon).
670		Field = "field",
671	}
672);
673
674string_enum!(
675	/// The edge an overlay docks to as a sheet or drawer.
676	OverlaySide {
677		/// A full-height sheet at the right.
678		Right = "right",
679		/// A full-height sheet at the left.
680		Left = "left",
681		/// A drawer at the bottom.
682		Bottom = "bottom",
683	}
684);
685
686string_enum!(
687	/// How a docked overlay sits at its edge.
688	OverlayVariant {
689		/// Flush with the edge (the default).
690		Edge = "edge",
691		/// Floating 8px off it with rounded corners.
692		Inset = "inset",
693	}
694);
695
696string_enum!(
697	/// A `rule`'s look.
698	RuleVariant {
699		/// A hairline with the label in it (the default).
700		Line = "line",
701		/// A framed row: icon, label, time at the right.
702		Note = "note",
703		/// A pill on the hairline.
704		Pin = "pin",
705	}
706);
707
708string_enum!(
709	/// How cited spans of a `text` look.
710	CiteStyle {
711		/// A superscript number (the default).
712		Chip = "chip",
713		/// A pill naming the source's site.
714		Pill = "pill",
715		/// A number, with the numbered sources under the text.
716		Note = "note",
717	}
718);
719
720string_enum!(
721	/// A sort direction.
722	SortDir {
723		/// Ascending.
724		Asc = "asc",
725		/// Descending.
726		Desc = "desc",
727	}
728);
729
730string_enum!(
731	/// How a `multi` table's picks and sort look.
732	TableVariant {
733		/// A checkbox column (the default).
734		Check = "check",
735		/// An accent bar on picked rows, the sorted header in a pill.
736		Bar = "bar",
737	}
738);
739
740string_enum!(
741	/// The approval look of a tool whose status is `ask`.
742	ToolConfirm {
743		/// A warning-toned frame with Deny, Always and Allow (the default).
744		Card = "card",
745		/// A compact strip under the head.
746		Strip = "strip",
747		/// A radio list of scopes, then Deny and Allow.
748		Scopes = "scopes",
749	}
750);
751
752string_enum!(
753	/// A tool's resolved approval, shown as a head chip.
754	Approved {
755		/// Allowed once.
756		Once = "once",
757		/// Allowed this session.
758		Session = "session",
759		/// Always allowed.
760		Always = "always",
761		/// Denied.
762		Denied = "denied",
763	}
764);
765
766string_enum!(
767	/// A `select`'s look.
768	SelectVariant {
769		/// A framed trigger (the default).
770		Field = "field",
771		/// No fill or ring, the value after a muted label.
772		Quiet = "quiet",
773	}
774);
775
776string_enum!(
777	/// A `switch`'s look.
778	SwitchVariant {
779		/// A 36×22 switch, a labeled one filling its row (the default).
780		Default = "default",
781		/// A 26×15 switch with its label to the right.
782		Compact = "compact",
783		/// An Off | On segmented control.
784		Segment = "segment",
785	}
786);
787
788string_enum!(
789	/// A `slider`'s look.
790	SliderVariant {
791		/// One row: label, track, value (the default).
792		Default = "default",
793		/// The label and value over an accent track.
794		Accent = "accent",
795	}
796);
797
798string_enum!(
799	/// How a `slider`'s value reads.
800	SliderFormat {
801		/// The value with the step's decimals, then `unit` (the default).
802		Number = "number",
803		/// A fraction as a percentage.
804		Percent = "percent",
805		/// Milliseconds as `200 ms`, `1.2 s`, `1 min 30 s`.
806		Duration = "duration",
807	}
808);
809
810string_enum!(
811	/// A `field`'s layout.
812	FieldVariant {
813		/// The label over the control (the default).
814		Stacked = "stacked",
815		/// The label and note left, the control right.
816		Row = "row",
817		/// A right-aligned label column `labelWidth` wide.
818		Grid = "grid",
819	}
820);
821
822string_enum!(
823	/// An `otp`'s look.
824	OtpVariant {
825		/// One cell per character (the default).
826		Cells = "cells",
827		/// Each group one field split into cells.
828		Joined = "joined",
829		/// Digits over rules, no boxes.
830		Underline = "underline",
831	}
832);
833
834string_enum!(
835	/// An `otp`'s state.
836	OtpState {
837		/// Ringed in `--bad`, shaken once.
838		Error = "error",
839		/// Ringed in `--ok`.
840		Success = "success",
841	}
842);
843
844string_enum!(
845	/// A `buttons` group's look.
846	ButtonsVariant {
847		/// Segments sharing one ring (the default).
848		Joined = "joined",
849		/// `joined` for an action and its menu part.
850		Split = "split",
851		/// A soft well holding bare buttons.
852		Toolbar = "toolbar",
853	}
854);
855
856string_enum!(
857	/// An avatar's shape.
858	AvatarVariant {
859		/// A circle (the default): people.
860		Circle = "circle",
861		/// A tinted squircle: bots, orgs, repositories.
862		Square = "square",
863	}
864);
865
866string_enum!(
867	/// An avatar's size.
868	AvatarSize {
869		/// 16px.
870		Xs = "xs",
871		/// 20px.
872		Sm = "sm",
873		/// 28px (the default).
874		Md = "md",
875		/// 36px.
876		Lg = "lg",
877	}
878);
879
880string_enum!(
881	/// A `breadcrumb`'s look.
882	BreadcrumbVariant {
883		/// Crumbs between chevrons (the default).
884		Chevron = "chevron",
885		/// A path joined by slashes.
886		Path = "path",
887	}
888);
889
890string_enum!(
891	/// A `pager`'s look.
892	PagerVariant {
893		/// "12–24 of 312" and arrows in one capsule (the default).
894		Range = "range",
895		/// Page numbers in a seven-slot window.
896		Pages = "pages",
897		/// A centered "Show 50 more" row.
898		More = "more",
899	}
900);
901
902string_enum!(
903	/// An `empty` state's look.
904	EmptyVariant {
905		/// Icon, title, line and actions, centered (the default).
906		Center = "center",
907		/// One row: icon, title, muted text, actions at the end.
908		Inline = "inline",
909		/// A dashed zone that takes a dropped file.
910		Drop = "drop",
911	}
912);
913
914string_enum!(
915	/// A `skeleton`'s look.
916	SkeletonVariant {
917		/// One band sweeping across the blocks (the default).
918		Shimmer = "shimmer",
919		/// Blocks fading in and out slowly.
920		Pulse = "pulse",
921		/// Dashed outlines, no fill, still.
922		Wire = "wire",
923	}
924);
925
926string_enum!(
927	/// What a `skeleton` stands for.
928	SkeletonShape {
929		/// Lines of text (the default).
930		Text = "text",
931		/// An avatar beside a name.
932		Avatar = "avatar",
933		/// List rows.
934		Rows = "rows",
935		/// A card of stat tiles.
936		Card = "card",
937		/// A block.
938		Block = "block",
939	}
940);
941
942string_enum!(
943	/// A `calendar`'s look.
944	CalendarVariant {
945		/// A month grid (the default).
946		Month = "month",
947		/// Presets beside a week strip.
948		Week = "week",
949	}
950);
951
952string_enum!(
953	/// The first column of a month grid.
954	WeekStart {
955		/// Monday (the default).
956		Mon = "mon",
957		/// Sunday.
958		Sun = "sun",
959	}
960);
961
962string_enum!(
963	/// A `menu`'s look.
964	MenuVariant {
965		/// Kit's menu (the default).
966		Default = "default",
967		/// 24px rows for long sort and filter menus.
968		Dense = "dense",
969	}
970);
971
972string_enum!(
973	/// Which edge of its trigger a menu lines up with.
974	MenuAlign {
975		/// The start (the default).
976		Start = "start",
977		/// The end.
978		End = "end",
979	}
980);
981
982string_enum!(
983	/// A `menubar`'s look.
984	MenubarVariant {
985		/// A full-width hairline strip (the default).
986		Strip = "strip",
987		/// A frosted capsule as wide as its titles.
988		Pill = "pill",
989	}
990);
991
992string_enum!(
993	/// A `nav`'s look.
994	NavVariant {
995		/// Pills in a well, sub-items in a menu (the default).
996		Pills = "pills",
997		/// Text links over a hairline, sub-items in a panel.
998		Links = "links",
999	}
1000);
1001
1002string_enum!(
1003	/// A `split`'s look.
1004	SplitVariant {
1005		/// One framed box, a 1px divider (the default).
1006		Hairline = "hairline",
1007		/// Every pane its own card, a dotted grip in the gap.
1008		Gutter = "gutter",
1009		/// Every pane its own card; the gap is the handle.
1010		Tiles = "tiles",
1011	}
1012);
1013
1014string_enum!(
1015	/// How a `split` lays out its panes.
1016	SplitDir {
1017		/// Side by side (the default).
1018		Row = "row",
1019		/// Stacked.
1020		Col = "col",
1021	}
1022);
1023
1024string_enum!(
1025	/// A `scroll`'s look.
1026	ScrollVariant {
1027		/// An overlay thumb while scrolling (the default).
1028		Bar = "bar",
1029		/// The bar, plus faded edges and a "N more" chip.
1030		Fade = "fade",
1031	}
1032);
1033
1034string_enum!(
1035	/// Where a `scroll` keeps to while its content grows.
1036	Stick {
1037		/// The end, until the user scrolls up from it.
1038		End = "end",
1039	}
1040);
1041
1042string_enum!(
1043	/// When a `scroll`'s thumb shows.
1044	ScrollBar {
1045		/// While scrolling (the default).
1046		Auto = "auto",
1047		/// Always, while there is something to scroll.
1048		Always = "always",
1049	}
1050);
1051
1052string_enum!(
1053	/// A `carousel`'s look.
1054	CarouselVariant {
1055		/// The next slide peeking in (the default).
1056		Peek = "peek",
1057		/// Round arrow buttons over the slide's edges.
1058		Arrows = "arrows",
1059		/// A row of thumbnails under the slide.
1060		Strip = "strip",
1061	}
1062);
1063
1064string_enum!(
1065	/// A `message`'s look.
1066	MessageVariant {
1067		/// An accent bubble on the end side, plain text on the start side
1068		/// (the default).
1069		Auto = "auto",
1070		/// Bubbles on both sides.
1071		Bubble = "bubble",
1072		/// Rows from the left under avatar, name and time.
1073		Flat = "flat",
1074	}
1075);
1076
1077string_enum!(
1078	/// Which side a `message` sits on.
1079	MessageSide {
1080		/// Others (the default).
1081		Start = "start",
1082		/// The local user.
1083		End = "end",
1084	}
1085);
1086
1087string_enum!(
1088	/// When a `message`'s action buttons show.
1089	Reveal {
1090		/// On hover (the default).
1091		Hover = "hover",
1092		/// Always.
1093		Always = "always",
1094	}
1095);
1096
1097string_enum!(
1098	/// An `attachment`'s look.
1099	AttachmentVariant {
1100		/// Type tile, name, meta line, action icons (the default).
1101		Chip = "chip",
1102		/// A thumbnail, the name under it.
1103		Tile = "tile",
1104		/// A capsule: icon, mono name, ×.
1105		Pill = "pill",
1106	}
1107);
1108
1109string_enum!(
1110	/// An `attachment`'s upload state.
1111	AttachmentState {
1112		/// At rest (the default).
1113		Idle = "idle",
1114		/// Uploading.
1115		Uploading = "uploading",
1116		/// Processing.
1117		Processing = "processing",
1118		/// Failed.
1119		Error = "error",
1120		/// Done.
1121		Done = "done",
1122	}
1123);
1124
1125string_enum!(
1126	/// A `trace`'s look.
1127	TraceVariant {
1128		/// Lines on a hairline rail (the default).
1129		Rail = "rail",
1130		/// One summary line that unfolds into the lines.
1131		Summary = "summary",
1132		/// A bare card: a head, a prose body, source chips.
1133		Card = "card",
1134	}
1135);
1136
1137string_enum!(
1138	/// A `stacktrace`'s look.
1139	StacktraceVariant {
1140		/// An error card over the app frames (the default).
1141		Frames = "frames",
1142		/// A frame list beside the selected frame's code.
1143		Split = "split",
1144		/// A mono well of `at fn (file:line:col)` lines.
1145		Mono = "mono",
1146	}
1147);
1148
1149string_enum!(
1150	/// A `suggestions`'s look.
1151	SuggestionsVariant {
1152		/// Wrapping pills (the default).
1153		Chips = "chips",
1154		/// Full-width rows with keycaps.
1155		Rows = "rows",
1156		/// Two columns of cards.
1157		Cards = "cards",
1158	}
1159);
1160
1161string_enum!(
1162	/// A `questions`'s look.
1163	QuestionsVariant {
1164		/// One card per question, in steps (the default).
1165		Steps = "steps",
1166		/// Prompt, keyed chips and a hint, in the flow.
1167		Inline = "inline",
1168		/// Every question; answered ones fold.
1169		List = "list",
1170	}
1171);
1172
1173/// One styled run of text: `{t, s?, fx?, href?, cite?}`.
1174#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1175pub struct Span {
1176	/// The text.
1177	pub t:    String,
1178	/// Space-separated style tokens.
1179	#[serde(skip_serializing_if = "Option::is_none")]
1180	pub s:    Option<String>,
1181	/// An effect.
1182	#[serde(skip_serializing_if = "Option::is_none")]
1183	pub fx:   Option<Fx>,
1184	/// A link.
1185	#[serde(skip_serializing_if = "Option::is_none")]
1186	pub href: Option<String>,
1187	/// The sources this run cites; the marker follows the run (an empty `t`
1188	/// draws the marker alone).
1189	#[serde(skip_serializing_if = "Vec::is_empty")]
1190	pub cite: Vec<Cite>,
1191}
1192
1193/// A source a span cites.
1194#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1195pub struct Cite {
1196	/// Where the source is; opened on click. A cite without one is ignored.
1197	pub href:    String,
1198	/// The source's title.
1199	#[serde(skip_serializing_if = "Option::is_none")]
1200	pub title:   Option<String>,
1201	/// The number shown (default: the next free one, by first appearance).
1202	#[serde(skip_serializing_if = "Option::is_none")]
1203	pub n:       Option<u32>,
1204	/// The name the pill shows (default: the `href`'s host, `www.` dropped).
1205	#[serde(skip_serializing_if = "Option::is_none")]
1206	pub site:    Option<String>,
1207	/// A quote shown in the hover card.
1208	#[serde(skip_serializing_if = "Option::is_none")]
1209	pub snippet: Option<String>,
1210}
1211
1212impl Cite {
1213	/// A source at `href`.
1214	pub fn new(href: impl Into<String>) -> Self {
1215		Self { href: href.into(), ..Self::default() }
1216	}
1217
1218	/// Sets the title.
1219	pub fn title(mut self, title: impl Into<String>) -> Self {
1220		self.title = Some(title.into());
1221		self
1222	}
1223
1224	/// Sets the number shown.
1225	pub const fn n(mut self, n: u32) -> Self {
1226		self.n = Some(n);
1227		self
1228	}
1229
1230	/// Sets the site name.
1231	pub fn site(mut self, site: impl Into<String>) -> Self {
1232		self.site = Some(site.into());
1233		self
1234	}
1235
1236	/// Sets the quote.
1237	pub fn snippet(mut self, snippet: impl Into<String>) -> Self {
1238		self.snippet = Some(snippet.into());
1239		self
1240	}
1241}
1242
1243impl Span {
1244	/// A plain span.
1245	pub fn new(t: impl Into<String>) -> Self {
1246		Self { t: t.into(), ..Self::default() }
1247	}
1248
1249	/// Adds a style token (or several, space-separated).
1250	pub fn style(mut self, token: impl Into<Token>) -> Self {
1251		let token = token.into();
1252		self.s = Some(match self.s.take() {
1253			Some(s) if !s.is_empty() => format!("{s} {token}"),
1254			_ => token.to_string(),
1255		});
1256		self
1257	}
1258
1259	/// Sets the effect.
1260	pub fn fx(mut self, fx: impl Into<Fx>) -> Self {
1261		self.fx = Some(fx.into());
1262		self
1263	}
1264
1265	/// Makes the span a link.
1266	pub fn href(mut self, href: impl Into<String>) -> Self {
1267		self.href = Some(href.into());
1268		self
1269	}
1270
1271	/// Cites a source after the run.
1272	pub fn cite(mut self, cite: Cite) -> Self {
1273		self.cite.push(cite);
1274		self
1275	}
1276}
1277
1278impl From<&str> for Span {
1279	fn from(t: &str) -> Self {
1280		Self::new(t)
1281	}
1282}
1283
1284impl From<String> for Span {
1285	fn from(t: String) -> Self {
1286		Self::new(t)
1287	}
1288}
1289
1290/// Text given as a plain string or as styled spans.
1291#[derive(Clone, Debug, PartialEq, Eq)]
1292pub enum Text {
1293	/// A plain string.
1294	Plain(String),
1295	/// Styled spans.
1296	Spans(Vec<Span>),
1297}
1298
1299impl Text {
1300	/// The text without styles.
1301	pub fn plain(&self) -> String {
1302		match self {
1303			Self::Plain(s) => s.clone(),
1304			Self::Spans(spans) => spans.iter().map(|s| s.t.as_str()).collect(),
1305		}
1306	}
1307}
1308
1309impl Default for Text {
1310	fn default() -> Self {
1311		Self::Plain(String::new())
1312	}
1313}
1314
1315impl Serialize for Text {
1316	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1317		match self {
1318			Self::Plain(t) => s.serialize_str(t),
1319			Self::Spans(spans) => spans.serialize(s),
1320		}
1321	}
1322}
1323
1324impl From<&str> for Text {
1325	fn from(t: &str) -> Self {
1326		Self::Plain(t.to_owned())
1327	}
1328}
1329
1330impl From<String> for Text {
1331	fn from(t: String) -> Self {
1332		Self::Plain(t)
1333	}
1334}
1335
1336impl From<&String> for Text {
1337	fn from(t: &String) -> Self {
1338		Self::Plain(t.clone())
1339	}
1340}
1341
1342impl From<Span> for Text {
1343	fn from(span: Span) -> Self {
1344		Self::Spans(vec![span])
1345	}
1346}
1347
1348impl From<Vec<Span>> for Text {
1349	fn from(spans: Vec<Span>) -> Self {
1350		Self::Spans(spans)
1351	}
1352}
1353
1354impl<const N: usize> From<[Span; N]> for Text {
1355	fn from(spans: [Span; N]) -> Self {
1356		Self::Spans(spans.into())
1357	}
1358}
1359
1360/// A size bound: `"<n>ch"`, `"<n>lines"` or a fraction of the parent.
1361#[derive(Clone, Copy, Debug, PartialEq)]
1362pub enum Extent {
1363	/// Character cells.
1364	Ch(f64),
1365	/// Text lines.
1366	Lines(f64),
1367	/// A fraction of the parent (`0.5` is half).
1368	Fraction(f64),
1369}
1370
1371impl Serialize for Extent {
1372	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1373		match *self {
1374			Self::Ch(n) => s.collect_str(&format_args!("{n}ch")),
1375			Self::Lines(n) => s.collect_str(&format_args!("{n}lines")),
1376			Self::Fraction(f) => s.serialize_f64(f),
1377		}
1378	}
1379}
1380
1381/// Size bounds `{w?, h?}` for `min` and `max`.
1382#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
1383pub struct Bound {
1384	/// The width bound.
1385	#[serde(skip_serializing_if = "Option::is_none")]
1386	pub w: Option<Extent>,
1387	/// The height bound.
1388	#[serde(skip_serializing_if = "Option::is_none")]
1389	pub h: Option<Extent>,
1390}
1391
1392impl Bound {
1393	/// A width bound.
1394	pub const fn w(w: Extent) -> Self {
1395		Self { w: Some(w), h: None }
1396	}
1397
1398	/// A height bound.
1399	pub const fn h(h: Extent) -> Self {
1400		Self { w: None, h: Some(h) }
1401	}
1402
1403	/// Both bounds.
1404	pub const fn wh(w: Extent, h: Extent) -> Self {
1405		Self { w: Some(w), h: Some(h) }
1406	}
1407}
1408
1409/// A flex basis.
1410#[derive(Clone, Copy, Debug, PartialEq)]
1411pub enum Basis {
1412	/// A fraction of the parent.
1413	Fraction(f64),
1414	/// The node's own size, without shrinking.
1415	Content,
1416}
1417
1418impl Serialize for Basis {
1419	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1420		match *self {
1421			Self::Fraction(f) => s.serialize_f64(f),
1422			Self::Content => s.serialize_str("content"),
1423		}
1424	}
1425}
1426
1427impl From<f64> for Basis {
1428	fn from(f: f64) -> Self {
1429		Self::Fraction(f)
1430	}
1431}
1432
1433/// A height in pixels or in the surface's cells and lines.
1434#[derive(Clone, Copy, Debug, PartialEq)]
1435pub enum Height {
1436	/// Pixels.
1437	Px(f64),
1438	/// Character cells.
1439	Ch(f64),
1440	/// Text lines.
1441	Lines(f64),
1442}
1443
1444impl Serialize for Height {
1445	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1446		match *self {
1447			Self::Px(px) => s.serialize_f64(px),
1448			Self::Ch(n) => s.collect_str(&format_args!("{n}ch")),
1449			Self::Lines(n) => s.collect_str(&format_args!("{n}lines")),
1450		}
1451	}
1452}
1453
1454/// The minimum size of a `split`'s panes along the split.
1455#[derive(Clone, Debug, PartialEq)]
1456pub enum PaneMin {
1457	/// The same minimum for every pane.
1458	All(Extent),
1459	/// A minimum per pane, in child order (`None` for none).
1460	Each(Vec<Option<Extent>>),
1461	/// The common size bound of the split itself.
1462	Bound(Bound),
1463}
1464
1465impl Serialize for PaneMin {
1466	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1467		match self {
1468			Self::All(extent) => extent.serialize(s),
1469			Self::Each(extents) => extents.serialize(s),
1470			Self::Bound(bound) => bound.serialize(s),
1471		}
1472	}
1473}
1474
1475impl From<Extent> for PaneMin {
1476	fn from(extent: Extent) -> Self {
1477		Self::All(extent)
1478	}
1479}
1480
1481impl From<Vec<Option<Extent>>> for PaneMin {
1482	fn from(extents: Vec<Option<Extent>>) -> Self {
1483		Self::Each(extents)
1484	}
1485}
1486
1487impl From<Bound> for PaneMin {
1488	fn from(bound: Bound) -> Self {
1489		Self::Bound(bound)
1490	}
1491}
1492
1493/// Text, or a bare flag: a `field`'s `error`/`warning` (`true` sets the
1494/// state without a line), a chart's `last` (`true` prints the value,
1495/// `false` hides it).
1496#[derive(Clone, Debug, PartialEq, Eq)]
1497pub enum TextFlag {
1498	/// On or off, without text.
1499	Flag(bool),
1500	/// The text.
1501	Text(Text),
1502}
1503
1504impl Serialize for TextFlag {
1505	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1506		match self {
1507			Self::Flag(b) => s.serialize_bool(*b),
1508			Self::Text(t) => t.serialize(s),
1509		}
1510	}
1511}
1512
1513impl From<bool> for TextFlag {
1514	fn from(b: bool) -> Self {
1515		Self::Flag(b)
1516	}
1517}
1518
1519impl<T: Into<Text>> From<T> for TextFlag {
1520	fn from(t: T) -> Self {
1521		Self::Text(t.into())
1522	}
1523}
1524
1525/// A `slider`'s value: one, or a range drawn with two thumbs.
1526#[derive(Clone, Copy, Debug, PartialEq)]
1527pub enum SliderValue {
1528	/// One value.
1529	One(f64),
1530	/// A range, low to high.
1531	Range(f64, f64),
1532}
1533
1534impl Serialize for SliderValue {
1535	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1536		match *self {
1537			Self::One(v) => s.serialize_f64(v),
1538			Self::Range(lo, hi) => [lo, hi].serialize(s),
1539		}
1540	}
1541}
1542
1543impl From<f64> for SliderValue {
1544	fn from(v: f64) -> Self {
1545		Self::One(v)
1546	}
1547}
1548
1549impl From<(f64, f64)> for SliderValue {
1550	fn from((lo, hi): (f64, f64)) -> Self {
1551		Self::Range(lo, hi)
1552	}
1553}
1554
1555/// A `slider`'s tick marks.
1556#[derive(Clone, Debug, PartialEq)]
1557pub enum Ticks {
1558	/// One at every step (`true`), or none (`false`).
1559	Steps(bool),
1560	/// The track divided into this many equal parts.
1561	Parts(u32),
1562	/// One at each of these values.
1563	At(Vec<f64>),
1564}
1565
1566impl Serialize for Ticks {
1567	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1568		match self {
1569			Self::Steps(b) => s.serialize_bool(*b),
1570			Self::Parts(n) => s.serialize_u32(*n),
1571			Self::At(values) => values.serialize(s),
1572		}
1573	}
1574}
1575
1576impl From<bool> for Ticks {
1577	fn from(b: bool) -> Self {
1578		Self::Steps(b)
1579	}
1580}
1581
1582impl From<u32> for Ticks {
1583	fn from(n: u32) -> Self {
1584		Self::Parts(n)
1585	}
1586}
1587
1588impl From<Vec<f64>> for Ticks {
1589	fn from(values: Vec<f64>) -> Self {
1590		Self::At(values)
1591	}
1592}
1593
1594/// How an `otp` hides what was typed.
1595#[derive(Clone, Debug, PartialEq, Eq)]
1596pub enum OtpMask {
1597	/// A dot in each filled cell (`true`), or shown (`false`).
1598	Dots(bool),
1599	/// This character in each filled cell.
1600	Char(String),
1601}
1602
1603impl Serialize for OtpMask {
1604	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1605		match self {
1606			Self::Dots(b) => s.serialize_bool(*b),
1607			Self::Char(c) => s.serialize_str(c),
1608		}
1609	}
1610}
1611
1612impl From<bool> for OtpMask {
1613	fn from(b: bool) -> Self {
1614		Self::Dots(b)
1615	}
1616}
1617
1618impl From<&str> for OtpMask {
1619	fn from(c: &str) -> Self {
1620		Self::Char(c.to_owned())
1621	}
1622}
1623
1624impl From<char> for OtpMask {
1625	fn from(c: char) -> Self {
1626		Self::Char(c.to_string())
1627	}
1628}
1629
1630/// What a `scroll`'s counts name: one word made plural with an `s`, or
1631/// both forms.
1632#[derive(Clone, Debug, PartialEq, Eq)]
1633pub enum Noun {
1634	/// `"line"`: "4 more lines".
1635	Word(String),
1636	/// `["entry", "entries"]`.
1637	Forms(String, String),
1638}
1639
1640impl Serialize for Noun {
1641	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1642		match self {
1643			Self::Word(w) => s.serialize_str(w),
1644			Self::Forms(one, many) => [one, many].serialize(s),
1645		}
1646	}
1647}
1648
1649impl From<&str> for Noun {
1650	fn from(w: &str) -> Self {
1651		Self::Word(w.to_owned())
1652	}
1653}
1654
1655impl From<String> for Noun {
1656	fn from(w: String) -> Self {
1657		Self::Word(w)
1658	}
1659}
1660
1661impl From<(&str, &str)> for Noun {
1662	fn from((one, many): (&str, &str)) -> Self {
1663		Self::Forms(one.to_owned(), many.to_owned())
1664	}
1665}
1666
1667/// A `calendar`'s or `date`'s selection: a `YYYY-MM-DD` day, or a range
1668/// whose end may still be open.
1669#[derive(Clone, Debug, PartialEq, Eq)]
1670pub enum DateValue {
1671	/// One day.
1672	Day(String),
1673	/// From a day to another (`None`: waiting for its end).
1674	Range(String, Option<String>),
1675}
1676
1677impl Serialize for DateValue {
1678	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1679		match self {
1680			Self::Day(day) => s.serialize_str(day),
1681			Self::Range(from, to) => (from, to).serialize(s),
1682		}
1683	}
1684}
1685
1686impl From<&str> for DateValue {
1687	fn from(day: &str) -> Self {
1688		Self::Day(day.to_owned())
1689	}
1690}
1691
1692impl From<String> for DateValue {
1693	fn from(day: String) -> Self {
1694		Self::Day(day)
1695	}
1696}
1697
1698impl From<(&str, &str)> for DateValue {
1699	fn from((from, to): (&str, &str)) -> Self {
1700		Self::Range(from.to_owned(), Some(to.to_owned()))
1701	}
1702}
1703
1704/// A node key: a string, or an integer named in decimal.
1705#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1706pub enum NodeKey {
1707	/// A string key.
1708	Str(String),
1709	/// An integer key.
1710	Int(i64),
1711}
1712
1713impl Serialize for NodeKey {
1714	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1715		match self {
1716			Self::Str(k) => s.serialize_str(k),
1717			Self::Int(n) => s.serialize_i64(*n),
1718		}
1719	}
1720}
1721
1722impl From<&str> for NodeKey {
1723	fn from(k: &str) -> Self {
1724		Self::Str(k.to_owned())
1725	}
1726}
1727
1728impl From<String> for NodeKey {
1729	fn from(k: String) -> Self {
1730		Self::Str(k)
1731	}
1732}
1733
1734impl From<&String> for NodeKey {
1735	fn from(k: &String) -> Self {
1736		Self::Str(k.clone())
1737	}
1738}
1739
1740/// Implements `From<integer>` for [`NodeKey`].
1741macro_rules! int_key {
1742	($($t:ty),*) => {
1743		$(impl From<$t> for NodeKey {
1744			fn from(n: $t) -> Self {
1745				Self::Int(n as i64)
1746			}
1747		})*
1748	};
1749}
1750
1751int_key!(i32, i64, u32, u64, usize);
1752
1753/// What a pointer does on a node: `{click?, dblclick?, menu?}`.
1754#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
1755pub struct Actions {
1756	/// The click action.
1757	#[serde(skip_serializing_if = "Option::is_none")]
1758	pub click:    Option<String>,
1759	/// The double-click action.
1760	#[serde(skip_serializing_if = "Option::is_none")]
1761	pub dblclick: Option<String>,
1762	/// Context-menu entries: actions, items with icons, keys and checks,
1763	/// separators, headers and submenus.
1764	#[serde(skip_serializing_if = "Vec::is_empty")]
1765	pub menu:     Vec<MenuEntry>,
1766}
1767
1768impl Actions {
1769	/// No actions.
1770	pub fn new() -> Self {
1771		Self::default()
1772	}
1773
1774	/// Sets the click action.
1775	pub fn click(mut self, act: impl Into<String>) -> Self {
1776		self.click = Some(act.into());
1777		self
1778	}
1779
1780	/// Sets the double-click action.
1781	pub fn dblclick(mut self, act: impl Into<String>) -> Self {
1782		self.dblclick = Some(act.into());
1783		self
1784	}
1785
1786	/// Adds a context-menu entry: an action's name, or a [`MenuEntry`].
1787	pub fn menu(mut self, entry: impl Into<MenuEntry>) -> Self {
1788		self.menu.push(entry.into());
1789		self
1790	}
1791}