1use crate::core::{Rectangle, Size};
2use crate::pane_grid::{Axis, Pane, Split};
3
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone)]
10pub enum Node {
11 Split {
13 id: Split,
15
16 axis: Axis,
18
19 ratio: f32,
21
22 a: Box<Node>,
24
25 b: Box<Node>,
27 },
28 Pane(Pane),
30}
31
32#[derive(Debug)]
33enum Count {
34 Split {
35 horizontal: usize,
36 vertical: usize,
37 a: Box<Count>,
38 b: Box<Count>,
39 },
40 Pane,
41}
42
43impl Count {
44 fn horizontal(&self) -> usize {
45 match self {
46 Count::Split { horizontal, .. } => *horizontal,
47 Count::Pane => 0,
48 }
49 }
50
51 fn vertical(&self) -> usize {
52 match self {
53 Count::Split { vertical, .. } => *vertical,
54 Count::Pane => 0,
55 }
56 }
57}
58
59impl Node {
60 pub fn splits(&self) -> impl Iterator<Item = &Split> {
62 let mut unvisited_nodes = vec![self];
63
64 std::iter::from_fn(move || {
65 while let Some(node) = unvisited_nodes.pop() {
66 if let Node::Split { id, a, b, .. } = node {
67 unvisited_nodes.push(a);
68 unvisited_nodes.push(b);
69
70 return Some(id);
71 }
72 }
73
74 None
75 })
76 }
77
78 fn count(&self) -> Count {
79 match self {
80 Node::Split { a, b, axis, .. } => {
81 let a = a.count();
82 let b = b.count();
83
84 let (horizontal, vertical) = match axis {
85 Axis::Horizontal => (
86 1 + a.horizontal() + b.horizontal(),
87 a.vertical().max(b.vertical()),
88 ),
89 Axis::Vertical => (
90 a.horizontal().max(b.horizontal()),
91 1 + a.vertical() + b.vertical(),
92 ),
93 };
94
95 Count::Split {
96 horizontal,
97 vertical,
98 a: Box::new(a),
99 b: Box::new(b),
100 }
101 }
102 Node::Pane(_) => Count::Pane,
103 }
104 }
105
106 pub fn pane_regions(
109 &self,
110 spacing: f32,
111 min_size: f32,
112 bounds: Size,
113 ) -> BTreeMap<Pane, Rectangle> {
114 let mut regions = BTreeMap::new();
115 let count = self.count();
116
117 self.compute_regions(
118 spacing,
119 min_size,
120 &Rectangle {
121 x: 0.0,
122 y: 0.0,
123 width: bounds.width,
124 height: bounds.height,
125 },
126 &count,
127 &mut regions,
128 );
129
130 regions
131 }
132
133 pub fn split_regions(
137 &self,
138 spacing: f32,
139 min_size: f32,
140 bounds: Size,
141 ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
142 let mut splits = BTreeMap::new();
143 let count = self.count();
144
145 self.compute_splits(
146 spacing,
147 min_size,
148 &Rectangle {
149 x: 0.0,
150 y: 0.0,
151 width: bounds.width,
152 height: bounds.height,
153 },
154 &count,
155 &mut splits,
156 );
157
158 splits
159 }
160
161 pub(crate) fn find(&mut self, pane: Pane) -> Option<&mut Node> {
162 match self {
163 Node::Split { a, b, .. } => a.find(pane).or_else(move || b.find(pane)),
164 Node::Pane(p) => {
165 if *p == pane {
166 Some(self)
167 } else {
168 None
169 }
170 }
171 }
172 }
173
174 pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
175 *self = Node::Split {
176 id,
177 axis,
178 ratio: 0.5,
179 a: Box::new(self.clone()),
180 b: Box::new(Node::Pane(new_pane)),
181 };
182 }
183
184 pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) {
185 *self = Node::Split {
186 id,
187 axis,
188 ratio: 0.5,
189 a: Box::new(Node::Pane(pane)),
190 b: Box::new(self.clone()),
191 };
192 }
193
194 pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
195 if let Node::Split { a, b, .. } = self {
196 a.update(f);
197 b.update(f);
198 }
199
200 f(self);
201 }
202
203 pub(crate) fn resize(&mut self, split: Split, percentage: f32) -> bool {
204 match self {
205 Node::Split {
206 id, ratio, a, b, ..
207 } => {
208 if *id == split {
209 *ratio = percentage;
210
211 true
212 } else if a.resize(split, percentage) {
213 true
214 } else {
215 b.resize(split, percentage)
216 }
217 }
218 Node::Pane(_) => false,
219 }
220 }
221
222 pub(crate) fn remove(&mut self, pane: Pane) -> Option<Pane> {
223 match self {
224 Node::Split { a, b, .. } => {
225 if a.pane() == Some(pane) {
226 *self = *b.clone();
227 Some(self.first_pane())
228 } else if b.pane() == Some(pane) {
229 *self = *a.clone();
230 Some(self.first_pane())
231 } else {
232 a.remove(pane).or_else(|| b.remove(pane))
233 }
234 }
235 Node::Pane(_) => None,
236 }
237 }
238
239 fn pane(&self) -> Option<Pane> {
240 match self {
241 Node::Split { .. } => None,
242 Node::Pane(pane) => Some(*pane),
243 }
244 }
245
246 fn first_pane(&self) -> Pane {
247 match self {
248 Node::Split { a, .. } => a.first_pane(),
249 Node::Pane(pane) => *pane,
250 }
251 }
252
253 fn compute_regions(
254 &self,
255 spacing: f32,
256 min_size: f32,
257 current: &Rectangle,
258 count: &Count,
259 regions: &mut BTreeMap<Pane, Rectangle>,
260 ) {
261 match (self, count) {
262 (
263 Node::Split {
264 axis, ratio, a, b, ..
265 },
266 Count::Split {
267 a: count_a,
268 b: count_b,
269 ..
270 },
271 ) => {
272 let (a_factor, b_factor) = match axis {
273 Axis::Horizontal => (count_a.horizontal(), count_b.horizontal()),
274 Axis::Vertical => (count_a.vertical(), count_b.vertical()),
275 };
276
277 let (region_a, region_b, _ratio) = axis.split(
278 current,
279 *ratio,
280 spacing,
281 min_size * (a_factor + 1) as f32 + spacing * a_factor as f32,
282 min_size * (b_factor + 1) as f32 + spacing * b_factor as f32,
283 );
284
285 a.compute_regions(spacing, min_size, ®ion_a, count_a, regions);
286 b.compute_regions(spacing, min_size, ®ion_b, count_b, regions);
287 }
288 (Node::Pane(pane), Count::Pane) => {
289 let _ = regions.insert(*pane, *current);
290 }
291 _ => {
292 unreachable!("Node configuration and count do not match")
293 }
294 }
295 }
296
297 fn compute_splits(
298 &self,
299 spacing: f32,
300 min_size: f32,
301 current: &Rectangle,
302 count: &Count,
303 splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
304 ) {
305 match (self, count) {
306 (
307 Node::Split {
308 axis,
309 ratio,
310 a,
311 b,
312 id,
313 },
314 Count::Split {
315 a: count_a,
316 b: count_b,
317 ..
318 },
319 ) => {
320 let (a_factor, b_factor) = match axis {
321 Axis::Horizontal => (count_a.horizontal(), count_b.horizontal()),
322 Axis::Vertical => (count_a.vertical(), count_b.vertical()),
323 };
324
325 let (region_a, region_b, ratio) = axis.split(
326 current,
327 *ratio,
328 spacing,
329 min_size * (a_factor + 1) as f32 + spacing * a_factor as f32,
330 min_size * (b_factor + 1) as f32 + spacing * b_factor as f32,
331 );
332
333 let _ = splits.insert(*id, (*axis, *current, ratio));
334
335 a.compute_splits(spacing, min_size, ®ion_a, count_a, splits);
336 b.compute_splits(spacing, min_size, ®ion_b, count_b, splits);
337 }
338 (Node::Pane(_), Count::Pane) => {}
339 _ => {
340 unreachable!("Node configuration and split count do not match")
341 }
342 }
343 }
344}
345
346impl std::hash::Hash for Node {
347 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
348 match self {
349 Node::Split {
350 id,
351 axis,
352 ratio,
353 a,
354 b,
355 } => {
356 id.hash(state);
357 axis.hash(state);
358 ((ratio * 100_000.0) as u32).hash(state);
359 a.hash(state);
360 b.hash(state);
361 }
362 Node::Pane(pane) => {
363 pane.hash(state);
364 }
365 }
366 }
367}