use crate::_private::NonExhaustive;
use crate::event::PagerOutcome;
use crate::layout::GenericLayout;
use crate::pager::{PageNavigation, PageNavigationState, Pager, PagerBuffer, PagerStyle};
use rat_event::{HandleEvent, MouseOnly, Regular};
use rat_reloc::RelocatableState;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect, Size};
use ratatui::prelude::{StatefulWidget, Style};
use ratatui::widgets::{Block, Widget};
use std::borrow::Cow;
use std::cell::{RefCell, RefMut};
use std::hash::Hash;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct DualPager<'a, W>
where
W: Eq + Hash + Clone,
{
pager: Pager<W>,
page_nav: PageNavigation<'a>,
}
#[derive(Debug)]
pub struct DualPagerBuffer<'a, W>
where
W: Eq + Hash + Clone,
{
pager0: PagerBuffer<'a, W>,
pager1: PagerBuffer<'a, W>,
}
#[derive(Debug, Clone)]
pub struct DualPagerState<W>
where
W: Eq + Hash + Clone,
{
pub layout: Rc<GenericLayout<W>>,
pub nav: PageNavigationState,
pub non_exhaustive: NonExhaustive,
}
impl<W> Default for DualPager<'_, W>
where
W: Eq + Hash + Clone,
{
fn default() -> Self {
Self {
pager: Default::default(),
page_nav: PageNavigation::new().pages(2),
}
}
}
impl<'a, W> DualPager<'a, W>
where
W: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self::default()
}
pub fn style(mut self, style: Style) -> Self {
self.pager = self.pager.style(style);
self.page_nav = self.page_nav.style(style);
self
}
pub fn label_style(mut self, style: Style) -> Self {
self.pager = self.pager.label_style(style);
self
}
pub fn label_alignment(mut self, alignment: Alignment) -> Self {
self.pager = self.pager.label_alignment(alignment);
self
}
pub fn nav_style(mut self, nav_style: Style) -> Self {
self.page_nav = self.page_nav.nav_style(nav_style);
self
}
pub fn title_style(mut self, title_style: Style) -> Self {
self.page_nav = self.page_nav.title_style(title_style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.page_nav = self.page_nav.block(block);
self
}
pub fn styles(mut self, styles: PagerStyle) -> Self {
self.pager = self.pager.styles(styles.clone());
self.page_nav = self.page_nav.styles(styles);
self
}
pub fn layout_size(&self, area: Rect) -> Size {
self.page_nav.layout_size(area)
}
pub fn into_buffer(
self,
area: Rect,
buf: &'a mut Buffer,
state: &mut DualPagerState<W>,
) -> DualPagerBuffer<'a, W> {
state.nav.page_count = (state.layout.page_count() + 1) / 2;
state.nav.set_page(state.nav.page);
self.page_nav.render(area, buf, &mut state.nav);
let buf = Rc::new(RefCell::new(buf));
DualPagerBuffer {
pager0: self
.pager
.clone()
.layout(state.layout.clone())
.page(state.nav.page * 2)
.into_buffer(state.nav.widget_areas[0], buf.clone()),
pager1: self
.pager
.clone()
.layout(state.layout.clone())
.page(state.nav.page * 2 + 1)
.into_buffer(state.nav.widget_areas[1], buf),
}
}
}
impl<'a, W> DualPagerBuffer<'a, W>
where
W: Eq + Hash + Clone,
{
pub fn is_visible(&self, widget: W) -> bool {
if let Some(idx) = self.pager0.widget_idx(widget) {
self.pager0.is_visible(idx) || self.pager1.is_visible(idx)
} else {
false
}
}
pub fn render_block(&mut self) {
self.pager0.render_block();
self.pager1.render_block();
}
#[inline(always)]
pub fn render_label<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
where
FN: FnOnce(&Option<Cow<'static, str>>) -> WW,
WW: Widget,
{
let Some(idx) = self.pager0.widget_idx(widget) else {
return false;
};
if self.pager0.is_label_visible(idx) {
self.pager0.render_label(idx, render_fn)
} else {
self.pager1.render_label(idx, render_fn)
}
}
#[inline(always)]
pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
where
FN: FnOnce() -> WW,
WW: Widget,
{
let Some(idx) = self.pager0.widget_idx(widget) else {
return false;
};
_ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
if self.pager0.is_visible(idx) {
self.pager0.render_widget(idx, render_fn)
} else {
self.pager1.render_widget(idx, render_fn)
}
}
#[inline(always)]
pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
where
FN: FnOnce() -> Option<WW>,
WW: StatefulWidget<State = SS>,
SS: RelocatableState,
{
let Some(idx) = self.pager0.widget_idx(widget) else {
return false;
};
_ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
if self.pager0.is_visible(idx) {
if self.pager0.render_opt(idx, render_fn, state) {
true
} else {
self.hidden(state);
false
}
} else {
if self.pager1.render_opt(idx, render_fn, state) {
true
} else {
self.hidden(state);
false
}
}
}
#[inline(always)]
pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
where
FN: FnOnce() -> WW,
WW: StatefulWidget<State = SS>,
SS: RelocatableState,
{
let Some(idx) = self.pager0.widget_idx(widget) else {
return false;
};
_ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
if self.pager0.is_visible(idx) {
if self.pager0.render(idx, render_fn, state) {
true
} else {
self.hidden(state);
false
}
} else {
if self.pager1.render(idx, render_fn, state) {
true
} else {
self.hidden(state);
false
}
}
}
#[inline(always)]
#[allow(clippy::question_mark)]
pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
where
FN: FnOnce() -> (WW, R),
WW: StatefulWidget<State = SS>,
SS: RelocatableState,
{
let Some(idx) = self.pager0.widget_idx(widget) else {
return None;
};
_ = self.pager0.render_auto_label(idx) || self.pager1.render_auto_label(idx);
if self.pager0.is_visible(idx) {
if let Some(remainder) = self.pager0.render2(idx, render_fn, state) {
Some(remainder)
} else {
self.hidden(state);
None
}
} else {
if let Some(remainder) = self.pager1.render2(idx, render_fn, state) {
Some(remainder)
} else {
self.hidden(state);
None
}
}
}
pub fn shift(&self) -> (i16, i16) {
(0, 0)
}
#[allow(clippy::question_mark)]
pub fn locate_widget(&self, widget: W) -> Option<Rect> {
let Some(idx) = self.pager0.widget_idx(widget) else {
return None;
};
self.pager0
.locate_widget(idx)
.or_else(|| self.pager1.locate_widget(idx))
}
#[allow(clippy::question_mark)]
pub fn locate_label(&self, widget: W) -> Option<Rect> {
let Some(idx) = self.pager0.widget_idx(widget) else {
return None;
};
self.pager0
.locate_label(idx)
.or_else(|| self.pager1.locate_label(idx))
}
pub fn locate_area(&self, area: Rect) -> Option<Rect> {
self.pager0
.locate_area(area)
.or_else(|| self.pager1.locate_area(area))
}
pub fn relocate<S>(&self, _state: &mut S)
where
S: RelocatableState,
{
}
pub fn hidden<S>(&self, state: &mut S)
where
S: RelocatableState,
{
state.relocate((0, 0), Rect::default())
}
pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
self.pager0.buffer()
}
}
impl<W> Default for DualPagerState<W>
where
W: Eq + Hash + Clone,
{
fn default() -> Self {
Self {
layout: Default::default(),
nav: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl<W> DualPagerState<W>
where
W: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self::default()
}
pub fn set_layout(&mut self, layout: Rc<GenericLayout<W>>) {
self.layout = layout;
}
pub fn layout(&self) -> Rc<GenericLayout<W>> {
self.layout.clone()
}
pub fn show(&mut self, widget: W) {
if let Some(page) = self.layout.page_of(widget) {
self.nav.set_page(page / 2);
}
}
pub fn first(&self, page: usize) -> Option<W> {
self.layout.first(page * 2)
}
pub fn page_of(&self, widget: W) -> Option<usize> {
self.layout.page_of(widget).map(|v| v / 2)
}
pub fn set_page(&mut self, page: usize) -> bool {
self.nav.set_page(page)
}
pub fn page(&self) -> usize {
self.nav.page()
}
pub fn next_page(&mut self) -> bool {
self.nav.next_page()
}
pub fn prev_page(&mut self) -> bool {
self.nav.prev_page()
}
}
impl<W> HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for DualPagerState<W>
where
W: Eq + Hash + Clone,
{
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
self.nav.handle(event, Regular)
}
}
impl<W> HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for DualPagerState<W>
where
W: Eq + Hash + Clone,
{
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
self.nav.handle(event, MouseOnly)
}
}