rat_widget/pager/
single_pager.rs

1use crate::_private::NonExhaustive;
2use crate::event::PagerOutcome;
3use crate::layout::GenericLayout;
4use crate::pager::{PageNavigation, PageNavigationState, Pager, PagerBuffer, PagerStyle};
5use log::debug;
6use rat_event::{HandleEvent, MouseOnly, Regular};
7use rat_reloc::RelocatableState;
8use ratatui::buffer::Buffer;
9use ratatui::layout::{Alignment, Rect, Size};
10use ratatui::style::Style;
11use ratatui::widgets::StatefulWidget;
12use ratatui::widgets::{Block, Widget};
13use std::borrow::Cow;
14use std::cell::{Ref, RefCell, RefMut};
15use std::hash::Hash;
16use std::rc::Rc;
17
18/// This widget renders a single page of a [GenericLayout].
19#[derive(Debug, Clone)]
20pub struct SinglePager<'a, W>
21where
22    W: Eq + Hash + Clone,
23{
24    layout: Option<GenericLayout<W>>,
25    pager: Pager<W>,
26    page_nav: PageNavigation<'a>,
27    auto_label: bool,
28}
29
30/// Renders directly to the frame buffer.
31///
32/// * It maps your widget area from layout coordinates
33///   to screen coordinates before rendering.
34/// * It helps with cleanup of the widget state if your
35///   widget is currently invisible.
36#[derive(Debug)]
37pub struct SinglePagerBuffer<'a, W>
38where
39    W: Eq + Hash + Clone,
40{
41    pager: PagerBuffer<'a, W>,
42    auto_label: bool,
43}
44
45/// Widget state.
46#[derive(Debug, Clone)]
47pub struct SinglePagerState<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 SinglePager<'_, 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: Default::default(),
72            auto_label: true,
73        }
74    }
75}
76
77impl<'a, W> SinglePager<'a, W>
78where
79    W: Eq + Hash + Clone,
80{
81    /// New SinglePage.
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: &'a mut SinglePagerState<W>,
182    ) -> SinglePagerBuffer<'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        SinglePagerBuffer {
194            pager: self
195                .pager //
196                .layout(state.layout.clone())
197                .page(state.nav.page)
198                .into_buffer(state.nav.widget_areas[0], Rc::new(RefCell::new(buf))),
199            auto_label: self.auto_label,
200        }
201    }
202}
203
204impl<'a, W> SinglePagerBuffer<'a, W>
205where
206    W: Eq + Hash + Clone,
207{
208    /// Is the given area visible?
209    pub fn is_visible(&self, widget: W) -> bool {
210        if let Some(idx) = self.pager.widget_idx(widget) {
211            self.pager.is_visible(idx)
212        } else {
213            false
214        }
215    }
216
217    /// Render all blocks for the current page.
218    pub fn render_block(&mut self) {
219        self.pager.render_block()
220    }
221
222    /// Render a manual label.
223    #[inline(always)]
224    pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
225    where
226        FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
227    {
228        let Some(idx) = self.pager.widget_idx(widget) else {
229            debug!("no widget");
230            return false;
231        };
232        self.pager.render_label(idx, render_fn)
233    }
234
235    /// Render a stateless widget and its label, if any.
236    #[inline(always)]
237    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
238    where
239        FN: FnOnce() -> WW,
240        WW: Widget,
241    {
242        let Some(idx) = self.pager.widget_idx(widget) else {
243            return false;
244        };
245        if self.auto_label {
246            self.pager.render_auto_label(idx);
247        }
248        self.pager.render_widget(idx, render_fn)
249    }
250
251    /// Render an optional stateful widget and its label, if any.
252    #[inline(always)]
253    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
254    where
255        FN: FnOnce() -> Option<WW>,
256        WW: StatefulWidget<State = SS>,
257        SS: RelocatableState,
258    {
259        let Some(idx) = self.pager.widget_idx(widget) else {
260            return false;
261        };
262        if self.auto_label {
263            self.pager.render_auto_label(idx);
264        }
265        if !self.pager.render_opt(idx, render_fn, state) {
266            self.hidden(state);
267            false
268        } else {
269            true
270        }
271    }
272
273    /// Render a stateful widget and its label, if any.
274    #[inline(always)]
275    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
276    where
277        FN: FnOnce() -> WW,
278        WW: StatefulWidget<State = SS>,
279        SS: RelocatableState,
280    {
281        let Some(idx) = self.pager.widget_idx(widget) else {
282            return false;
283        };
284        if self.auto_label {
285            self.pager.render_auto_label(idx);
286        }
287        if self.pager.render(idx, render_fn, state) {
288            true
289        } else {
290            self.hidden(state);
291            false
292        }
293    }
294
295    /// Render a stateful widget and its label, if any.
296    /// The closure can return a second value, which will be forwarded
297    /// if the widget is visible.
298    #[inline(always)]
299    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
300    where
301        FN: FnOnce() -> (WW, R),
302        WW: StatefulWidget<State = SS>,
303        SS: RelocatableState,
304    {
305        let Some(idx) = self.pager.widget_idx(widget) else {
306            return None;
307        };
308        if self.auto_label {
309            self.pager.render_auto_label(idx);
310        }
311        if let Some(remainder) = self.pager.render2(idx, render_fn, state) {
312            Some(remainder)
313        } else {
314            self.hidden(state);
315            None
316        }
317    }
318
319    /// Calculate the necessary shift from view to screen.
320    /// This does nothing as pager always places the widgets
321    /// in screen coordinates.
322    ///
323    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
324    pub fn shift(&self) -> (i16, i16) {
325        (0, 0)
326    }
327
328    /// Relocate the widget area to screen coordinates.
329    /// Returns None if the widget is not visible.
330    /// This clips the area to page_area.
331    #[allow(clippy::question_mark)]
332    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
333        let Some(idx) = self.pager.widget_idx(widget) else {
334            return None;
335        };
336        self.pager.locate_widget(idx)
337    }
338
339    /// Relocate the label area to screen coordinates.
340    /// Returns None if the widget is not visible.
341    /// This clips the area to page_area.
342    #[allow(clippy::question_mark)]
343    pub fn locate_label(&self, widget: W) -> Option<Rect> {
344        let Some(idx) = self.pager.widget_idx(widget) else {
345            return None;
346        };
347        self.pager.locate_label(idx)
348    }
349
350    /// Relocate an area from layout coordinates to screen coordinates.
351    /// A result None indicates that the area is invisible.
352    ///
353    /// This will clip the area to the page_area.
354    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
355        self.pager.locate_area(area)
356    }
357
358    /// Does nothing for pager.
359    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
360    pub fn relocate<S>(&self, _state: &mut S)
361    where
362        S: RelocatableState,
363    {
364    }
365
366    /// Clear the areas in the widget-state.
367    /// This is called by render_xx whenever a widget is invisible.
368    pub fn hidden<S>(&self, state: &mut S)
369    where
370        S: RelocatableState,
371    {
372        state.relocate((0, 0), Rect::default())
373    }
374
375    /// Get access to the buffer during rendering a page.
376    pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
377        self.pager.buffer()
378    }
379}
380
381impl<W> Default for SinglePagerState<W>
382where
383    W: Eq + Hash + Clone,
384{
385    fn default() -> Self {
386        Self {
387            layout: Default::default(),
388            nav: Default::default(),
389            non_exhaustive: NonExhaustive,
390        }
391    }
392}
393
394impl<W> SinglePagerState<W>
395where
396    W: Eq + Hash + Clone,
397{
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    /// Clear the layout data and reset the page/page-count.
403    pub fn clear(&mut self) {
404        self.layout.borrow_mut().clear();
405        self.nav.clear();
406    }
407
408    /// Layout needs to change?
409    pub fn valid_layout(&self, size: Size) -> bool {
410        let layout = self.layout.borrow();
411        !layout.size_changed(size) && !layout.is_empty()
412    }
413
414    /// Set the layout.
415    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
416        self.layout = Rc::new(RefCell::new(layout));
417    }
418
419    /// Layout.
420    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
421        self.layout.borrow()
422    }
423
424    /// Show the page for this widget.
425    /// If there is no widget for the given identifier, this
426    /// will set the page to 0.
427    pub fn show(&mut self, widget: W) {
428        if let Some(page) = self.layout.borrow().page_of(widget) {
429            self.nav.set_page(page);
430        } else {
431            self.nav.set_page(0);
432        }
433    }
434
435    /// Returns the first widget for the given page.
436    pub fn first(&self, page: usize) -> Option<W> {
437        self.layout.borrow().first(page)
438    }
439
440    /// Calculates the page of the widget.
441    pub fn page_of(&self, widget: W) -> Option<usize> {
442        self.layout.borrow().page_of(widget)
443    }
444
445    /// Set the visible page.
446    pub fn set_page(&mut self, page: usize) -> bool {
447        self.nav.set_page(page)
448    }
449
450    /// Visible page
451    pub fn page(&self) -> usize {
452        self.nav.page()
453    }
454
455    /// Select next page. Keeps the page in bounds.
456    pub fn next_page(&mut self) -> bool {
457        self.nav.next_page()
458    }
459
460    /// Select prev page.
461    pub fn prev_page(&mut self) -> bool {
462        self.nav.prev_page()
463    }
464}
465
466impl<W> HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for SinglePagerState<W>
467where
468    W: Eq + Hash + Clone,
469{
470    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
471        self.nav.handle(event, Regular)
472    }
473}
474
475impl<W> HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for SinglePagerState<W>
476where
477    W: Eq + Hash + Clone,
478{
479    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
480        self.nav.handle(event, MouseOnly)
481    }
482}