Skip to main content

tern_sdk/ui/
data.rs

1//! Structured prop values: table columns and rows, tree items, list and
2//! picker entries, meter parts, tool and agent facts, checklist phases and
3//! `prefs` pages. Every field is public; optional ones are omitted from the
4//! wire when `None` or empty.
5
6use std::collections::BTreeMap;
7
8use serde::{Serialize, Serializer, ser::SerializeMap};
9use serde_json::Value;
10
11use super::{
12	icon::Icon,
13	types::{ChoiceStyle, ColumnFormat, ItemStatus, Text, TextAlign, Tone, Truncate},
14};
15
16/// One `kv` pair.
17#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
18pub struct KvItem {
19	/// The key.
20	pub k: Text,
21	/// The value.
22	pub v: Text,
23}
24
25/// A `table` column.
26#[derive(Clone, Debug, Default, PartialEq, Serialize)]
27pub struct Column {
28	/// The key of this column's cell in each row.
29	pub id:       String,
30	/// Header text.
31	#[serde(skip_serializing_if = "Option::is_none")]
32	pub head:     Option<Text>,
33	/// Alignment of the header and cells.
34	#[serde(skip_serializing_if = "Option::is_none")]
35	pub align:    Option<TextAlign>,
36	/// Where a cell that doesn't fit is cut.
37	#[serde(skip_serializing_if = "Option::is_none")]
38	pub truncate: Option<Truncate>,
39	/// Lower hides first when the table is narrow.
40	#[serde(skip_serializing_if = "Option::is_none")]
41	pub priority: Option<f64>,
42	/// Share of the spare width.
43	#[serde(skip_serializing_if = "Option::is_none")]
44	pub grow:     Option<f64>,
45}
46
47impl Column {
48	/// A column with header `head`.
49	pub fn new(id: impl Into<String>, head: impl Into<Text>) -> Self {
50		Self { id: id.into(), head: Some(head.into()), ..Self::default() }
51	}
52
53	/// Sets the alignment.
54	pub fn align(mut self, align: impl Into<TextAlign>) -> Self {
55		self.align = Some(align.into());
56		self
57	}
58
59	/// Sets the grow share.
60	pub const fn grow(mut self, grow: f64) -> Self {
61		self.grow = Some(grow);
62		self
63	}
64
65	/// Sets the hide priority.
66	pub const fn priority(mut self, priority: f64) -> Self {
67		self.priority = Some(priority);
68		self
69	}
70
71	/// Sets where cells are cut.
72	pub fn truncate(mut self, truncate: impl Into<Truncate>) -> Self {
73		self.truncate = Some(truncate.into());
74		self
75	}
76}
77
78/// A stacked part of a meter or meter cell.
79#[derive(Clone, Debug, Default, PartialEq, Serialize)]
80pub struct Part {
81	/// The part's share.
82	pub value: f64,
83	/// Its color token.
84	#[serde(skip_serializing_if = "Option::is_none")]
85	pub token: Option<String>,
86	/// Its tooltip label.
87	#[serde(skip_serializing_if = "Option::is_none")]
88	pub label: Option<String>,
89	/// Diagonal hatching.
90	#[serde(skip_serializing_if = "Option::is_none")]
91	pub hatch: Option<bool>,
92}
93
94/// Fill levels that tint a meter.
95#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
96pub struct Thresholds {
97	/// `warn` at or above this.
98	#[serde(skip_serializing_if = "Option::is_none")]
99	pub warn: Option<f64>,
100	/// `bad` at or above this.
101	#[serde(skip_serializing_if = "Option::is_none")]
102	pub bad:  Option<f64>,
103}
104
105/// A table cell drawn as a meter.
106#[derive(Clone, Debug, Default, PartialEq, Serialize)]
107pub struct MeterCell {
108	/// The fill, 0–1.
109	#[serde(skip_serializing_if = "Option::is_none")]
110	pub value:      Option<f64>,
111	/// Stacked fills.
112	#[serde(skip_serializing_if = "Vec::is_empty")]
113	pub parts:      Vec<Part>,
114	/// Tint levels.
115	#[serde(skip_serializing_if = "Option::is_none")]
116	pub thresholds: Option<Thresholds>,
117	/// The fill's tone.
118	#[serde(skip_serializing_if = "Option::is_none")]
119	pub tone:       Option<Tone>,
120	/// The tooltip.
121	#[serde(skip_serializing_if = "Option::is_none")]
122	pub title:      Option<String>,
123}
124
125/// A table cell: text or a meter.
126#[derive(Clone, Debug, PartialEq)]
127pub enum Cell {
128	/// Text.
129	Text(Text),
130	/// A meter.
131	Meter(MeterCell),
132}
133
134impl Serialize for Cell {
135	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
136		match self {
137			Self::Text(t) => t.serialize(s),
138			Self::Meter(m) => {
139				let mut map = s.serialize_map(Some(1))?;
140				map.serialize_entry("meter", m)?;
141				map.end()
142			},
143		}
144	}
145}
146
147impl<T: Into<Text>> From<T> for Cell {
148	fn from(t: T) -> Self {
149		Self::Text(t.into())
150	}
151}
152
153impl From<MeterCell> for Cell {
154	fn from(m: MeterCell) -> Self {
155		Self::Meter(m)
156	}
157}
158
159/// A `table` row.
160#[derive(Clone, Debug, Default, PartialEq, Serialize)]
161pub struct TableRow {
162	/// Row identity, unique in the table.
163	pub id:    String,
164	/// One cell per column id.
165	pub cells: BTreeMap<String, Cell>,
166}
167
168impl TableRow {
169	/// An empty row.
170	pub fn new(id: impl Into<String>) -> Self {
171		Self { id: id.into(), cells: BTreeMap::new() }
172	}
173
174	/// Sets the cell of column `col`.
175	pub fn cell(mut self, col: impl Into<String>, cell: impl Into<Cell>) -> Self {
176		self.cells.insert(col.into(), cell.into());
177		self
178	}
179}
180
181/// A `tree` item.
182#[derive(Clone, Debug, Default, PartialEq, Serialize)]
183pub struct TreeItem {
184	/// Identity, unique in the tree.
185	pub id:       String,
186	/// The row's text.
187	pub label:    Text,
188	/// An icon before the label.
189	#[serde(skip_serializing_if = "Option::is_none")]
190	pub icon:     Option<Icon>,
191	/// Whether the children show.
192	#[serde(skip_serializing_if = "Option::is_none")]
193	pub open:     Option<bool>,
194	/// Nested items.
195	#[serde(skip_serializing_if = "Vec::is_empty")]
196	pub children: Vec<Self>,
197}
198
199impl TreeItem {
200	/// An item labeled `label`.
201	pub fn new(id: impl Into<String>, label: impl Into<Text>) -> Self {
202		Self { id: id.into(), label: label.into(), ..Self::default() }
203	}
204
205	/// Adds a child item.
206	pub fn child(mut self, item: Self) -> Self {
207		self.children.push(item);
208		self
209	}
210
211	/// Sets whether the children show.
212	pub const fn open(mut self, open: bool) -> Self {
213		self.open = Some(open);
214		self
215	}
216}
217
218/// A tab of a `tabs` strip.
219#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
220pub struct Tab {
221	/// The tab's id.
222	pub id:    String,
223	/// Its label.
224	pub label: Text,
225}
226
227/// A code line mark.
228#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
229pub struct CodeMark {
230	/// The line, in the gutter's numbering.
231	pub line:   u64,
232	/// The tone.
233	#[serde(skip_serializing_if = "Option::is_none")]
234	pub tone:   Option<Tone>,
235	/// Character ranges `[from, to)` marked on the line.
236	#[serde(skip_serializing_if = "Vec::is_empty")]
237	pub ranges: Vec<[u64; 2]>,
238}
239
240/// A diff hunk given directly.
241#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct Hunk {
244	/// The old side's first line.
245	#[serde(skip_serializing_if = "Option::is_none")]
246	pub old_start: Option<u64>,
247	/// The new side's first line.
248	#[serde(skip_serializing_if = "Option::is_none")]
249	pub new_start: Option<u64>,
250	/// Lines starting with `+`, `-` or a space.
251	pub lines:     Vec<String>,
252}
253
254/// A styled range of an editor's text (UTF-16 offsets).
255#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
256pub struct Decor {
257	/// Start.
258	pub from: usize,
259	/// End.
260	pub to:   usize,
261	/// Style tokens.
262	pub s:    String,
263	/// An effect.
264	#[serde(skip_serializing_if = "Option::is_none")]
265	pub fx:   Option<super::types::Fx>,
266}
267
268/// A shimmer's sweep colors, as span tokens.
269#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
270pub struct ShimmerPalette {
271	/// The low color.
272	#[serde(skip_serializing_if = "Option::is_none")]
273	pub low:  Option<String>,
274	/// The middle color.
275	#[serde(skip_serializing_if = "Option::is_none")]
276	pub mid:  Option<String>,
277	/// The high color.
278	#[serde(skip_serializing_if = "Option::is_none")]
279	pub high: Option<String>,
280}
281
282/// A tick on a meter.
283#[derive(Clone, Debug, Default, PartialEq, Serialize)]
284pub struct MeterMark {
285	/// Position, 0–1.
286	pub at:    f64,
287	/// Tick color.
288	#[serde(skip_serializing_if = "Option::is_none")]
289	pub tone:  Option<Tone>,
290	/// Tooltip.
291	#[serde(skip_serializing_if = "Option::is_none")]
292	pub title: Option<String>,
293	/// An icon on the track.
294	#[serde(skip_serializing_if = "Option::is_none")]
295	pub icon:  Option<Icon>,
296}
297
298/// One bar of a `bars` or `spark` chart.
299#[derive(Clone, Debug, Default, PartialEq, Serialize)]
300pub struct Point {
301	/// The value.
302	pub value: f64,
303	/// A label.
304	#[serde(skip_serializing_if = "Option::is_none")]
305	pub label: Option<String>,
306	/// A tooltip.
307	#[serde(skip_serializing_if = "Option::is_none")]
308	pub title: Option<String>,
309}
310
311/// A heatmap column label.
312#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
313pub struct ChartCol {
314	/// The 0-based column.
315	pub at:    u32,
316	/// The label.
317	pub label: String,
318}
319
320/// Where an overlay floats.
321#[derive(Clone, Debug, PartialEq, Eq)]
322pub enum Anchor {
323	/// Centered.
324	Center,
325	/// At the top.
326	Top,
327	/// At the bottom.
328	Bottom,
329	/// Beside node `id`, below it unless `above`.
330	Node {
331		/// The node.
332		id:    String,
333		/// Above instead of below.
334		above: bool,
335	},
336	/// Below the caret of field `id`.
337	Caret(String),
338}
339
340impl Serialize for Anchor {
341	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
342		use serde::ser::SerializeMap as _;
343		match self {
344			Self::Center => s.serialize_str("center"),
345			Self::Top => s.serialize_str("top"),
346			Self::Bottom => s.serialize_str("bottom"),
347			Self::Node { id, above } => {
348				let mut m = s.serialize_map(Some(2))?;
349				m.serialize_entry("node", id)?;
350				m.serialize_entry("side", if *above { "above" } else { "below" })?;
351				m.end()
352			},
353			Self::Caret(id) => {
354				let mut m = s.serialize_map(Some(1))?;
355				m.serialize_entry("caret", id)?;
356				m.end()
357			},
358		}
359	}
360}
361
362/// A badge in a tool's or agent's head, or after a picker row's label.
363#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
364pub struct BadgeSpec {
365	/// The text.
366	pub text:  String,
367	/// The tone.
368	#[serde(skip_serializing_if = "Option::is_none")]
369	pub tone:  Option<Tone>,
370	/// A tooltip.
371	#[serde(skip_serializing_if = "Option::is_none")]
372	pub title: Option<String>,
373}
374
375/// A hover button on a tool.
376#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
377pub struct ToolButton {
378	/// The action id.
379	pub id:    String,
380	/// The label.
381	#[serde(skip_serializing_if = "Option::is_none")]
382	pub label: Option<String>,
383	/// Keycaps.
384	#[serde(skip_serializing_if = "Vec::is_empty")]
385	pub keys:  Vec<String>,
386}
387
388/// The clamp of a collapsed tool body.
389#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390pub enum ToolPreview {
391	/// The first lines.
392	Lines(u32),
393	/// The last lines.
394	Tail(u32),
395}
396
397impl Serialize for ToolPreview {
398	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
399		use serde::ser::SerializeMap as _;
400		let mut m = s.serialize_map(Some(1))?;
401		match *self {
402			Self::Lines(n) => m.serialize_entry("lines", &n)?,
403			Self::Tail(n) => m.serialize_entry("tail", &n)?,
404		}
405		m.end()
406	}
407}
408
409/// Lines a collapsed card shows.
410#[derive(Clone, Copy, Debug, PartialEq, Eq)]
411pub enum Preview {
412	/// Ten lines.
413	Auto,
414	/// This many lines.
415	Lines(u32),
416}
417
418impl Serialize for Preview {
419	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
420		use serde::ser::SerializeMap as _;
421		match *self {
422			Self::Auto => s.serialize_str("auto"),
423			Self::Lines(n) => {
424				let mut m = s.serialize_map(Some(1))?;
425				m.serialize_entry("lines", &n)?;
426				m.end()
427			},
428		}
429	}
430}
431
432/// An agent's stats.
433#[derive(Clone, Debug, Default, PartialEq, Serialize)]
434#[serde(rename_all = "camelCase")]
435pub struct AgentStats {
436	/// Tool calls.
437	#[serde(skip_serializing_if = "Option::is_none")]
438	pub tools:         Option<u64>,
439	/// Requests.
440	#[serde(skip_serializing_if = "Option::is_none")]
441	pub requests:      Option<u64>,
442	/// Fraction done, 0–1.
443	#[serde(skip_serializing_if = "Option::is_none")]
444	pub done:          Option<f64>,
445	/// Context used, 0–1.
446	#[serde(skip_serializing_if = "Option::is_none")]
447	pub context:       Option<f64>,
448	/// The context meter's label.
449	#[serde(skip_serializing_if = "Option::is_none")]
450	pub context_label: Option<String>,
451	/// Tokens used.
452	#[serde(skip_serializing_if = "Option::is_none")]
453	pub tokens:        Option<u64>,
454	/// Cost in dollars.
455	#[serde(skip_serializing_if = "Option::is_none")]
456	pub cost:          Option<f64>,
457	/// Elapsed ms when sent.
458	#[serde(skip_serializing_if = "Option::is_none")]
459	pub age:           Option<i64>,
460	/// Final duration in ms.
461	#[serde(skip_serializing_if = "Option::is_none")]
462	pub took:          Option<u64>,
463}
464
465/// The tool an agent runs now.
466#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
467pub struct AgentTool {
468	/// The tool's name.
469	pub name:   String,
470	/// What it does.
471	#[serde(skip_serializing_if = "Option::is_none")]
472	pub intent: Option<String>,
473	/// Elapsed ms when sent.
474	#[serde(skip_serializing_if = "Option::is_none")]
475	pub age:    Option<i64>,
476}
477
478/// An agent's retry countdown.
479#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
480pub struct Retry {
481	/// The attempt.
482	#[serde(skip_serializing_if = "Option::is_none")]
483	pub attempt: Option<u32>,
484	/// Attempts allowed.
485	#[serde(skip_serializing_if = "Option::is_none")]
486	pub max:     Option<u32>,
487	/// The delay in ms.
488	#[serde(skip_serializing_if = "Option::is_none")]
489	pub delay:   Option<u64>,
490	/// Elapsed ms when sent.
491	#[serde(skip_serializing_if = "Option::is_none")]
492	pub age:     Option<i64>,
493	/// The error retried.
494	#[serde(skip_serializing_if = "Option::is_none")]
495	pub error:   Option<String>,
496}
497
498/// A checklist item.
499#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
500pub struct ChecklistItem {
501	/// Item id.
502	pub id:     String,
503	/// The text.
504	pub text:   Text,
505	/// The state.
506	#[serde(skip_serializing_if = "Option::is_none")]
507	pub status: Option<ItemStatus>,
508	/// A line under the item.
509	#[serde(skip_serializing_if = "Option::is_none")]
510	pub note:   Option<Text>,
511}
512
513/// A checklist phase.
514#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
515pub struct Phase {
516	/// Phase id.
517	pub id:        String,
518	/// The title.
519	#[serde(skip_serializing_if = "Option::is_none")]
520	pub title:     Option<Text>,
521	/// The items.
522	pub items:     Vec<ChecklistItem>,
523	/// The initial fold.
524	#[serde(skip_serializing_if = "Option::is_none")]
525	pub collapsed: Option<bool>,
526}
527
528/// A picker catalog item.
529#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
530pub struct PickerItem {
531	/// Item id.
532	pub id:       String,
533	/// Row text.
534	pub label:    Text,
535	/// Text after the label.
536	#[serde(skip_serializing_if = "Option::is_none")]
537	pub detail:   Option<Text>,
538	/// Lead icon.
539	#[serde(skip_serializing_if = "Option::is_none")]
540	pub icon:     Option<Icon>,
541	/// Picks the lead icon from a role.
542	#[serde(skip_serializing_if = "Option::is_none")]
543	pub role:     Option<String>,
544	/// An initials avatar `{text, seed?}`.
545	#[serde(skip_serializing_if = "Option::is_none")]
546	pub mark:     Option<Value>,
547	/// A status dot.
548	#[serde(skip_serializing_if = "Option::is_none")]
549	pub dot:      Option<Tone>,
550	/// Monospace label.
551	#[serde(skip_serializing_if = "Option::is_none")]
552	pub mono:     Option<bool>,
553	/// Column values by column id.
554	#[serde(skip_serializing_if = "BTreeMap::is_empty")]
555	pub facts:    BTreeMap<String, Value>,
556	/// Badges after the label.
557	#[serde(skip_serializing_if = "Vec::is_empty")]
558	pub badges:   Vec<BadgeSpec>,
559	/// Role chips `{text, on?, auto?, dot?}`.
560	#[serde(skip_serializing_if = "Vec::is_empty")]
561	pub chips:    Vec<Value>,
562	/// The row's tone.
563	#[serde(skip_serializing_if = "Option::is_none")]
564	pub tone:     Option<Tone>,
565	/// `true` or a reason: dims the row.
566	#[serde(skip_serializing_if = "Option::is_none")]
567	pub disabled: Option<Value>,
568	/// Match ranges `[from, to)` in the label (UTF-16).
569	#[serde(skip_serializing_if = "Vec::is_empty")]
570	pub hits:     Vec<[usize; 2]>,
571	/// `timeline` node style.
572	#[serde(skip_serializing_if = "Option::is_none")]
573	pub node:     Option<String>,
574	/// `tree` depth.
575	#[serde(skip_serializing_if = "Option::is_none")]
576	pub depth:    Option<u32>,
577	/// `tree` chevron state.
578	#[serde(skip_serializing_if = "Option::is_none")]
579	pub open:     Option<bool>,
580	/// Row tooltip.
581	#[serde(skip_serializing_if = "Option::is_none")]
582	pub title:    Option<String>,
583}
584
585impl PickerItem {
586	/// An item labeled `label`.
587	pub fn new(id: impl Into<String>, label: impl Into<Text>) -> Self {
588		Self { id: id.into(), label: label.into(), ..Self::default() }
589	}
590}
591
592/// A picker `order` entry: an item id or a group header.
593#[derive(Clone, Debug, PartialEq, Eq)]
594pub enum OrderEntry {
595	/// An item id.
596	Item(String),
597	/// A group header.
598	Group {
599		/// The group's key.
600		group: String,
601		/// Its label.
602		label: String,
603		/// A count.
604		count: Option<u64>,
605	},
606}
607
608impl Serialize for OrderEntry {
609	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
610		use serde::ser::SerializeMap as _;
611		match self {
612			Self::Item(id) => s.serialize_str(id),
613			Self::Group { group, label, count } => {
614				let mut m = s.serialize_map(None)?;
615				m.serialize_entry("group", group)?;
616				m.serialize_entry("label", label)?;
617				if let Some(count) = count {
618					m.serialize_entry("count", count)?;
619				}
620				m.end()
621			},
622		}
623	}
624}
625
626/// A picker fact column.
627#[derive(Clone, Debug, Default, PartialEq, Serialize)]
628pub struct PickerColumn {
629	/// The `facts` key.
630	pub id:       String,
631	/// Head label.
632	#[serde(skip_serializing_if = "Option::is_none")]
633	pub head:     Option<String>,
634	/// Format.
635	#[serde(skip_serializing_if = "Option::is_none")]
636	pub format:   Option<ColumnFormat>,
637	/// Lower hides first.
638	#[serde(skip_serializing_if = "Option::is_none")]
639	pub priority: Option<f64>,
640	/// Minimum width in characters.
641	#[serde(skip_serializing_if = "Option::is_none")]
642	pub min:      Option<u32>,
643}
644
645/// A picker confirm strip.
646#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
647pub struct Confirm {
648	/// The question.
649	pub text:  String,
650	/// The action sent (default `confirm`).
651	#[serde(skip_serializing_if = "Option::is_none")]
652	pub act:   Option<String>,
653	/// The button label (default `Delete`).
654	#[serde(skip_serializing_if = "Option::is_none")]
655	pub label: Option<String>,
656}
657
658/// A picker scope.
659#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
660pub struct Scope {
661	/// Scope id.
662	pub id:       String,
663	/// Label.
664	pub label:    String,
665	/// Group header.
666	#[serde(skip_serializing_if = "Option::is_none")]
667	pub group:    Option<String>,
668	/// Initials avatar `{text, seed?}`.
669	#[serde(skip_serializing_if = "Option::is_none")]
670	pub mark:     Option<Value>,
671	/// Icon.
672	#[serde(skip_serializing_if = "Option::is_none")]
673	pub icon:     Option<Icon>,
674	/// Status dot.
675	#[serde(skip_serializing_if = "Option::is_none")]
676	pub dot:      Option<Tone>,
677	/// Count.
678	#[serde(skip_serializing_if = "Option::is_none")]
679	pub count:    Option<u64>,
680	/// `true` or a reason.
681	#[serde(skip_serializing_if = "Option::is_none")]
682	pub disabled: Option<Value>,
683}
684
685/// A picker tab.
686#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
687pub struct PickerTab {
688	/// Tab id.
689	pub id:    String,
690	/// Label.
691	pub label: String,
692	/// Count.
693	#[serde(skip_serializing_if = "Option::is_none")]
694	pub count: Option<u64>,
695}
696
697/// A switch chip of a picker strip.
698#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
699pub struct StripItem {
700	/// Chip id.
701	pub id:    String,
702	/// Label.
703	pub label: String,
704	/// Checked.
705	#[serde(skip_serializing_if = "Option::is_none")]
706	pub on:    Option<bool>,
707	/// Dot palette name.
708	#[serde(skip_serializing_if = "Option::is_none")]
709	pub dot:   Option<String>,
710}
711
712/// A picker's chip strip.
713#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
714pub struct Strip {
715	/// Label.
716	#[serde(skip_serializing_if = "Option::is_none")]
717	pub label:    Option<String>,
718	/// Chips.
719	pub items:    Vec<StripItem>,
720	/// Outlined chip.
721	#[serde(skip_serializing_if = "Option::is_none")]
722	pub selected: Option<String>,
723}
724
725/// A picker action bar button.
726#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
727pub struct PickerAction {
728	/// Action id.
729	pub id:       String,
730	/// Label (default the id).
731	#[serde(skip_serializing_if = "Option::is_none")]
732	pub label:    Option<String>,
733	/// Keycaps.
734	#[serde(skip_serializing_if = "Vec::is_empty")]
735	pub keys:     Vec<String>,
736	/// The primary action.
737	#[serde(skip_serializing_if = "Option::is_none")]
738	pub primary:  Option<bool>,
739	/// At the right end.
740	#[serde(skip_serializing_if = "Option::is_none")]
741	pub end:      Option<bool>,
742	/// Destructive.
743	#[serde(skip_serializing_if = "Option::is_none")]
744	pub danger:   Option<bool>,
745	/// Toggled on.
746	#[serde(skip_serializing_if = "Option::is_none")]
747	pub on:       Option<bool>,
748	/// `true` or a reason.
749	#[serde(skip_serializing_if = "Option::is_none")]
750	pub disabled: Option<Value>,
751}
752
753/// A `prefs` nav page.
754#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
755pub struct PrefsPage {
756	/// Page id.
757	pub id:       String,
758	/// Label.
759	#[serde(skip_serializing_if = "Option::is_none")]
760	pub label:    Option<String>,
761	/// Group heading.
762	#[serde(skip_serializing_if = "Option::is_none")]
763	pub group:    Option<String>,
764	/// Icon.
765	#[serde(skip_serializing_if = "Option::is_none")]
766	pub icon:     Option<Icon>,
767	/// Why it is disabled.
768	#[serde(skip_serializing_if = "Option::is_none")]
769	pub disabled: Option<String>,
770	/// Rows changed from default.
771	#[serde(skip_serializing_if = "Option::is_none")]
772	pub changed:  Option<u32>,
773}
774
775/// A `prefs` section.
776#[derive(Clone, Debug, Default, PartialEq, Serialize)]
777pub struct PrefsSection {
778	/// Section id.
779	#[serde(skip_serializing_if = "Option::is_none")]
780	pub id:    Option<String>,
781	/// Title.
782	#[serde(skip_serializing_if = "Option::is_none")]
783	pub title: Option<String>,
784	/// The page it belongs to (search results).
785	#[serde(skip_serializing_if = "Option::is_none")]
786	pub page:  Option<String>,
787	/// The rows.
788	pub rows:  Vec<PrefsRow>,
789}
790
791/// A `prefs` row.
792#[derive(Clone, Debug, Default, PartialEq, Serialize)]
793#[serde(rename_all = "camelCase")]
794pub struct PrefsRow {
795	/// Row id.
796	pub id:            String,
797	/// Label.
798	#[serde(skip_serializing_if = "Option::is_none")]
799	pub label:         Option<String>,
800	/// A line under the label.
801	#[serde(skip_serializing_if = "Option::is_none")]
802	pub hint:          Option<String>,
803	/// A warning.
804	#[serde(skip_serializing_if = "Option::is_none")]
805	pub warning:       Option<String>,
806	/// Why it is disabled.
807	#[serde(skip_serializing_if = "Option::is_none")]
808	pub disabled:      Option<String>,
809	/// Changed from default.
810	#[serde(skip_serializing_if = "Option::is_none")]
811	pub changed:       Option<bool>,
812	/// The default, named in the changed dot's tooltip.
813	#[serde(skip_serializing_if = "Option::is_none")]
814	pub default_label: Option<String>,
815	/// The control.
816	#[serde(skip_serializing_if = "Option::is_none")]
817	pub control:       Option<Control>,
818}
819
820/// An option of a `prefs` choice or multi control.
821#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
822pub struct ChoiceOption {
823	/// The value.
824	pub value:  String,
825	/// The label.
826	#[serde(skip_serializing_if = "Option::is_none")]
827	pub label:  Option<String>,
828	/// A tooltip or second line.
829	#[serde(skip_serializing_if = "Option::is_none")]
830	pub detail: Option<String>,
831}
832
833/// A `prefs` row's control, by `k`.
834#[derive(Clone, Debug, PartialEq, Serialize)]
835#[serde(tag = "k", rename_all = "lowercase")]
836pub enum Control {
837	/// A toggle.
838	Switch {
839		/// On.
840		on: bool,
841	},
842	/// One of several options.
843	Choice {
844		/// The current value.
845		value:   String,
846		/// The options.
847		options: Vec<ChoiceOption>,
848		/// How it draws.
849		#[serde(skip_serializing_if = "Option::is_none")]
850		style:   Option<ChoiceStyle>,
851		/// Monospace labels.
852		#[serde(skip_serializing_if = "Option::is_none")]
853		mono:    Option<bool>,
854	},
855	/// A stepper.
856	Number {
857		/// The value.
858		value:  f64,
859		/// Minimum.
860		#[serde(skip_serializing_if = "Option::is_none")]
861		min:    Option<f64>,
862		/// Maximum.
863		#[serde(skip_serializing_if = "Option::is_none")]
864		max:    Option<f64>,
865		/// Step (default 1).
866		#[serde(skip_serializing_if = "Option::is_none")]
867		step:   Option<f64>,
868		/// Unit.
869		#[serde(skip_serializing_if = "Option::is_none")]
870		unit:   Option<String>,
871		/// Labels by value.
872		#[serde(skip_serializing_if = "BTreeMap::is_empty")]
873		labels: BTreeMap<String, String>,
874	},
875	/// A text field look.
876	Text {
877		/// The value.
878		value:       String,
879		/// Placeholder.
880		#[serde(skip_serializing_if = "Option::is_none")]
881		placeholder: Option<String>,
882		/// Dots instead of text.
883		#[serde(skip_serializing_if = "Option::is_none")]
884		secret:      Option<bool>,
885		/// Monospace.
886		#[serde(skip_serializing_if = "Option::is_none")]
887		mono:        Option<bool>,
888	},
889	/// Keycap chords.
890	Keys {
891		/// Chords, each a list of key names.
892		keys: Vec<Vec<String>>,
893	},
894	/// Toggle chips or an ordered list.
895	Multi {
896		/// Enabled values.
897		values:  Vec<String>,
898		/// The options.
899		options: Vec<ChoiceOption>,
900		/// A reorderable list.
901		#[serde(skip_serializing_if = "Option::is_none")]
902		ordered: Option<bool>,
903	},
904	/// A button.
905	Action {
906		/// The action (default `edit`).
907		#[serde(skip_serializing_if = "Option::is_none")]
908		act:   Option<String>,
909		/// The label.
910		#[serde(skip_serializing_if = "Option::is_none")]
911		label: Option<String>,
912	},
913}
914
915/// The `prefs` row being edited with the keyboard.
916#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
917pub struct Editing {
918	/// The row.
919	pub row:    String,
920	/// The highlighted option.
921	#[serde(skip_serializing_if = "Option::is_none")]
922	pub option: Option<String>,
923	/// A text row's draft.
924	#[serde(skip_serializing_if = "Option::is_none")]
925	pub draft:  Option<String>,
926	/// The caret in the draft (UTF-16).
927	#[serde(skip_serializing_if = "Option::is_none")]
928	pub cursor: Option<usize>,
929}