rat_widget/pager/
dual_pager.rs

1use crate::_private::NonExhaustive;
2use crate::event::PagerOutcome;
3use crate::layout::GenericLayout;
4use crate::pager::{PageNavigation, PageNavigationState, Pager, PagerBuffer, PagerStyle};
5use rat_event::{HandleEvent, MouseOnly, Regular};
6use rat_reloc::RelocatableState;
7use ratatui::buffer::Buffer;
8use ratatui::layout::{Alignment, Rect, Size};
9use ratatui::prelude::{StatefulWidget, Style};
10use ratatui::widgets::{Block, Widget};
11use std::borrow::Cow;
12use std::cell::{Ref, RefCell, RefMut};
13use std::hash::Hash;
14use std::rc::Rc;
15
16/// This widget renders twp pages of a [GenericLayout].
17#[derive(Debug, Clone)]
18pub struct DualPager<'a, W>
19where
20    W: Eq + Hash + Clone,
21{
22    layout: Option<GenericLayout<W>>,
23    pager: Pager<W>,
24    page_nav: PageNavigation<'a>,
25}
26
27/// Renders directly to the frame buffer.
28///
29/// * It maps your widget area from layout coordinates
30///   to screen coordinates before rendering.
31/// * It helps with cleanup of the widget state if your
32///   widget is currently invisible.
33#[derive(Debug)]
34pub struct DualPagerBuffer<'a, W>
35where
36    W: Eq + Hash + Clone,
37{
38    pager0: PagerBuffer<'a, W>,
39    pager1: PagerBuffer<'a, W>,
40}
41
42/// Widget state.
43#[derive(Debug, Clone)]
44pub struct DualPagerState<W>
45where
46    W: Eq + Hash + Clone,
47{
48    /// Page layout
49    /// __read+write__ renewed with each render.
50    pub layout: Rc<RefCell<GenericLayout<W>>>,
51
52    /// PageNavigationState holds most of our state.
53    /// __read+write__
54    pub nav: PageNavigationState,
55
56    /// Only construct with `..Default::default()`.
57    pub non_exhaustive: NonExhaustive,
58}
59
60impl<W> Default for DualPager<'_, W>
61where
62    W: Eq + Hash + Clone,
63{
64    fn default() -> Self {
65        Self {
66            layout: Default::default(),
67            pager: Default::default(),
68            page_nav: PageNavigation::new().pages(2),
69        }
70    }
71}
72
73impl<'a, W> DualPager<'a, W>
74where
75    W: Eq + Hash + Clone,
76{
77    /// New DualPager
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Set the layout. If no layout is set here the layout is
83    /// taken from the state.
84    pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
85        self.layout = Some(layout);
86        self
87    }
88
89    /// Base style.
90    pub fn style(mut self, style: Style) -> Self {
91        self.pager = self.pager.style(style);
92        self.page_nav = self.page_nav.style(style);
93        self
94    }
95
96    /// Style for text labels.
97    pub fn label_style(mut self, style: Style) -> Self {
98        self.pager = self.pager.label_style(style);
99        self
100    }
101
102    /// Alignment for text labels.
103    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
104        self.pager = self.pager.label_alignment(alignment);
105        self
106    }
107
108    /// Style for navigation.
109    pub fn nav_style(mut self, nav_style: Style) -> Self {
110        self.page_nav = self.page_nav.nav_style(nav_style);
111        self
112    }
113
114    /// Style for the title.
115    pub fn title_style(mut self, title_style: Style) -> Self {
116        self.page_nav = self.page_nav.title_style(title_style);
117        self
118    }
119
120    /// Block for border
121    pub fn block(mut self, block: Block<'a>) -> Self {
122        self.page_nav = self.page_nav.block(block);
123        self
124    }
125
126    /// Set all styles.
127    pub fn styles(mut self, styles: PagerStyle) -> Self {
128        self.pager = self.pager.styles(styles.clone());
129        self.page_nav = self.page_nav.styles(styles);
130        self
131    }
132
133    /// Calculate the layout page size.
134    pub fn layout_size(&self, area: Rect) -> Size {
135        self.page_nav.layout_size(area)
136    }
137
138    /// Run the layout and create the second stage.
139    pub fn into_buffer(
140        self,
141        area: Rect,
142        buf: &'a mut Buffer,
143        state: &mut DualPagerState<W>,
144    ) -> DualPagerBuffer<'a, W> {
145        // set layout
146        if let Some(layout) = self.layout {
147            state.layout = Rc::new(RefCell::new(layout));
148        }
149
150        state.nav.page_count = (state.layout.borrow().page_count() + 1) / 2;
151        state.nav.set_page(state.nav.page);
152
153        self.page_nav.render(area, buf, &mut state.nav);
154
155        let buf = Rc::new(RefCell::new(buf));
156
157        DualPagerBuffer {
158            pager0: self
159                .pager
160                .clone()
161                .layout(state.layout.clone())
162                .page(state.nav.page * 2)
163                .into_buffer(state.nav.widget_areas[0], buf.clone()),
164            pager1: self
165                .pager
166                .clone()
167                .layout(state.layout.clone())
168                .page(state.nav.page * 2 + 1)
169                .into_buffer(state.nav.widget_areas[1], buf),
170        }
171    }
172}
173
174impl<'a, W> DualPagerBuffer<'a, W>
175where
176    W: Eq + Hash + Clone,
177{
178    /// Is the given area visible?
179    pub fn is_visible(&self, widget: W) -> bool {
180        if let Some(idx) = self.pager0.widget_idx(widget) {
181            self.pager0.is_visible(idx) || self.pager1.is_visible(idx)
182        } else {
183            false
184        }
185    }
186
187    /// Render all blocks for the current page.
188    pub fn render_block(&mut self) {
189        self.pager0.render_block();
190        self.pager1.render_block();
191    }
192
193    /// Render a manual label.
194    #[inline(always)]
195    pub fn render_label<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
196    where
197        FN: FnOnce(&Option<Cow<'static, str>>) -> WW,
198        WW: Widget,
199    {
200        let Some(idx) = self.pager0.widget_idx(widget) else {
201            return false;
202        };
203        if self.pager0.is_label_visible(idx) {
204            self.pager0.render_label(idx, render_fn)
205        } else {
206            self.pager1.render_label(idx, render_fn)
207        }
208    }
209
210    /// Render a stateless widget and its label, if any.
211    #[inline(always)]
212    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
213    where
214        FN: FnOnce() -> WW,
215        WW: Widget,
216    {
217        let Some(idx) = self.pager0.widget_idx(widget) else {
218            return false;
219        };
220        _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
221        if self.pager0.is_visible(idx) {
222            self.pager0.render_widget(idx, render_fn)
223        } else {
224            self.pager1.render_widget(idx, render_fn)
225        }
226    }
227
228    /// Render an optional stateful widget and its label, if any.
229    #[inline(always)]
230    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
231    where
232        FN: FnOnce() -> Option<WW>,
233        WW: StatefulWidget<State = SS>,
234        SS: RelocatableState,
235    {
236        let Some(idx) = self.pager0.widget_idx(widget) else {
237            return false;
238        };
239        _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
240
241        if self.pager0.is_visible(idx) {
242            if self.pager0.render_opt(idx, render_fn, state) {
243                true
244            } else {
245                self.hidden(state);
246                false
247            }
248        } else {
249            if self.pager1.render_opt(idx, render_fn, state) {
250                true
251            } else {
252                self.hidden(state);
253                false
254            }
255        }
256    }
257
258    /// Render a stateful widget and its label, if any.
259    #[inline(always)]
260    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
261    where
262        FN: FnOnce() -> WW,
263        WW: StatefulWidget<State = SS>,
264        SS: RelocatableState,
265    {
266        let Some(idx) = self.pager0.widget_idx(widget) else {
267            return false;
268        };
269        _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
270        if self.pager0.is_visible(idx) {
271            if self.pager0.render(idx, render_fn, state) {
272                true
273            } else {
274                self.hidden(state);
275                false
276            }
277        } else {
278            if self.pager1.render(idx, render_fn, state) {
279                true
280            } else {
281                self.hidden(state);
282                false
283            }
284        }
285    }
286
287    /// Render a stateful widget and its label, if any.
288    /// The closure can return a second value, which will be forwarded
289    /// if the widget is visible.
290    #[inline(always)]
291    #[allow(clippy::question_mark)]
292    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
293    where
294        FN: FnOnce() -> (WW, R),
295        WW: StatefulWidget<State = SS>,
296        SS: RelocatableState,
297    {
298        let Some(idx) = self.pager0.widget_idx(widget) else {
299            return None;
300        };
301        _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
302
303        if self.pager0.is_visible(idx) {
304            if let Some(remainder) = self.pager0.render2(idx, render_fn, state) {
305                Some(remainder)
306            } else {
307                self.hidden(state);
308                None
309            }
310        } else {
311            if let Some(remainder) = self.pager1.render2(idx, render_fn, state) {
312                Some(remainder)
313            } else {
314                self.hidden(state);
315                None
316            }
317        }
318    }
319
320    /// Calculate the necessary shift from view to screen.
321    /// This does nothing as pager always places the widgets
322    /// in screen coordinates.
323    ///
324    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
325    pub fn shift(&self) -> (i16, i16) {
326        (0, 0)
327    }
328
329    /// Relocate the widget area to screen coordinates.
330    /// Returns None if the widget is not visible.
331    /// This clips the area to page_area.
332    #[allow(clippy::question_mark)]
333    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
334        let Some(idx) = self.pager0.widget_idx(widget) else {
335            return None;
336        };
337        self.pager0
338            .locate_widget(idx)
339            .or_else(|| self.pager1.locate_widget(idx))
340    }
341
342    /// Relocate the label area to screen coordinates.
343    /// Returns None if the widget is not visible.
344    /// This clips the area to page_area.
345    #[allow(clippy::question_mark)]
346    pub fn locate_label(&self, widget: W) -> Option<Rect> {
347        let Some(idx) = self.pager0.widget_idx(widget) else {
348            return None;
349        };
350        self.pager0
351            .locate_label(idx)
352            .or_else(|| self.pager1.locate_label(idx))
353    }
354
355    /// Relocate an area from layout coordinates to screen coordinates.
356    /// A result None indicates that the area is invisible.
357    ///
358    /// This will clip the area to the page_area.
359    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
360        self.pager0
361            .locate_area(area)
362            .or_else(|| self.pager1.locate_area(area))
363    }
364
365    /// Does nothing for pager.
366    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
367    pub fn relocate<S>(&self, _state: &mut S)
368    where
369        S: RelocatableState,
370    {
371    }
372
373    /// Clear the areas in the widget-state.
374    /// This is called by render_xx whenever a widget is invisible.
375    pub fn hidden<S>(&self, state: &mut S)
376    where
377        S: RelocatableState,
378    {
379        state.relocate((0, 0), Rect::default())
380    }
381
382    /// Get access to the buffer during rendering a page.
383    pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
384        self.pager0.buffer()
385    }
386}
387
388impl<W> Default for DualPagerState<W>
389where
390    W: Eq + Hash + Clone,
391{
392    fn default() -> Self {
393        Self {
394            layout: Default::default(),
395            nav: Default::default(),
396            non_exhaustive: NonExhaustive,
397        }
398    }
399}
400
401impl<W> DualPagerState<W>
402where
403    W: Eq + Hash + Clone,
404{
405    /// State
406    pub fn new() -> Self {
407        Self::default()
408    }
409
410    /// Clear the layout data and reset the page/page-count.
411    pub fn clear(&mut self) {
412        self.layout.borrow_mut().clear();
413        self.nav.clear();
414    }
415
416    /// Layout needs to change?
417    pub fn valid_layout(&self, size: Size) -> bool {
418        let layout = self.layout.borrow();
419        !layout.size_changed(size) && !layout.is_empty()
420    }
421
422    /// Set the layout.
423    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
424        self.layout = Rc::new(RefCell::new(layout));
425    }
426
427    /// Layout.
428    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
429        self.layout.borrow()
430    }
431
432    /// Show the page for this rect.
433    pub fn show(&mut self, widget: W) {
434        if let Some(page) = self.layout.borrow().page_of(widget) {
435            self.nav.set_page(page / 2);
436        }
437    }
438
439    /// Returns the first widget for the given page.
440    pub fn first(&self, page: usize) -> Option<W> {
441        self.layout.borrow().first(page * 2)
442    }
443
444    /// Calculates the page of the widget.
445    pub fn page_of(&self, widget: W) -> Option<usize> {
446        self.layout.borrow().page_of(widget).map(|v| v / 2)
447    }
448
449    /// Set the visible page.
450    pub fn set_page(&mut self, page: usize) -> bool {
451        self.nav.set_page(page)
452    }
453
454    /// Visible page
455    pub fn page(&self) -> usize {
456        self.nav.page()
457    }
458
459    /// Select next page. Keeps the page in bounds.
460    pub fn next_page(&mut self) -> bool {
461        self.nav.next_page()
462    }
463
464    /// Select prev page.
465    pub fn prev_page(&mut self) -> bool {
466        self.nav.prev_page()
467    }
468}
469
470impl<W> HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for DualPagerState<W>
471where
472    W: Eq + Hash + Clone,
473{
474    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
475        self.nav.handle(event, Regular)
476    }
477}
478
479impl<W> HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for DualPagerState<W>
480where
481    W: Eq + Hash + Clone,
482{
483    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
484        self.nav.handle(event, MouseOnly)
485    }
486}