rat_widget/pager/
dual_pager.rs

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