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