rat_widget/pager/
pager.rs

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