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#[derive(Debug, Clone)]
18pub struct DualPager<'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#[derive(Debug)]
34pub struct DualPagerBuffer<'a, W>
35where
36 W: Eq + Hash + Clone,
37{
38 pager0: PagerBuffer<'a, W>,
39 pager1: PagerBuffer<'a, W>,
40}
41
42#[derive(Debug, Clone)]
44pub struct DualPagerState<W>
45where
46 W: Eq + Hash + Clone,
47{
48 pub layout: Rc<RefCell<GenericLayout<W>>>,
51
52 pub nav: PageNavigationState,
55
56 pub non_exhaustive: NonExhaustive,
58}
59
60impl<W> Default for DualPager<'_, W>
61where
62 W: Eq + Hash + Clone,
63{
64 fn default() -> Self {
65 Self {
66 layout: Default::default(),
67 pager: Default::default(),
68 page_nav: PageNavigation::new().pages(2),
69 }
70 }
71}
72
73impl<'a, W> DualPager<'a, W>
74where
75 W: Eq + Hash + Clone,
76{
77 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
85 self.layout = Some(layout);
86 self
87 }
88
89 pub fn style(mut self, style: Style) -> Self {
91 self.pager = self.pager.style(style);
92 self.page_nav = self.page_nav.style(style);
93 self
94 }
95
96 pub fn label_style(mut self, style: Style) -> Self {
98 self.pager = self.pager.label_style(style);
99 self
100 }
101
102 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
104 self.pager = self.pager.label_alignment(alignment);
105 self
106 }
107
108 pub fn nav_style(mut self, nav_style: Style) -> Self {
110 self.page_nav = self.page_nav.nav_style(nav_style);
111 self
112 }
113
114 pub fn title_style(mut self, title_style: Style) -> Self {
116 self.page_nav = self.page_nav.title_style(title_style);
117 self
118 }
119
120 pub fn block(mut self, block: Block<'a>) -> Self {
122 self.page_nav = self.page_nav.block(block);
123 self
124 }
125
126 pub fn styles(mut self, styles: PagerStyle) -> Self {
128 self.pager = self.pager.styles(styles.clone());
129 self.page_nav = self.page_nav.styles(styles);
130 self
131 }
132
133 pub fn layout_size(&self, area: Rect) -> Size {
135 self.page_nav.layout_size(area)
136 }
137
138 pub fn into_buffer(
140 self,
141 area: Rect,
142 buf: &'a mut Buffer,
143 state: &mut DualPagerState<W>,
144 ) -> DualPagerBuffer<'a, W> {
145 if let Some(layout) = self.layout {
147 state.layout = Rc::new(RefCell::new(layout));
148 }
149
150 state.nav.page_count = (state.layout.borrow().page_count() + 1) / 2;
151 state.nav.set_page(state.nav.page);
152
153 self.page_nav.render(area, buf, &mut state.nav);
154
155 let buf = Rc::new(RefCell::new(buf));
156
157 DualPagerBuffer {
158 pager0: self
159 .pager
160 .clone()
161 .layout(state.layout.clone())
162 .page(state.nav.page * 2)
163 .into_buffer(state.nav.widget_areas[0], buf.clone()),
164 pager1: self
165 .pager
166 .clone()
167 .layout(state.layout.clone())
168 .page(state.nav.page * 2 + 1)
169 .into_buffer(state.nav.widget_areas[1], buf),
170 }
171 }
172}
173
174impl<'a, W> DualPagerBuffer<'a, W>
175where
176 W: Eq + Hash + Clone,
177{
178 pub fn is_visible(&self, widget: W) -> bool {
180 if let Some(idx) = self.pager0.widget_idx(widget) {
181 self.pager0.is_visible(idx) || self.pager1.is_visible(idx)
182 } else {
183 false
184 }
185 }
186
187 pub fn render_block(&mut self) {
189 self.pager0.render_block();
190 self.pager1.render_block();
191 }
192
193 #[inline(always)]
195 pub fn render_label<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
196 where
197 FN: FnOnce(&Option<Cow<'static, str>>) -> WW,
198 WW: Widget,
199 {
200 let Some(idx) = self.pager0.widget_idx(widget) else {
201 return false;
202 };
203 if self.pager0.is_label_visible(idx) {
204 self.pager0.render_label(idx, render_fn)
205 } else {
206 self.pager1.render_label(idx, render_fn)
207 }
208 }
209
210 #[inline(always)]
212 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
213 where
214 FN: FnOnce() -> WW,
215 WW: Widget,
216 {
217 let Some(idx) = self.pager0.widget_idx(widget) else {
218 return false;
219 };
220 _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
221 if self.pager0.is_visible(idx) {
222 self.pager0.render_widget(idx, render_fn)
223 } else {
224 self.pager1.render_widget(idx, render_fn)
225 }
226 }
227
228 #[inline(always)]
230 pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
231 where
232 FN: FnOnce() -> Option<WW>,
233 WW: StatefulWidget<State = SS>,
234 SS: RelocatableState,
235 {
236 let Some(idx) = self.pager0.widget_idx(widget) else {
237 return false;
238 };
239 _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
240
241 if self.pager0.is_visible(idx) {
242 if self.pager0.render_opt(idx, render_fn, state) {
243 true
244 } else {
245 self.hidden(state);
246 false
247 }
248 } else {
249 if self.pager1.render_opt(idx, render_fn, state) {
250 true
251 } else {
252 self.hidden(state);
253 false
254 }
255 }
256 }
257
258 #[inline(always)]
260 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
261 where
262 FN: FnOnce() -> WW,
263 WW: StatefulWidget<State = SS>,
264 SS: RelocatableState,
265 {
266 let Some(idx) = self.pager0.widget_idx(widget) else {
267 return false;
268 };
269 _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
270 if self.pager0.is_visible(idx) {
271 if self.pager0.render(idx, render_fn, state) {
272 true
273 } else {
274 self.hidden(state);
275 false
276 }
277 } else {
278 if self.pager1.render(idx, render_fn, state) {
279 true
280 } else {
281 self.hidden(state);
282 false
283 }
284 }
285 }
286
287 #[inline(always)]
291 #[allow(clippy::question_mark)]
292 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
293 where
294 FN: FnOnce() -> (WW, R),
295 WW: StatefulWidget<State = SS>,
296 SS: RelocatableState,
297 {
298 let Some(idx) = self.pager0.widget_idx(widget) else {
299 return None;
300 };
301 _ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
302
303 if self.pager0.is_visible(idx) {
304 if let Some(remainder) = self.pager0.render2(idx, render_fn, state) {
305 Some(remainder)
306 } else {
307 self.hidden(state);
308 None
309 }
310 } else {
311 if let Some(remainder) = self.pager1.render2(idx, render_fn, state) {
312 Some(remainder)
313 } else {
314 self.hidden(state);
315 None
316 }
317 }
318 }
319
320 pub fn shift(&self) -> (i16, i16) {
326 (0, 0)
327 }
328
329 #[allow(clippy::question_mark)]
333 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
334 let Some(idx) = self.pager0.widget_idx(widget) else {
335 return None;
336 };
337 self.pager0
338 .locate_widget(idx)
339 .or_else(|| self.pager1.locate_widget(idx))
340 }
341
342 #[allow(clippy::question_mark)]
346 pub fn locate_label(&self, widget: W) -> Option<Rect> {
347 let Some(idx) = self.pager0.widget_idx(widget) else {
348 return None;
349 };
350 self.pager0
351 .locate_label(idx)
352 .or_else(|| self.pager1.locate_label(idx))
353 }
354
355 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
360 self.pager0
361 .locate_area(area)
362 .or_else(|| self.pager1.locate_area(area))
363 }
364
365 pub fn relocate<S>(&self, _state: &mut S)
368 where
369 S: RelocatableState,
370 {
371 }
372
373 pub fn hidden<S>(&self, state: &mut S)
376 where
377 S: RelocatableState,
378 {
379 state.relocate((0, 0), Rect::default())
380 }
381
382 pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
384 self.pager0.buffer()
385 }
386}
387
388impl<W> Default for DualPagerState<W>
389where
390 W: Eq + Hash + Clone,
391{
392 fn default() -> Self {
393 Self {
394 layout: Default::default(),
395 nav: Default::default(),
396 non_exhaustive: NonExhaustive,
397 }
398 }
399}
400
401impl<W> DualPagerState<W>
402where
403 W: Eq + Hash + Clone,
404{
405 pub fn new() -> Self {
407 Self::default()
408 }
409
410 pub fn clear(&mut self) {
412 self.layout.borrow_mut().clear();
413 self.nav.clear();
414 }
415
416 pub fn valid_layout(&self, size: Size) -> bool {
418 let layout = self.layout.borrow();
419 !layout.size_changed(size) && !layout.is_empty()
420 }
421
422 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
424 self.layout = Rc::new(RefCell::new(layout));
425 }
426
427 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
429 self.layout.borrow()
430 }
431
432 pub fn show(&mut self, widget: W) {
434 if let Some(page) = self.layout.borrow().page_of(widget) {
435 self.nav.set_page(page / 2);
436 }
437 }
438
439 pub fn first(&self, page: usize) -> Option<W> {
441 self.layout.borrow().first(page * 2)
442 }
443
444 pub fn page_of(&self, widget: W) -> Option<usize> {
446 self.layout.borrow().page_of(widget).map(|v| v / 2)
447 }
448
449 pub fn set_page(&mut self, page: usize) -> bool {
451 self.nav.set_page(page)
452 }
453
454 pub fn page(&self) -> usize {
456 self.nav.page()
457 }
458
459 pub fn next_page(&mut self) -> bool {
461 self.nav.next_page()
462 }
463
464 pub fn prev_page(&mut self) -> bool {
466 self.nav.prev_page()
467 }
468}
469
470impl<W> HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for DualPagerState<W>
471where
472 W: Eq + Hash + Clone,
473{
474 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
475 self.nav.handle(event, Regular)
476 }
477}
478
479impl<W> HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for DualPagerState<W>
480where
481 W: Eq + Hash + Clone,
482{
483 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
484 self.nav.handle(event, MouseOnly)
485 }
486}