Skip to main content

rusty_rich/
layout.rs

1//! Layout — split-pane layout system. Equivalent to Rich's `layout.py`.
2
3use std::collections::HashMap;
4
5use crate::console::{Console, ConsoleOptions, DynRenderable, Renderable};
6
7// ---------------------------------------------------------------------------
8// Region
9// ---------------------------------------------------------------------------
10
11/// A region on screen.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Region {
14    pub x: usize,
15    pub y: usize,
16    pub width: usize,
17    pub height: usize,
18}
19
20// ---------------------------------------------------------------------------
21// Direction
22// ---------------------------------------------------------------------------
23
24/// Direction of a split.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Direction {
27    /// Split content side by side (left to right).
28    Horizontal,
29    /// Split content stacked (top to bottom).
30    Vertical,
31}
32
33// ---------------------------------------------------------------------------
34// LayoutNode
35// ---------------------------------------------------------------------------
36
37/// A layout node — can be a leaf (containing a renderable) or a split.
38#[derive(Debug, Clone)]
39pub enum LayoutNode {
40    /// A split container with children and a direction.
41    Split {
42        /// Direction of the split (horizontal or vertical).
43        direction: Direction,
44        /// Relative size ratios for children.
45        sizes: Vec<usize>,
46        /// Child layout nodes.
47        children: Vec<LayoutNode>,
48    },
49    /// A leaf with a renderable name (placeholder) and optional fixed size.
50    Leaf {
51        /// Name identifier for this leaf.
52        name: String,
53        /// Optional label for the renderable.
54        renderable: Option<String>,
55        /// Optional fixed size constraint.
56        size: Option<usize>,
57    },
58}
59
60impl LayoutNode {
61    /// Create a new split node with equal-size children.
62    ///
63    /// Each child is assigned an initial ratio of 1. Use
64    /// [`sizes`](LayoutNode::sizes) to customize the ratios.
65    pub fn split(direction: Direction, children: Vec<LayoutNode>) -> Self {
66        let sizes = vec![1; children.len()];
67        Self::Split {
68            direction,
69            sizes,
70            children,
71        }
72    }
73
74    /// Builder: set the size ratios for the children of this split node.
75    pub fn sizes(mut self, sizes: Vec<usize>) -> Self {
76        if let Self::Split {
77            sizes: ref mut s, ..
78        } = self
79        {
80            *s = sizes;
81        }
82        self
83    }
84}
85
86// ---------------------------------------------------------------------------
87// Layout
88// ---------------------------------------------------------------------------
89
90/// The Layout compute engine. Assigns screen regions to a tree of layout
91/// nodes by recursively splitting available space.
92#[derive(Debug)]
93pub struct Layout {
94    /// The root [`LayoutNode`] defining the split hierarchy.
95    pub root: LayoutNode,
96    /// Whether the layout is visible.
97    pub visible: bool,
98    /// Minimum size for any region.
99    pub minimum_size: usize,
100    /// Named renderables for leaf nodes.
101    pub renderables: HashMap<String, DynRenderable>,
102    /// Active splitters for dividing regions among children.
103    pub splitters: Vec<Box<dyn Splitter>>,
104    /// Auto-incrementing pane ID counter.
105    next_pane_id: usize,
106}
107
108impl Layout {
109    /// Create a new layout with the given root node.
110    pub fn new(root: LayoutNode) -> Self {
111        Self {
112            root,
113            visible: true,
114            minimum_size: 1,
115            renderables: HashMap::new(),
116            splitters: Vec::new(),
117            next_pane_id: 0,
118        }
119    }
120
121    /// Create a new layout from a named renderable (single-pane leaf).
122    ///
123    /// This is a convenience constructor that wraps the renderable in a leaf
124    /// node with the given name.
125    pub fn from_renderable(
126        name: impl Into<String>,
127        renderable: impl Renderable + Send + Sync + 'static,
128    ) -> Self {
129        let name = name.into();
130        let node = LayoutNode::Leaf {
131            name: name.clone(),
132            renderable: None,
133            size: None,
134        };
135        let mut renderables = HashMap::new();
136        renderables.insert(name, DynRenderable::new(renderable));
137        Self {
138            root: node,
139            visible: true,
140            minimum_size: 1,
141            renderables,
142            splitters: Vec::new(),
143            next_pane_id: 1,
144        }
145    }
146
147    /// Split the current root node into the given direction.
148    ///
149    /// The existing root becomes the first child of the new split, and a new
150    /// empty leaf is added as the second child.
151    /// Returns a mutable reference to the root node.
152    pub fn split(&mut self, direction: Direction) -> &mut LayoutNode {
153        let name_a = format!("_split_a_{}", self.next_pane_id);
154        let name_b = format!("_split_b_{}", self.next_pane_id + 1);
155        self.next_pane_id += 2;
156
157        let old_root = std::mem::replace(
158            &mut self.root,
159            LayoutNode::Split {
160                direction,
161                sizes: vec![1, 1],
162                children: vec![
163                    LayoutNode::Leaf {
164                        name: name_a,
165                        renderable: None,
166                        size: None,
167                    },
168                    LayoutNode::Leaf {
169                        name: name_b,
170                        renderable: None,
171                        size: None,
172                    },
173                ],
174            },
175        );
176
177        // Put the old root back as the first child
178        if let LayoutNode::Split {
179            ref mut children, ..
180        } = self.root
181        {
182            children[0] = old_root;
183        }
184
185        &mut self.root
186    }
187
188    /// Remove the split at the root.  If the root is a split, it is replaced
189    /// with its first child.  If the root is already a leaf, this is a no-op.
190    pub fn unsplit(&mut self) {
191        let replacement = std::mem::replace(
192            &mut self.root,
193            LayoutNode::Leaf {
194                name: String::new(),
195                renderable: None,
196                size: None,
197            },
198        );
199        match replacement {
200            LayoutNode::Split { mut children, .. } if !children.is_empty() => {
201                self.root = children.remove(0);
202            }
203            other => {
204                self.root = other;
205            }
206        }
207    }
208
209    /// Convenience: split the root into a column layout (horizontal split).
210    pub fn split_column(&mut self) -> &mut Self {
211        self.split(Direction::Horizontal);
212        self
213    }
214
215    /// Convenience: split the root into a row layout (vertical split).
216    pub fn split_row(&mut self) -> &mut Self {
217        self.split(Direction::Vertical);
218        self
219    }
220
221    /// Add a child pane with a renderable and a ratio weight.
222    ///
223    /// If the root is already a `Split` node, the new child is appended.
224    /// If the root is a `Leaf`, it is first converted to a `Split` containing
225    /// the old leaf and the new child.
226    ///
227    /// Returns the pane ID (index of the new child in the children list).
228    pub fn add_split(
229        &mut self,
230        renderable: impl Renderable + Send + Sync + 'static,
231        ratio: usize,
232    ) -> usize {
233        let id = self.next_pane_id;
234        self.next_pane_id += 1;
235        let name = format!("_pane_{}", id);
236        self.renderables
237            .insert(name.clone(), DynRenderable::new(renderable));
238
239        match &mut self.root {
240            LayoutNode::Split {
241                children, sizes, ..
242            } => {
243                children.push(LayoutNode::Leaf {
244                    name: name.clone(),
245                    renderable: None,
246                    size: None,
247                });
248                sizes.push(ratio);
249                children.len() - 1
250            }
251            LayoutNode::Leaf { .. } => {
252                // Convert leaf root to a Split containing old + new children
253                let old_root = std::mem::replace(
254                    &mut self.root,
255                    LayoutNode::Split {
256                        direction: Direction::Vertical,
257                        sizes: vec![1, ratio],
258                        children: vec![
259                            LayoutNode::Leaf {
260                                name: String::new(),
261                                renderable: None,
262                                size: None,
263                            },
264                            LayoutNode::Leaf {
265                                name: name.clone(),
266                                renderable: None,
267                                size: None,
268                            },
269                        ],
270                    },
271                );
272                if let LayoutNode::Split {
273                    ref mut children, ..
274                } = self.root
275                {
276                    children[0] = old_root;
277                }
278                1
279            }
280        }
281    }
282
283    /// Get the root renderable (if the root is a leaf and has a renderable).
284    pub fn renderable(&self) -> Option<&dyn Renderable> {
285        match &self.root {
286            LayoutNode::Leaf { name, .. } => {
287                self.renderables.get(name).map(|dr| dr as &dyn Renderable)
288            }
289            _ => None,
290        }
291    }
292
293    /// Get child layout nodes (if the root is a split).
294    pub fn children(&self) -> &[LayoutNode] {
295        match &self.root {
296            LayoutNode::Split { children, .. } => children,
297            _ => &[],
298        }
299    }
300
301    /// Get the active splitters.
302    pub fn splitters(&self) -> Vec<&dyn Splitter> {
303        self.splitters.iter().map(|s| s.as_ref()).collect()
304    }
305
306    /// Get the layout tree root.
307    pub fn tree(&self) -> &LayoutNode {
308        &self.root
309    }
310
311    /// Apply a function to all leaf renderables, replacing each with the
312    /// result.
313    pub fn map(&mut self, f: impl Fn(&dyn Renderable) -> DynRenderable) {
314        let mut new_renderables = HashMap::new();
315        for (name, dr) in &self.renderables {
316            let new_dr = f(dr as &dyn Renderable);
317            new_renderables.insert(name.clone(), new_dr);
318        }
319        self.renderables = new_renderables;
320    }
321
322    /// Get a named renderable from the tree.
323    pub fn get(&self, name: &str) -> Option<&dyn Renderable> {
324        self.renderables.get(name).map(|dr| dr as &dyn Renderable)
325    }
326
327    /// Update a named renderable, returning `true` if it existed.
328    pub fn update(
329        &mut self,
330        name: &str,
331        renderable: impl Renderable + Send + Sync + 'static,
332    ) -> bool {
333        if self.renderables.contains_key(name) {
334            self.renderables
335                .insert(name.to_string(), DynRenderable::new(renderable));
336            true
337        } else {
338            false
339        }
340    }
341
342    /// Refresh the layout on screen by re-rendering all visible regions.
343    ///
344    /// Computes the layout for the current terminal size and renders each
345    /// leaf's renderable into the console.
346    pub fn refresh_screen(&mut self, console: &mut Console) {
347        if !self.visible {
348            return;
349        }
350        let dims = crate::console::ConsoleDimensions::detect();
351        let regions = self.compute(dims.width, dims.height);
352        // Sort regions top-to-bottom so they render in order
353        for (name, _region) in &regions {
354            if let Some(renderable) = self.renderables.get(name) {
355                // Render each pane — in a full implementation we'd clip to
356                // the region; here we just print sequentially.
357                let rendered = renderable.render(&ConsoleOptions::default());
358                let text = rendered.to_ansi();
359                if !text.is_empty() {
360                    console.print_str(&text);
361                }
362            }
363        }
364    }
365
366    /// Compute region assignments by recursively splitting the given area.
367    ///
368    /// Returns a list of `(name, region)` pairs for each leaf node in the
369    /// layout tree.
370    pub fn compute(&self, total_width: usize, total_height: usize) -> Vec<(String, Region)> {
371        let mut regions = Vec::new();
372        let region = Region {
373            x: 0,
374            y: 0,
375            width: total_width,
376            height: total_height,
377        };
378        Self::layout_node(&self.root, region, &mut regions);
379        regions
380    }
381
382    fn layout_node(node: &LayoutNode, region: Region, out: &mut Vec<(String, Region)>) {
383        match node {
384            LayoutNode::Leaf { name, size, .. } => {
385                let mut r = region;
386                if let Some(s) = size {
387                    r.width = r.width.min(*s);
388                    r.height = r.height.min(*s);
389                } else {
390                    r.width = r.width.max(2);
391                    r.height = r.height.max(1);
392                }
393                out.push((name.clone(), r));
394            }
395            LayoutNode::Split {
396                direction,
397                sizes,
398                children,
399            } => {
400                let total_size: usize = sizes.iter().sum();
401                if total_size == 0 || children.is_empty() {
402                    return;
403                }
404                let count = children.len();
405
406                match direction {
407                    Direction::Horizontal => {
408                        let mut x = region.x;
409                        let total_spacing = count.saturating_sub(1);
410                        let avail = region.width.saturating_sub(total_spacing);
411                        for (i, child) in children.iter().enumerate() {
412                            let ratio = sizes.get(i).copied().unwrap_or(1);
413                            let child_w = (avail * ratio) / total_size;
414                            let child_r = Region {
415                                x,
416                                y: region.y,
417                                width: child_w.max(1),
418                                height: region.height,
419                            };
420                            Self::layout_node(child, child_r, out);
421                            x += child_w + 1; // 1 char gutter
422                        }
423                    }
424                    Direction::Vertical => {
425                        let mut y = region.y;
426                        for (i, child) in children.iter().enumerate() {
427                            let ratio = sizes.get(i).copied().unwrap_or(1);
428                            let child_h = (region.height * ratio) / total_size;
429                            let child_r = Region {
430                                x: region.x,
431                                y,
432                                width: region.width,
433                                height: child_h.max(1),
434                            };
435                            Self::layout_node(child, child_r, out);
436                            y += child_h;
437                        }
438                    }
439                }
440            }
441        }
442    }
443}
444
445// ---------------------------------------------------------------------------
446// Splitter trait and implementations
447// ---------------------------------------------------------------------------
448
449/// Trait for layout splitters (interface).
450///
451/// A `Splitter` defines how to divide a [`Region`] among a list of child
452/// [`LayoutNode`]s given a [`Direction`].
453pub trait Splitter: std::fmt::Debug {
454    /// Split `region` among `children` according to `direction`.
455    ///
456    /// Returns one [`Region`] per child.
457    fn split(&self, region: &Region, children: &[LayoutNode], direction: &Direction)
458        -> Vec<Region>;
459}
460
461/// Default splitter that divides space equally among all children.
462#[derive(Debug)]
463pub struct NoSplitter;
464
465impl Splitter for NoSplitter {
466    fn split(
467        &self,
468        region: &Region,
469        children: &[LayoutNode],
470        direction: &Direction,
471    ) -> Vec<Region> {
472        let count = children.len().max(1);
473        match direction {
474            Direction::Horizontal => {
475                let col_width = region.width / count;
476                children
477                    .iter()
478                    .enumerate()
479                    .map(|(i, _)| Region {
480                        x: region.x + i * col_width,
481                        y: region.y,
482                        width: col_width,
483                        height: region.height,
484                    })
485                    .collect()
486            }
487            Direction::Vertical => {
488                let row_height = region.height / count;
489                children
490                    .iter()
491                    .enumerate()
492                    .map(|(i, _)| Region {
493                        x: region.x,
494                        y: region.y + i * row_height,
495                        width: region.width,
496                        height: row_height,
497                    })
498                    .collect()
499            }
500        }
501    }
502}
503
504/// Splits a region into equal-width columns (ignores the direction).
505#[derive(Debug)]
506pub struct ColumnSplitter;
507
508impl Splitter for ColumnSplitter {
509    fn split(
510        &self,
511        region: &Region,
512        children: &[LayoutNode],
513        _direction: &Direction,
514    ) -> Vec<Region> {
515        let count = children.len().max(1);
516        let col_width = region.width / count;
517        children
518            .iter()
519            .enumerate()
520            .map(|(i, _)| Region {
521                x: region.x + i * col_width,
522                y: region.y,
523                width: col_width,
524                height: region.height,
525            })
526            .collect()
527    }
528}
529
530/// Splits a region into equal-height rows (ignores the direction).
531#[derive(Debug)]
532pub struct RowSplitter;
533
534impl Splitter for RowSplitter {
535    fn split(
536        &self,
537        region: &Region,
538        children: &[LayoutNode],
539        _direction: &Direction,
540    ) -> Vec<Region> {
541        let count = children.len().max(1);
542        let row_height = region.height / count;
543        children
544            .iter()
545            .enumerate()
546            .map(|(i, _)| Region {
547                x: region.x,
548                y: region.y + i * row_height,
549                width: region.width,
550                height: row_height,
551            })
552            .collect()
553    }
554}
555
556// ---------------------------------------------------------------------------
557// Tests
558// ---------------------------------------------------------------------------
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn test_region_defaults() {
566        let r = Region {
567            x: 0,
568            y: 0,
569            width: 80,
570            height: 24,
571        };
572        assert_eq!(r.width, 80);
573        assert_eq!(r.height, 24);
574    }
575
576    #[test]
577    fn test_layout_single_leaf() {
578        let node = LayoutNode::Leaf {
579            name: "root".into(),
580            renderable: None,
581            size: None,
582        };
583        let layout = Layout::new(node);
584        let regions = layout.compute(80, 24);
585        assert_eq!(regions.len(), 1);
586        assert_eq!(regions[0].0, "root");
587    }
588
589    #[test]
590    fn test_layout_horizontal_split() {
591        let children = vec![
592            LayoutNode::Leaf {
593                name: "left".into(),
594                renderable: None,
595                size: None,
596            },
597            LayoutNode::Leaf {
598                name: "right".into(),
599                renderable: None,
600                size: None,
601            },
602        ];
603        let node = LayoutNode::split(Direction::Horizontal, children);
604        let layout = Layout::new(node);
605        let regions = layout.compute(80, 24);
606        assert_eq!(regions.len(), 2);
607        assert!(regions[0].1.x < regions[1].1.x);
608    }
609
610    #[test]
611    fn test_layout_vertical_split() {
612        let children = vec![
613            LayoutNode::Leaf {
614                name: "top".into(),
615                renderable: None,
616                size: None,
617            },
618            LayoutNode::Leaf {
619                name: "bottom".into(),
620                renderable: None,
621                size: None,
622            },
623        ];
624        let node = LayoutNode::split(Direction::Vertical, children);
625        let layout = Layout::new(node);
626        let regions = layout.compute(80, 24);
627        assert_eq!(regions.len(), 2);
628        assert!(regions[0].1.y < regions[1].1.y);
629    }
630
631    #[test]
632    fn test_split_method() {
633        let mut layout = Layout::new(LayoutNode::Leaf {
634            name: "root".into(),
635            renderable: None,
636            size: None,
637        });
638        layout.split(Direction::Horizontal);
639        // Root should now be a Split
640        match &layout.root {
641            LayoutNode::Split { children, .. } => {
642                assert_eq!(children.len(), 2);
643            }
644            _ => panic!("expected Split after split()"),
645        }
646    }
647
648    #[test]
649    fn test_unsplit_method() {
650        let mut layout = Layout::new(LayoutNode::Leaf {
651            name: "root".into(),
652            renderable: None,
653            size: None,
654        });
655        layout.split(Direction::Horizontal);
656        layout.unsplit();
657        // Root should be back to a Leaf (the original)
658        match &layout.root {
659            LayoutNode::Leaf { .. } => {} // ok
660            _ => panic!("expected Leaf after unsplit()"),
661        }
662    }
663
664    #[test]
665    fn test_split_column() {
666        let mut layout = Layout::new(LayoutNode::Leaf {
667            name: "root".into(),
668            renderable: None,
669            size: None,
670        });
671        layout.split_column();
672        match &layout.root {
673            LayoutNode::Split { direction, .. } => {
674                assert_eq!(*direction, Direction::Horizontal);
675            }
676            _ => panic!("expected Horizontal split"),
677        }
678    }
679
680    #[test]
681    fn test_split_row() {
682        let mut layout = Layout::new(LayoutNode::Leaf {
683            name: "root".into(),
684            renderable: None,
685            size: None,
686        });
687        layout.split_row();
688        match &layout.root {
689            LayoutNode::Split { direction, .. } => {
690                assert_eq!(*direction, Direction::Vertical);
691            }
692            _ => panic!("expected Vertical split"),
693        }
694    }
695
696    #[test]
697    fn test_children_method() {
698        let mut layout = Layout::new(LayoutNode::Leaf {
699            name: "root".into(),
700            renderable: None,
701            size: None,
702        });
703        // Before split, children is empty
704        assert!(layout.children().is_empty());
705        layout.split(Direction::Horizontal);
706        assert_eq!(layout.children().len(), 2);
707    }
708
709    #[test]
710    fn test_tree_method() {
711        let layout = Layout::new(LayoutNode::Leaf {
712            name: "root".into(),
713            renderable: None,
714            size: None,
715        });
716        match layout.tree() {
717            LayoutNode::Leaf { name, .. } => assert_eq!(name, "root"),
718            _ => panic!("expected Leaf"),
719        }
720    }
721
722    #[test]
723    fn test_renderable_none_for_empty_layout() {
724        let layout = Layout::new(LayoutNode::Leaf {
725            name: "root".into(),
726            renderable: None,
727            size: None,
728        });
729        // No renderable registered
730        assert!(layout.renderable().is_none());
731    }
732
733    #[test]
734    fn test_from_renderable() {
735        let layout = Layout::from_renderable("main", "hello world");
736        assert!(layout.get("main").is_some());
737    }
738
739    #[test]
740    fn test_get_and_update() {
741        let mut layout = Layout::from_renderable("main", "initial");
742        assert!(layout.get("main").is_some());
743
744        let updated = layout.update("main", "updated");
745        assert!(updated);
746
747        // Non-existent key
748        let not_found = layout.update("nonexistent", "nope");
749        assert!(!not_found);
750    }
751
752    #[test]
753    fn test_map() {
754        let mut layout = Layout::from_renderable("main", "hello");
755        layout.map(|_r| DynRenderable::new("mapped"));
756        assert!(layout.get("main").is_some());
757    }
758
759    #[test]
760    fn test_add_split_to_leaf() {
761        let mut layout = Layout::from_renderable("main", "content");
762        let id = layout.add_split("second", 2);
763        // Root should now be a Split
764        match &layout.root {
765            LayoutNode::Split {
766                children, sizes, ..
767            } => {
768                assert_eq!(children.len(), 2);
769                assert_eq!(*sizes, vec![1, 2]);
770                assert_eq!(id, 1);
771            }
772            _ => panic!("expected Split after add_split"),
773        }
774    }
775
776    #[test]
777    fn test_no_splitter() {
778        let splitter = NoSplitter;
779        let children = vec![
780            LayoutNode::Leaf {
781                name: "a".into(),
782                renderable: None,
783                size: None,
784            },
785            LayoutNode::Leaf {
786                name: "b".into(),
787                renderable: None,
788                size: None,
789            },
790        ];
791        let region = Region {
792            x: 0,
793            y: 0,
794            width: 80,
795            height: 24,
796        };
797        let regions = splitter.split(&region, &children, &Direction::Horizontal);
798        assert_eq!(regions.len(), 2);
799        assert_eq!(regions[0].width, 40);
800        assert_eq!(regions[1].width, 40);
801    }
802
803    #[test]
804    fn test_column_splitter() {
805        let splitter = ColumnSplitter;
806        let children = vec![
807            LayoutNode::Leaf {
808                name: "a".into(),
809                renderable: None,
810                size: None,
811            },
812            LayoutNode::Leaf {
813                name: "b".into(),
814                renderable: None,
815                size: None,
816            },
817            LayoutNode::Leaf {
818                name: "c".into(),
819                renderable: None,
820                size: None,
821            },
822        ];
823        let region = Region {
824            x: 0,
825            y: 0,
826            width: 90,
827            height: 24,
828        };
829        let regions = splitter.split(&region, &children, &Direction::Vertical);
830        assert_eq!(regions.len(), 3);
831        assert_eq!(regions[0].width, 30);
832        assert_eq!(regions[1].x, 30);
833        assert_eq!(regions[2].x, 60);
834    }
835
836    #[test]
837    fn test_row_splitter() {
838        let splitter = RowSplitter;
839        let children = vec![
840            LayoutNode::Leaf {
841                name: "a".into(),
842                renderable: None,
843                size: None,
844            },
845            LayoutNode::Leaf {
846                name: "b".into(),
847                renderable: None,
848                size: None,
849            },
850        ];
851        let region = Region {
852            x: 0,
853            y: 0,
854            width: 80,
855            height: 24,
856        };
857        let regions = splitter.split(&region, &children, &Direction::Horizontal);
858        assert_eq!(regions.len(), 2);
859        assert_eq!(regions[0].height, 12);
860        assert_eq!(regions[1].y, 12);
861    }
862
863    #[test]
864    fn test_splitters_method() {
865        let layout = Layout::new(LayoutNode::Leaf {
866            name: "root".into(),
867            renderable: None,
868            size: None,
869        });
870        assert!(layout.splitters().is_empty());
871    }
872
873    #[test]
874    fn test_compute_with_fixed_size() {
875        let node = LayoutNode::Leaf {
876            name: "fixed".into(),
877            renderable: None,
878            size: Some(10),
879        };
880        let layout = Layout::new(node);
881        let regions = layout.compute(80, 24);
882        assert_eq!(regions[0].1.width, 10);
883        assert_eq!(regions[0].1.height, 10);
884    }
885}