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#[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#[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#[derive(Debug, Clone)]
49pub struct DualPagerState<W>
50where
51 W: Eq + Hash + Clone,
52{
53 pub layout: Rc<RefCell<GenericLayout<W>>>,
56
57 pub nav: PageNavigationState,
60
61 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 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
91 self.layout = Some(layout);
92 self
93 }
94
95 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 pub fn auto_label(mut self, auto: bool) -> Self {
106 self.auto_label = auto;
107 self
108 }
109
110 pub fn label_style(mut self, style: Style) -> Self {
112 self.pager = self.pager.label_style(style);
113 self
114 }
115
116 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
118 self.pager = self.pager.label_alignment(alignment);
119 self
120 }
121
122 pub fn caption_style(mut self, style: CaptionStyle) -> Self {
124 self.pager = self.pager.caption_style(style);
125 self
126 }
127
128 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 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 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 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 pub fn layout_size(&self, area: Rect) -> Size {
175 self.page_nav.layout_size(area)
176 }
177
178 pub fn inner(&self, area: Rect) -> Rect {
180 self.page_nav.inner(area)
181 }
182
183 pub fn into_buffer(
186 self,
187 area: Rect,
188 buf: &'a mut Buffer,
189 state: &mut DualPagerState<W>,
190 ) -> DualPagerBuffer<'a, W> {
191 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 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 pub fn render_block(&mut self) {
236 self.pager0.render_block();
237 self.pager1.render_block();
238 }
239
240 #[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 #[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 #[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 #[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 #[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 #[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 pub fn shift(&self) -> (i16, i16) {
397 (0, 0)
398 }
399
400 #[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 #[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 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 pub fn relocate<S>(&self, _state: &mut S)
439 where
440 S: RelocatableState,
441 {
442 }
443
444 pub fn hidden<S>(&self, state: &mut S)
447 where
448 S: RelocatableState,
449 {
450 state.relocate((0, 0), Rect::default())
451 }
452
453 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 pub fn clear(&mut self) {
482 self.layout.borrow_mut().clear();
483 self.nav.clear();
484 }
485
486 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 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
494 self.layout = Rc::new(RefCell::new(layout));
495 }
496
497 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
499 self.layout.borrow()
500 }
501
502 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 pub fn first(&self, page: usize) -> Option<W> {
515 self.layout.borrow().first(page)
516 }
517
518 pub fn page_of(&self, widget: W) -> Option<usize> {
520 self.layout.borrow().page_of(widget)
521 }
522
523 pub fn set_page(&mut self, page: usize) -> bool {
525 self.nav.set_page(page)
526 }
527
528 pub fn page(&self) -> usize {
530 self.nav.page()
531 }
532
533 pub fn next_page(&mut self) -> bool {
535 self.nav.next_page()
536 }
537
538 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}