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