rat_widget/pager/
pager.rs

1use crate::layout::GenericLayout;
2use crate::pager::PagerStyle;
3use ratatui::buffer::Buffer;
4use ratatui::layout::{Alignment, Rect};
5use ratatui::style::Style;
6use ratatui::text::Line;
7use ratatui::widgets::{StatefulWidget, Widget};
8use std::borrow::Cow;
9use std::cell::{RefCell, RefMut};
10use std::hash::Hash;
11use std::rc::Rc;
12
13/// Renders widgets for one page of a [GenericLayout].
14#[derive(Debug)]
15pub struct Pager<W>
16where
17    W: Eq + Hash + Clone,
18{
19    layout: Rc<RefCell<GenericLayout<W>>>,
20    page: usize,
21    style: Style,
22    label_style: Option<Style>,
23    label_alignment: Option<Alignment>,
24}
25
26/// Rendering phase.
27#[derive(Debug)]
28pub struct PagerBuffer<'a, W>
29where
30    W: Eq + Hash + Clone,
31{
32    layout: Rc<RefCell<GenericLayout<W>>>,
33    page_area: Rect,
34    widget_area: Rect,
35    buffer: Rc<RefCell<&'a mut Buffer>>,
36    label_style: Option<Style>,
37    label_alignment: Option<Alignment>,
38}
39
40impl<W> Clone for Pager<W>
41where
42    W: Eq + Hash + Clone,
43{
44    fn clone(&self) -> Self {
45        Self {
46            layout: self.layout.clone(),
47            page: self.page,
48            style: self.style,
49            label_style: self.label_style,
50            label_alignment: self.label_alignment,
51        }
52    }
53}
54
55impl<W> Default for Pager<W>
56where
57    W: Eq + Hash + Clone,
58{
59    fn default() -> Self {
60        Self {
61            layout: Default::default(),
62            page: Default::default(),
63            style: Default::default(),
64            label_style: Default::default(),
65            label_alignment: Default::default(),
66        }
67    }
68}
69
70impl<W> Pager<W>
71where
72    W: Eq + Hash + Clone,
73{
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Layout
79    pub fn layout(mut self, layout: Rc<RefCell<GenericLayout<W>>>) -> Self {
80        self.layout = layout;
81        self
82    }
83
84    /// Display page.
85    pub fn page(mut self, page: usize) -> Self {
86        self.page = page;
87        self
88    }
89
90    /// Base style.
91    pub fn style(mut self, style: Style) -> Self {
92        self.style = style;
93        self
94    }
95
96    /// Style for auto-labels.
97    pub fn label_style(mut self, style: Style) -> Self {
98        self.label_style = Some(style);
99        self
100    }
101
102    /// Alignment for auto-labels.
103    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
104        self.label_alignment = Some(alignment);
105        self
106    }
107
108    /// Set all styles.
109    pub fn styles(mut self, styles: PagerStyle) -> Self {
110        self.style = styles.style;
111        if let Some(label) = styles.label_style {
112            self.label_style = Some(label);
113        }
114        if let Some(alignment) = styles.label_alignment {
115            self.label_alignment = Some(alignment);
116        }
117        self
118    }
119
120    /// Create the second stage.
121    #[allow(clippy::needless_lifetimes)]
122    pub fn into_buffer<'b>(
123        self,
124        area: Rect,
125        buf: Rc<RefCell<&'b mut Buffer>>,
126    ) -> PagerBuffer<'b, W> {
127        let page_size = self.layout.borrow().page_size();
128        let page_area = Rect::new(
129            0,
130            self.page as u16 * page_size.height,
131            page_size.width,
132            page_size.height,
133        );
134
135        PagerBuffer {
136            layout: self.layout,
137            page_area,
138            widget_area: area,
139            buffer: buf,
140            label_style: self.label_style,
141            label_alignment: self.label_alignment,
142        }
143    }
144}
145
146impl<'a, W> PagerBuffer<'a, W>
147where
148    W: Eq + Hash + Clone,
149{
150    /// Is the widget visible.
151    #[inline]
152    pub fn is_visible(&self, idx: usize) -> bool {
153        let area = self.layout.borrow().widget(idx);
154        self.page_area.intersects(area)
155    }
156
157    /// Is the label visible.
158    #[inline]
159    pub fn is_label_visible(&self, idx: usize) -> bool {
160        let area = self.layout.borrow().widget(idx);
161        self.page_area.intersects(area)
162    }
163
164    /// Get the widget index.
165    #[inline(always)]
166    pub fn widget_idx(&self, widget: W) -> Option<usize> {
167        self.layout.borrow().try_index_of(widget)
168    }
169
170    /// Render a label.
171    #[inline(always)]
172    pub fn render_label<FN>(&mut self, idx: usize, render_fn: FN) -> bool
173    where
174        FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
175    {
176        let Some(label_area) = self.locate_label(idx) else {
177            return false;
178        };
179        let layout = self.layout.borrow();
180        let mut buffer = self.buffer.borrow_mut();
181        if let Some(label_str) = layout.try_label_str(idx) {
182            render_fn(label_str, label_area, *buffer);
183        } else {
184            render_fn(&Cow::default(), label_area, *buffer);
185        }
186        true
187    }
188
189    /// Render the label with the set style and alignment.
190    #[inline(always)]
191    pub fn render_auto_label(&mut self, idx: usize) -> bool {
192        let Some(label_area) = self.locate_label(idx) else {
193            return false;
194        };
195        let layout = self.layout.borrow();
196        let Some(label_str) = layout.try_label_str(idx) else {
197            return false;
198        };
199
200        let mut buffer = self.buffer.borrow_mut();
201        let mut label = Line::from(label_str.as_ref());
202        if let Some(style) = self.label_style {
203            label = label.style(style)
204        };
205        if let Some(align) = self.label_alignment {
206            label = label.alignment(align);
207        }
208        label.render(label_area, *buffer);
209
210        true
211    }
212
213    /// Render a stateless widget.
214    #[inline(always)]
215    pub fn render_widget<FN, WW>(&mut self, idx: usize, render_fn: FN) -> bool
216    where
217        FN: FnOnce() -> WW,
218        WW: Widget,
219    {
220        let Some(widget_area) = self.locate_widget(idx) else {
221            return false;
222        };
223        let mut buffer = self.buffer.borrow_mut();
224        render_fn().render(widget_area, *buffer);
225        true
226    }
227
228    /// Render a stateful widget.
229    #[inline(always)]
230    pub fn render<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
231    where
232        FN: FnOnce() -> WW,
233        WW: StatefulWidget<State = SS>,
234    {
235        let Some(widget_area) = self.locate_widget(idx) else {
236            return false;
237        };
238        let mut buffer = self.buffer.borrow_mut();
239        render_fn().render(widget_area, *buffer, state);
240
241        true
242    }
243
244    /// Render a stateful widget.
245    #[inline(always)]
246    pub fn render_opt<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
247    where
248        FN: FnOnce() -> Option<WW>,
249        WW: StatefulWidget<State = SS>,
250    {
251        let Some(widget_area) = self.locate_widget(idx) else {
252            return false;
253        };
254        let mut buffer = self.buffer.borrow_mut();
255        let widget = render_fn();
256        if let Some(widget) = widget {
257            widget.render(widget_area, *buffer, state);
258        }
259        true
260    }
261
262    /// Render a stateful widget that will split in two parts.
263    /// Render the first part and return the second part to be
264    /// rendered later/elsewhere.
265    #[inline(always)]
266    #[allow(clippy::question_mark)]
267    pub fn render2<FN, WW, SS, R>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> Option<R>
268    where
269        FN: FnOnce() -> (WW, R),
270        WW: StatefulWidget<State = SS>,
271    {
272        let Some(widget_area) = self.locate_widget(idx) else {
273            return None;
274        };
275        let mut buffer = self.buffer.borrow_mut();
276        let (widget, remainder) = render_fn();
277        widget.render(widget_area, *buffer, state);
278
279        Some(remainder)
280    }
281
282    /// Render all blocks for the current page.
283    pub fn render_block(&mut self) {
284        let mut buffer = self.buffer.borrow_mut();
285        for (idx, block_area) in self.layout.borrow().block_area_iter().enumerate() {
286            if let Some(block_area) = self.locate_area(*block_area) {
287                if let Some(block) = self.layout.borrow().block(idx) {
288                    block.render(block_area, *buffer);
289                }
290            }
291        }
292    }
293
294    /// Relocate the widget area to screen coordinates.
295    /// Returns None if the widget is not visible.
296    /// This clips the area to page_area.
297    #[inline]
298    pub fn locate_widget(&self, idx: usize) -> Option<Rect> {
299        self.locate_area(self.layout.borrow().widget(idx))
300    }
301
302    /// Relocate the label area to screen coordinates.
303    /// Returns None if the widget is not visible.
304    /// This clips the area to page_area.
305    #[inline]
306    pub fn locate_label(&self, idx: usize) -> Option<Rect> {
307        self.locate_area(self.layout.borrow().label(idx))
308    }
309
310    /// Relocate an area from layout coordinates to screen coordinates.
311    /// A result None indicates that the area is invisible.
312    ///
313    /// This will clip the area to the page_area.
314    #[inline]
315    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
316        let area = self.page_area.intersection(area);
317        if area.is_empty() {
318            None
319        } else {
320            Some(Rect::new(
321                area.x - self.page_area.x + self.widget_area.x,
322                area.y - self.page_area.y + self.widget_area.y,
323                area.width,
324                area.height,
325            ))
326        }
327    }
328
329    /// Get access to the buffer during rendering a page.
330    #[inline]
331    pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
332        self.buffer.borrow_mut()
333    }
334}