tuiwindow/windows/
page.rs1use ratatui::{
2 layout::{Position, Rect},
3 style::Style,
4};
5
6use crate::{
7 area_calculation::{unroll, UnrolledComponents},
8 core::RenderId,
9 core::{RenderComponent, RenderFlow, RenderNode},
10};
11
12use super::menu::{Menu, MenuEvent};
13
14pub struct Page {
15 id: RenderId,
16 pub(crate) title: String,
17 pub(crate) shortcut: char,
18 root: RenderComponent,
19 unrolled: UnrolledComponents,
20 menu: Menu,
21 pub(crate) style: Style,
22}
23
24impl Page {
25 pub fn get_page_id(&self) -> &RenderId {
26 &self.id
27 }
28
29 pub fn new<T: Into<RenderComponent>, S: Into<String>>(
30 title: S,
31 shortcut: char,
32 root: T,
33 ) -> Self {
34 let root: RenderComponent = root.into();
35 let layout_factories = unroll(&root);
36 Self {
37 id: RenderId::new(),
38 shortcut,
39 title: title.into(),
40 root,
41 menu: Menu::default(),
42 unrolled: layout_factories,
43 style: Style::default(),
44 }
45 }
46
47 pub fn with_style(mut self, style: Style) -> Self {
48 self.style = style;
49 self
50 }
51
52 pub fn with_menu(&mut self, menu: Menu) -> &mut Self {
53 self.menu = menu;
54 self
55 }
56
57 pub fn with_menu_entries<T: Into<String>, F: Fn(MenuEvent) + 'static>(
58 &mut self,
59 entries: Vec<(char, T, F)>,
60 ) -> &mut Self {
61 self.menu = Menu::from_entries(entries);
62 self
63 }
64
65 pub fn visit(&self, f: &mut dyn FnMut(&RenderNode) -> bool) {
66 self.root.visit(f);
67 }
68
69 pub fn components_at_position(&self, pos: &Position, area: &Rect) -> Vec<&RenderId> {
70 self.unrolled
71 .0
72 .iter()
73 .filter_map(|(id, area_calc)| {
74 let sub_area = area_calc(*area);
75 if sub_area.contains(*pos) {
76 Some(id)
77 } else {
78 None
79 }
80 })
81 .collect()
82 }
83
84 pub(crate) fn get_active_element_menu(
85 &self,
86 focused_element: &Option<RenderId>,
87 ) -> Option<Menu> {
88 if let Some(fe) = focused_element {
89 let mut found = None;
90 self.visit(&mut |details| {
91 if details.id == *fe {
92 found = details.render.get_menu();
93 false
94 } else {
95 true
96 }
97 });
98 found
99 } else {
100 None
101 }
102 }
103}
104
105impl RenderFlow for Page {
106 fn render(
107 &mut self,
108 opts: &mut crate::core::VRenderProps,
109 component_buffer: &mut crate::core::ComponentBuffer,
110 buff: &mut ratatui::prelude::Buffer,
111 area: ratatui::prelude::Rect,
112 ) {
113 self.root.render(opts, component_buffer, buff, area)
114 }
115
116 fn get_menu(&self) -> Option<&Menu> {
117 Some(&self.menu)
118 }
119
120 fn get_focusable_elements(&self) -> Vec<RenderId> {
121 self.root.get_focusable_elements()
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::render::Render;
128
129 use super::Page;
130
131 struct MyWidget {}
132
133 impl Render for MyWidget {
134 fn render(
135 &mut self,
136 _render_props: &crate::render::RenderProps,
137 _buff: &mut ratatui::prelude::Buffer,
138 _area: ratatui::prelude::Rect,
139 ) {
140 todo!()
141 }
142 }
143 #[test]
144 fn test_creating_page() {
145 let _page = Page::new("P1", 'p', MyWidget {});
146 let _page2 = Page::new("P2", 'd', MyWidget {}).with_menu_entries(vec![(
147 'i',
148 "Insert",
149 |_: super::MenuEvent<'_>| {},
150 )]);
151 }
152}