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, .. } => {
164 a.find(pane).or_else(move || b.find(pane))
165 }
166 Node::Pane(p) => {
167 if *p == pane {
168 Some(self)
169 } else {
170 None
171 }
172 }
173 }
174 }
175
176 pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
177 *self = Node::Split {
178 id,
179 axis,
180 ratio: 0.5,
181 a: Box::new(self.clone()),
182 b: Box::new(Node::Pane(new_pane)),
183 };
184 }
185
186 pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) {
187 *self = Node::Split {
188 id,
189 axis,
190 ratio: 0.5,
191 a: Box::new(Node::Pane(pane)),
192 b: Box::new(self.clone()),
193 };
194 }
195
196 pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
197 if let Node::Split { a, b, .. } = self {
198 a.update(f);
199 b.update(f);
200 }
201
202 f(self);
203 }
204
205 pub(crate) fn resize(&mut self, split: Split, percentage: f32) -> bool {
206 match self {
207 Node::Split {
208 id, ratio, a, b, ..
209 } => {
210 if *id == split {
211 *ratio = percentage;
212
213 true
214 } else if a.resize(split, percentage) {
215 true
216 } else {
217 b.resize(split, percentage)
218 }
219 }
220 Node::Pane(_) => false,
221 }
222 }
223
224 pub(crate) fn remove(&mut self, pane: Pane) -> Option<Pane> {
225 match self {
226 Node::Split { a, b, .. } => {
227 if a.pane() == Some(pane) {
228 *self = *b.clone();
229 Some(self.first_pane())
230 } else if b.pane() == Some(pane) {
231 *self = *a.clone();
232 Some(self.first_pane())
233 } else {
234 a.remove(pane).or_else(|| b.remove(pane))
235 }
236 }
237 Node::Pane(_) => None,
238 }
239 }
240
241 fn pane(&self) -> Option<Pane> {
242 match self {
243 Node::Split { .. } => None,
244 Node::Pane(pane) => Some(*pane),
245 }
246 }
247
248 fn first_pane(&self) -> Pane {
249 match self {
250 Node::Split { a, .. } => a.first_pane(),
251 Node::Pane(pane) => *pane,
252 }
253 }
254
255 fn compute_regions(
256 &self,
257 spacing: f32,
258 min_size: f32,
259 current: &Rectangle,
260 count: &Count,
261 regions: &mut BTreeMap<Pane, Rectangle>,
262 ) {
263 match (self, count) {
264 (
265 Node::Split {
266 axis, ratio, a, b, ..
267 },
268 Count::Split {
269 a: count_a,
270 b: count_b,
271 ..
272 },
273 ) => {
274 let (a_factor, b_factor) = match axis {
275 Axis::Horizontal => {
276 (count_a.horizontal(), count_b.horizontal())
277 }
278 Axis::Vertical => (count_a.vertical(), count_b.vertical()),
279 };
280
281 let (region_a, region_b, _ratio) = axis.split(
282 current,
283 *ratio,
284 spacing,
285 min_size * (a_factor + 1) as f32
286 + spacing * a_factor as f32,
287 min_size * (b_factor + 1) as f32
288 + spacing * b_factor as f32,
289 );
290
291 a.compute_regions(
292 spacing, min_size, ®ion_a, count_a, regions,
293 );
294 b.compute_regions(
295 spacing, min_size, ®ion_b, count_b, regions,
296 );
297 }
298 (Node::Pane(pane), Count::Pane) => {
299 let _ = regions.insert(*pane, *current);
300 }
301 _ => {
302 unreachable!("Node configuration and count do not match")
303 }
304 }
305 }
306
307 fn compute_splits(
308 &self,
309 spacing: f32,
310 min_size: f32,
311 current: &Rectangle,
312 count: &Count,
313 splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
314 ) {
315 match (self, count) {
316 (
317 Node::Split {
318 axis,
319 ratio,
320 a,
321 b,
322 id,
323 },
324 Count::Split {
325 a: count_a,
326 b: count_b,
327 ..
328 },
329 ) => {
330 let (a_factor, b_factor) = match axis {
331 Axis::Horizontal => {
332 (count_a.horizontal(), count_b.horizontal())
333 }
334 Axis::Vertical => (count_a.vertical(), count_b.vertical()),
335 };
336
337 let (region_a, region_b, ratio) = axis.split(
338 current,
339 *ratio,
340 spacing,
341 min_size * (a_factor + 1) as f32
342 + spacing * a_factor as f32,
343 min_size * (b_factor + 1) as f32
344 + spacing * b_factor as f32,
345 );
346
347 let _ = splits.insert(*id, (*axis, *current, ratio));
348
349 a.compute_splits(spacing, min_size, ®ion_a, count_a, splits);
350 b.compute_splits(spacing, min_size, ®ion_b, count_b, splits);
351 }
352 (Node::Pane(_), Count::Pane) => {}
353 _ => {
354 unreachable!("Node configuration and split count do not match")
355 }
356 }
357 }
358}
359
360impl std::hash::Hash for Node {
361 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
362 match self {
363 Node::Split {
364 id,
365 axis,
366 ratio,
367 a,
368 b,
369 } => {
370 id.hash(state);
371 axis.hash(state);
372 ((ratio * 100_000.0) as u32).hash(state);
373 a.hash(state);
374 b.hash(state);
375 }
376 Node::Pane(pane) => {
377 pane.hash(state);
378 }
379 }
380 }
381}