1use crate::config;
2
3pub use parser::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum Layout {
7 Pane(PaneGeom),
8 H(PaneGeom, Vec<Layout>),
9 V(PaneGeom, Vec<Layout>),
10}
11
12impl Layout {
13 pub fn parse(input: &str) -> Result<Layout, Error> {
14 Ok(parser::parse_layout(input)?.1)
15 }
16
17 pub fn geom(&self) -> &PaneGeom {
18 match self {
19 Layout::Pane(geom) => geom,
20 Layout::H(geom, _) => geom,
21 Layout::V(geom, _) => geom,
22 }
23 }
24
25 pub fn width(&self) -> u32 {
26 self.geom().width()
27 }
28
29 pub fn height(&self) -> u32 {
30 self.geom().height()
31 }
32}
33
34impl From<Layout> for config::Split {
35 fn from(split: Layout) -> Self {
36 const LINE_WIDTH: f32 = 1.0;
37
38 match split {
39 Layout::Pane(_) => config::Split::default(),
40 Layout::H(_, mut splits) => {
41 let Some(last_split) = splits.pop() else {
42 return config::Split::default();
43 };
44
45 let mut acc_width = last_split.width() as f32;
46 let mut acc_split = last_split.into();
47
48 for left_split in splits.into_iter().rev() {
51 let new_width = acc_width + left_split.width() as f32 - LINE_WIDTH;
52 let right_width_percent = (acc_width * 100f32 / new_width).round();
53 acc_split = config::Split::H {
54 left: config::HSplitPart {
55 width: None,
56 split: Box::new(left_split.into()),
57 },
58 right: config::HSplitPart {
59 width: Some(format!("{:.0}%", right_width_percent)),
60 split: Box::new(acc_split),
61 },
62 };
63 acc_width = new_width;
64 }
65 acc_split
66 }
67 Layout::V(_, mut splits) => {
68 let Some(last_split) = splits.pop() else {
69 return config::Split::default();
70 };
71
72 let mut acc_height = last_split.height() as f32;
73 let mut acc_split = last_split.into();
74
75 for top_split in splits.into_iter().rev() {
78 let new_height = acc_height + top_split.height() as f32 - LINE_WIDTH;
79 let bottom_height_percent = (acc_height * 100f32 / new_height).round();
80 acc_split = config::Split::V {
81 top: config::VSplitPart {
82 height: None,
83 split: Box::new(top_split.into()),
84 },
85 bottom: config::VSplitPart {
86 height: Some(format!("{:.0}%", bottom_height_percent)),
87 split: Box::new(acc_split),
88 },
89 };
90 acc_height = new_height;
91 }
92 acc_split
93 }
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
99pub struct PaneGeom {
100 pub size: Size,
101 pub x_offset: u32,
102 pub y_offset: u32,
103}
104
105impl PaneGeom {
106 pub fn width(&self) -> u32 {
107 self.size.width
108 }
109
110 pub fn height(&self) -> u32 {
111 self.size.height
112 }
113}
114
115#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
116pub struct Size {
117 pub width: u32,
118 pub height: u32,
119}
120
121impl Size {
122 fn new(width: u32, height: u32) -> Self {
123 Self { width, height }
124 }
125}
126
127mod parser {
128 use nom::{
129 branch::alt,
130 bytes::complete::{tag, take, take_until},
131 character::complete::{digit1, u32},
132 combinator::{all_consuming, map, value},
133 multi::separated_list1,
134 sequence::{delimited, pair, preceded, terminated},
135 IResult, Parser,
136 };
137 use thiserror::Error;
138
139 use super::*;
140
141 pub(super) fn parse_layout(i: I) -> Result<Layout> {
142 all_consuming(layout).parse(i)
143 }
144
145 #[derive(Debug, Error)]
146 pub enum Error {
147 #[error("layout parse error: {0}")]
148 ParseError(String),
149 }
150
151 impl<E: std::error::Error> From<nom::Err<E>> for Error {
152 fn from(err: nom::Err<E>) -> Self {
153 Error::ParseError(format!("{}", err))
154 }
155 }
156
157 type I<'a> = &'a str;
158 type Result<'a, A> = IResult<I<'a>, A>;
159
160 fn layout(i: I) -> Result<Layout> {
161 preceded(checksum, split).parse(i)
162 }
163
164 fn split(i: I) -> Result<Layout> {
165 alt((pane_split, h_split, v_split)).parse(i)
166 }
167
168 fn pane_split(i: I) -> Result<Layout> {
169 map(terminated(pane_geom, pair(tag(","), digit1)), Layout::Pane).parse(i)
170 }
171
172 fn h_split(i: I) -> Result<Layout> {
173 map(
174 pair(
175 pane_geom,
176 delimited(tag("{"), separated_list1(tag(","), split), tag("}")),
177 ),
178 |(pane, splits)| Layout::H(pane, splits),
179 )
180 .parse(i)
181 }
182
183 fn v_split(i: I) -> Result<Layout> {
184 map(
185 pair(
186 pane_geom,
187 delimited(tag("["), separated_list1(tag(","), split), tag("]")),
188 ),
189 |(pane, splits)| Layout::V(pane, splits),
190 )
191 .parse(i)
192 }
193
194 fn pane_geom(i: I) -> Result<PaneGeom> {
195 map(
196 (size, tag(","), u32, tag(","), u32),
197 |(size, _, x_offset, _, y_offset)| PaneGeom {
198 size,
199 x_offset,
200 y_offset,
201 },
202 )
203 .parse(i)
204 }
205
206 fn checksum(i: I) -> Result<()> {
207 value((), (take_until(","), take(1usize))).parse(i)
208 }
209
210 fn size(i: I) -> Result<Size> {
211 map((u32, tag("x"), u32), |(width, _, height)| {
212 Size::new(width, height)
213 })
214 .parse(i)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn sample1() {
224 let sample1 = "4264,401x112,0,0{200x112,0,0[200x56,0,0,546,200x55,0,57,798],200x112,201,0[200x56,201,0,795,200x55,201,57{100x55,201,57,796,99x55,302,57[99x27,302,57,797,99x27,302,85,799]}]}";
225 let layout = Layout::parse(sample1).unwrap();
226
227 use Layout::*;
228
229 assert_eq!(
230 layout,
231 H(
232 PaneGeom {
233 size: Size {
234 width: 401,
235 height: 112,
236 },
237 x_offset: 0,
238 y_offset: 0,
239 },
240 vec![
241 V(
242 PaneGeom {
243 size: Size {
244 width: 200,
245 height: 112,
246 },
247 x_offset: 0,
248 y_offset: 0,
249 },
250 vec![
251 Pane(PaneGeom {
252 size: Size {
253 width: 200,
254 height: 56,
255 },
256 x_offset: 0,
257 y_offset: 0,
258 },),
259 Pane(PaneGeom {
260 size: Size {
261 width: 200,
262 height: 55,
263 },
264 x_offset: 0,
265 y_offset: 57,
266 },),
267 ],
268 ),
269 V(
270 PaneGeom {
271 size: Size {
272 width: 200,
273 height: 112,
274 },
275 x_offset: 201,
276 y_offset: 0,
277 },
278 vec![
279 Pane(PaneGeom {
280 size: Size {
281 width: 200,
282 height: 56,
283 },
284 x_offset: 201,
285 y_offset: 0,
286 },),
287 H(
288 PaneGeom {
289 size: Size {
290 width: 200,
291 height: 55,
292 },
293 x_offset: 201,
294 y_offset: 57,
295 },
296 vec![
297 Pane(PaneGeom {
298 size: Size {
299 width: 100,
300 height: 55,
301 },
302 x_offset: 201,
303 y_offset: 57,
304 },),
305 V(
306 PaneGeom {
307 size: Size {
308 width: 99,
309 height: 55,
310 },
311 x_offset: 302,
312 y_offset: 57,
313 },
314 vec![
315 Pane(PaneGeom {
316 size: Size {
317 width: 99,
318 height: 27,
319 },
320 x_offset: 302,
321 y_offset: 57,
322 },),
323 Pane(PaneGeom {
324 size: Size {
325 width: 99,
326 height: 27,
327 },
328 x_offset: 302,
329 y_offset: 85,
330 },),
331 ],
332 ),
333 ],
334 ),
335 ],
336 ),
337 ],
338 )
339 );
340 }
341}