1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
use ::ratatui::backend::Backend;
use ::ratatui::layout::{Constraint, Direction, Rect};
use ::ratatui::terminal::Frame;
use super::HeaderDetails;
use super::HelpText;
use super::Table;
const FIRST_HEIGHT_BREAKPOINT: u16 = 30;
const FIRST_WIDTH_BREAKPOINT: u16 = 120;
fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) {
let parts = ::ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
[
Constraint::Length(1),
Constraint::Length(rect.height - 2),
Constraint::Length(1),
]
.as_ref(),
)
.split(rect);
(parts[0], parts[1], parts[2])
}
pub struct Layout<'a> {
pub header: HeaderDetails<'a>,
pub children: Vec<Table<'a>>,
pub footer: HelpText,
}
impl<'a> Layout<'a> {
fn progressive_split(&self, rect: Rect, splits: Vec<Direction>) -> Vec<Rect> {
splits
.into_iter()
.fold(vec![rect], |mut layout, direction| {
let last_rect = layout.pop().unwrap();
let halves = ::ratatui::layout::Layout::default()
.direction(direction)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(last_rect);
layout.append(&mut halves.to_vec());
layout
})
}
fn build_two_children_layout(&self, rect: Rect) -> Vec<Rect> {
// if there are two elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
// if the space is not enough, we drop one element
vec![rect]
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
} else {
// by default we display two elements splitting vertically
self.progressive_split(rect, vec![Direction::Horizontal])
}
}
fn build_three_children_layout(&self, rect: Rect) -> Vec<Rect> {
// if there are three elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
//if the space is not enough, we drop two elements
vec![rect]
} else if rect.height < FIRST_HEIGHT_BREAKPOINT {
// if the vertical space is not enough, we drop one element and we split vertically
self.progressive_split(rect, vec![Direction::Horizontal])
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
} else {
// default layout
let halves = ::ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(rect);
let top_quarters = ::ratatui::layout::Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(halves[0]);
vec![top_quarters[0], top_quarters[1], halves[1]]
}
}
fn build_layout(&self, rect: Rect) -> Vec<Rect> {
if self.children.len() == 1 {
// if there's only one element to render, it can take the whole frame
vec![rect]
} else if self.children.len() == 2 {
self.build_two_children_layout(rect)
} else {
self.build_three_children_layout(rect)
}
}
pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect, ui_offset: usize) {
let (top, app, bottom) = top_app_and_bottom_split(rect);
let layout_slots = self.build_layout(app);
for i in 0..layout_slots.len() {
if let Some(rect) = layout_slots.get(i) {
if let Some(child) = self.children.get((i + ui_offset) % self.children.len()) {
child.render(frame, *rect);
}
}
}
self.header.render(frame, top);
self.footer.render(frame, bottom);
}
}
|