1use serde::{Deserialize, Serialize};
7
8use crate::node::{Common, IntoNode, Node};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum Direction {
13 #[default]
15 Vertical,
16 Horizontal,
18}
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub enum Flex {
23 Legacy,
25 #[default]
27 Start,
28 End,
30 Center,
32 SpaceBetween,
34 SpaceAround,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
41pub enum Constraint {
42 Length(u16),
44 Percentage(u16),
46 Ratio(u32, u32),
48 Min(u16),
50 Max(u16),
52 Fill(u16),
54}
55
56#[derive(Deserialize)]
57#[serde(untagged)]
58enum ConstraintRaw {
59 Bare(u16),
60 Tagged(ConstraintTagged),
61}
62
63#[derive(Deserialize)]
64enum ConstraintTagged {
65 Length(u16),
66 Percentage(u16),
67 Ratio([u32; 2]),
69 Min(u16),
70 Max(u16),
71 Fill(u16),
72}
73
74impl<'de> Deserialize<'de> for Constraint {
75 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
76 match ConstraintRaw::deserialize(deserializer)? {
77 ConstraintRaw::Bare(n) => Ok(Self::Length(n)),
78 ConstraintRaw::Tagged(tagged) => Ok(match tagged {
79 ConstraintTagged::Length(n) => Self::Length(n),
80 ConstraintTagged::Percentage(p) => Self::Percentage(p),
81 ConstraintTagged::Ratio([a, b]) => Self::Ratio(a, b),
82 ConstraintTagged::Min(n) => Self::Min(n),
83 ConstraintTagged::Max(n) => Self::Max(n),
84 ConstraintTagged::Fill(w) => Self::Fill(w),
85 }),
86 }
87 }
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[serde(untagged)]
93pub enum Margin {
94 Uniform(u16),
96 Axes {
98 horizontal: u16,
100 vertical: u16,
102 },
103}
104
105impl Margin {
106 #[must_use]
108 pub fn uniform(n: u16) -> Self {
109 Self::Uniform(n)
110 }
111
112 #[must_use]
114 pub fn hv(horizontal: u16, vertical: u16) -> Self {
115 Self::Axes {
116 horizontal,
117 vertical,
118 }
119 }
120}
121
122#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct Layout {
127 #[serde(flatten)]
129 pub common: Common,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub direction: Option<Direction>,
133 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub constraints: Vec<Constraint>,
136 pub children: Vec<Node>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub spacing: Option<u16>,
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub margin: Option<Margin>,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub flex: Option<Flex>,
147 #[serde(skip_serializing_if = "Option::is_none")]
150 pub collapse: Option<u16>,
151}
152
153impl Layout {
154 #[must_use]
156 pub fn vertical(constraints: impl IntoIterator<Item = Constraint>) -> Self {
157 Self {
158 direction: Some(Direction::Vertical),
159 constraints: constraints.into_iter().collect(),
160 ..Self::default()
161 }
162 }
163
164 #[must_use]
166 pub fn horizontal(constraints: impl IntoIterator<Item = Constraint>) -> Self {
167 Self {
168 direction: Some(Direction::Horizontal),
169 constraints: constraints.into_iter().collect(),
170 ..Self::default()
171 }
172 }
173
174 #[must_use]
176 pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
177 self.constraints = constraints.into_iter().collect();
178 self
179 }
180
181 #[must_use]
183 pub fn children(mut self, children: impl IntoIterator<Item = Node>) -> Self {
184 self.children = children.into_iter().collect();
185 self
186 }
187
188 #[must_use]
190 pub fn spacing(mut self, spacing: u16) -> Self {
191 self.spacing = Some(spacing);
192 self
193 }
194
195 #[must_use]
197 pub fn margin(mut self, margin: Margin) -> Self {
198 self.margin = Some(margin);
199 self
200 }
201
202 #[must_use]
204 pub fn flex(mut self, flex: Flex) -> Self {
205 self.flex = Some(flex);
206 self
207 }
208
209 #[must_use]
211 pub fn collapse(mut self, collapse: u16) -> Self {
212 self.collapse = Some(collapse);
213 self
214 }
215}
216
217impl IntoNode for Layout {}
218impl From<Layout> for Node {
219 fn from(l: Layout) -> Self {
220 Node::Layout(l)
221 }
222}
223#[cfg(test)]
224mod tests {
225 #![allow(
226 clippy::unwrap_used,
227 clippy::expect_used,
228 clippy::panic,
229 reason = "tests assert by unwrapping"
230 )]
231 use super::*;
232
233 #[test]
234 fn bare_integer_is_length() {
235 let c: Constraint = serde_json::from_str("3").expect("bare int parses");
236 assert_eq!(c, Constraint::Length(3));
237 let c: Constraint = serde_json::from_str(r#"{"Fill":1}"#).expect("tagged parses");
238 assert_eq!(c, Constraint::Fill(1));
239 let c: Constraint = serde_json::from_str(r#"{"Ratio":[1,4]}"#).expect("ratio parses");
240 assert_eq!(c, Constraint::Ratio(1, 4));
241 }
242}