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