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#[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#[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#[derive(Debug, Clone)]
47pub struct DualPagerState<W>
48where
49 W: Eq + Hash + Clone,
50{
51 pub layout: Rc<RefCell<GenericLayout<W>>>,
54
55 pub nav: PageNavigationState,
58
59 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 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
89 self.layout = Some(layout);
90 self
91 }
92
93 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 pub fn auto_label(mut self, auto: bool) -> Self {
104 self.auto_label = auto;
105 self
106 }
107
108 pub fn label_style(mut self, style: Style) -> Self {
110 self.pager = self.pager.label_style(style);
111 self
112 }
113
114 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
116 self.pager = self.pager.label_alignment(alignment);
117 self
118 }
119
120 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 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 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 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 pub fn layout_size(&self, area: Rect) -> Size {
167 self.page_nav.layout_size(area)
168 }
169
170 pub fn inner(&self, area: Rect) -> Rect {
172 self.page_nav.inner(area)
173 }
174
175 pub fn into_buffer(
178 self,
179 area: Rect,
180 buf: &'a mut Buffer,
181 state: &mut DualPagerState<W>,
182 ) -> DualPagerBuffer<'a, W> {
183 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 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 pub fn render_block(&mut self) {
227 self.pager0.render_block();
228 self.pager1.render_block();
229 }
230
231 #[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 #[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 #[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 #[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 #[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 pub fn shift(&self) -> (i16, i16) {
370 (0, 0)
371 }
372
373 #[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 #[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 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 pub fn relocate<S>(&self, _state: &mut S)
412 where
413 S: RelocatableState,
414 {
415 }
416
417 pub fn hidden<S>(&self, state: &mut S)
420 where
421 S: RelocatableState,
422 {
423 state.relocate((0, 0), Rect::default())
424 }
425
426 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 pub fn clear(&mut self) {
455 self.layout.borrow_mut().clear();
456 self.nav.clear();
457 }
458
459 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 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
467 self.layout = Rc::new(RefCell::new(layout));
468 }
469
470 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
472 self.layout.borrow()
473 }
474
475 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 pub fn first(&self, page: usize) -> Option<W> {
488 self.layout.borrow().first(page)
489 }
490
491 pub fn page_of(&self, widget: W) -> Option<usize> {
493 self.layout.borrow().page_of(widget)
494 }
495
496 pub fn set_page(&mut self, page: usize) -> bool {
498 self.nav.set_page(page)
499 }
500
501 pub fn page(&self) -> usize {
503 self.nav.page()
504 }
505
506 pub fn next_page(&mut self) -> bool {
508 self.nav.next_page()
509 }
510
511 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}