1use facet::Facet;
18use serde::{Deserialize, Serialize};
19
20pub type ActionToken = String;
22
23#[derive(Facet, Serialize, Deserialize, Clone, Debug)]
25#[repr(C)]
26pub enum InputValue {
27 Text(String),
28 Bool(bool),
29 Int(i64),
30}
31
32#[derive(Facet, Serialize, Deserialize, Clone, Debug)]
34#[repr(C)]
35pub enum Action {
36 Fired { token: ActionToken },
38 Input { id: String, value: InputValue },
40 Restore { data: String },
42 Start,
45}
46
47#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
50#[repr(C)]
51pub enum TextStyle { Body, Title, Subtitle, Caption, Emphasis }
52
53#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
54#[repr(C)]
55pub enum ButtonStyle { Filled, Outlined, Text }
56
57#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
58#[repr(C)]
59pub enum CardStyle { Elevated, Outlined, Filled }
60
61#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
63#[repr(C)]
64pub enum Tone { Neutral, Success, Warning, Danger, Info }
65
66#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
67#[repr(C)]
68pub enum Spacing { Xs, Sm, Md, Lg, Xl }
69
70#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
72#[repr(C)]
73pub enum Icon { Delete, Add, Edit, Close, Settings, Check, Star }
74
75#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
76#[repr(C)]
77pub enum ImageShape { Square, Rounded, Circle }
78
79#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
80#[repr(C)]
81pub enum ImageRatio { Wide, Square, Tall }
82
83#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
84#[repr(C)]
85pub enum BoxAlign { TopStart, TopEnd, Center, BottomStart, BottomCenter, BottomEnd }
86
87#[derive(Facet, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
90#[repr(C)]
91pub enum ProjectColor { Indigo, Teal, Coral, Amber, Lime, Pink }
92
93#[derive(Facet, Serialize, Deserialize, Clone, Debug)]
96#[repr(C)]
97pub struct Tab {
98 pub label: String,
99 pub selected: bool,
100 pub on_select: ActionToken,
101}
102
103#[derive(Facet, Serialize, Deserialize, Clone, Debug)]
107#[repr(C)]
108pub enum Widget {
109 Text { content: String, style: TextStyle },
111 Image { source: String, shape: ImageShape, ratio: ImageRatio },
112 Badge { label: String, tone: Tone },
113 ColorDot { color: ProjectColor },
115 Divider,
116 Spacer { size: Spacing },
117 Row { children: Vec<Widget> },
119 Column { children: Vec<Widget> },
120 Card { child: Box<Widget>, style: CardStyle, on_press: Option<ActionToken> },
122 Box { children: Vec<Widget>, align: BoxAlign, scrim: bool },
126 Grid { children: Vec<Widget> },
128 Button { label: String, style: ButtonStyle, on_press: ActionToken },
130 IconButton { icon: Icon, on_press: ActionToken },
131 Chip { label: String, selected: bool, on_press: ActionToken },
132 TextField { id: String, placeholder: String, value: String },
133 Toggle { id: String, label: String, value: bool },
134 Checkbox { id: String, label: String, value: bool },
135 Slider { id: String, value: i32, max: i32 },
137 Stepper { value: i32, on_decrement: ActionToken, on_increment: ActionToken },
139 Scaffold {
148 title: String,
149 body: Box<Widget>,
150 tabs: Vec<Tab>,
151 back: Option<ActionToken>,
152 dark_mode: bool,
153 route: String,
154 depth: u32,
155 },
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use serde::Serialize;
162 use serde::de::DeserializeOwned;
163
164 fn round_trips<T: Serialize + DeserializeOwned>(value: &T) {
167 let a = serde_json::to_string(value).expect("serialize");
168 let back: T = serde_json::from_str(&a).expect("deserialize");
169 let b = serde_json::to_string(&back).expect("re-serialize");
170 assert_eq!(a, b);
171 }
172
173 #[test]
174 fn action_round_trips() {
175 round_trips(&Action::Start);
176 round_trips(&Action::Fired { token: "tok".to_string() });
177 round_trips(&Action::Input { id: "field".to_string(), value: InputValue::Bool(true) });
178 round_trips(&Action::Restore { data: "{}".to_string() });
179 }
180
181 #[test]
182 fn widget_round_trips() {
183 round_trips(&Widget::Text { content: "hi".to_string(), style: TextStyle::Title });
184 round_trips(&Widget::ColorDot { color: ProjectColor::Teal });
185 round_trips(&Widget::Scaffold {
186 title: "T".to_string(),
187 body: Box::new(Widget::Divider),
188 tabs: vec![Tab { label: "A".to_string(), selected: true, on_select: "t".to_string() }],
189 back: Some("b".to_string()),
190 dark_mode: true,
191 route: "r".to_string(),
192 depth: 2,
193 });
194 }
195}