tuiwindow/windows/
page.rs

1use 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}