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