1use crate::LayoutItem;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub enum LayoutIssue {
10 DuplicateId {
11 id: String,
12 },
13 NonPositiveSize {
14 id: String,
15 w: i32,
16 h: i32,
17 },
18 OutOfHorizontalBounds {
19 id: String,
20 x: i32,
21 w: i32,
22 cols: i32,
23 },
24 MinMaxWidth {
25 id: String,
26 w: i32,
27 min_w: Option<i32>,
28 max_w: Option<i32>,
29 },
30 MinMaxHeight {
31 id: String,
32 h: i32,
33 min_h: Option<i32>,
34 max_h: Option<i32>,
35 },
36 Overlap {
37 a: String,
38 b: String,
39 },
40}
41
42pub fn validate_layout(layout: &[LayoutItem], cols: i32) -> Result<(), Vec<LayoutIssue>> {
44 let mut issues = Vec::new();
45 let mut seen: HashMap<&str, usize> = HashMap::new();
46
47 for item in layout {
48 if seen.insert(&item.id[..], 1).is_some() {
49 issues.push(LayoutIssue::DuplicateId {
50 id: item.id.clone(),
51 });
52 }
53 if item.w < 1 || item.h < 1 {
54 issues.push(LayoutIssue::NonPositiveSize {
55 id: item.id.clone(),
56 w: item.w,
57 h: item.h,
58 });
59 }
60 if item.x < 0 || item.x + item.w > cols {
61 issues.push(LayoutIssue::OutOfHorizontalBounds {
62 id: item.id.clone(),
63 x: item.x,
64 w: item.w,
65 cols,
66 });
67 }
68 if let Some(min) = item.min_w
69 && item.w < min
70 {
71 issues.push(LayoutIssue::MinMaxWidth {
72 id: item.id.clone(),
73 w: item.w,
74 min_w: item.min_w,
75 max_w: item.max_w,
76 });
77 }
78 if let Some(max) = item.max_w
79 && item.w > max
80 {
81 issues.push(LayoutIssue::MinMaxWidth {
82 id: item.id.clone(),
83 w: item.w,
84 min_w: item.min_w,
85 max_w: item.max_w,
86 });
87 }
88 if let Some(min) = item.min_h
89 && item.h < min
90 {
91 issues.push(LayoutIssue::MinMaxHeight {
92 id: item.id.clone(),
93 h: item.h,
94 min_h: item.min_h,
95 max_h: item.max_h,
96 });
97 }
98 if let Some(max) = item.max_h
99 && item.h > max
100 {
101 issues.push(LayoutIssue::MinMaxHeight {
102 id: item.id.clone(),
103 h: item.h,
104 min_h: item.min_h,
105 max_h: item.max_h,
106 });
107 }
108 }
109
110 for i in 0..layout.len() {
111 for j in (i + 1)..layout.len() {
112 if crate::collides(&layout[i], &layout[j]) {
113 issues.push(LayoutIssue::Overlap {
114 a: layout[i].id.clone(),
115 b: layout[j].id.clone(),
116 });
117 }
118 }
119 }
120
121 if issues.is_empty() {
122 Ok(())
123 } else {
124 Err(issues)
125 }
126}
127
128pub fn repair_layout(layout: &mut [LayoutItem], cols: i32) {
130 let mut seen: HashMap<String, u32> = HashMap::new();
131 for item in layout.iter_mut() {
132 let base = item.id.clone();
133 let n = seen.entry(base.clone()).or_insert(0);
134 if *n > 0 {
135 item.id = format!("{base}-{}", *n);
136 }
137 *n += 1;
138
139 item.w = item.w.max(1);
140 item.h = item.h.max(1);
141 item.x = item.x.max(0);
142 if item.w > cols {
143 item.w = cols;
144 }
145 item.x = item.x.min((cols - item.w).max(0));
146
147 if let Some(min) = item.min_w {
148 item.w = item.w.max(min);
149 }
150 if let Some(max) = item.max_w {
151 item.w = item.w.min(max);
152 }
153 if let Some(min) = item.min_h {
154 item.h = item.h.max(min);
155 }
156 if let Some(max) = item.max_h {
157 item.h = item.h.min(max);
158 }
159
160 item.w = item.w.min(cols);
161 item.x = item.x.min((cols - item.w).max(0));
162 }
163}