use crate::_private::NonExhaustive;
use crate::event::PagerOutcome;
use crate::layout::StructuredLayout;
use crate::pager::{AreaHandle, PagerLayout, PagerStyle};
use crate::util::revert_style;
use rat_event::util::MouseFlagsN;
use rat_event::{ct_event, HandleEvent, MouseOnly, Regular};
use rat_focus::ContainerFlag;
use rat_reloc::RelocatableState;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::prelude::{Span, StatefulWidget, Style};
use ratatui::widgets::{Block, Borders, Widget};
use std::cmp::min;
use std::ops::Index;
#[derive(Debug, Default, Clone)]
pub struct DualPager<'a> {
layout: PagerLayout,
block: Option<Block<'a>>,
style: Style,
nav_style: Option<Style>,
title_style: Option<Style>,
divider_style: Option<Style>,
}
#[derive(Debug)]
pub struct DualPagerBuffer<'a> {
layout: PagerLayout,
page: usize,
buffer: &'a mut Buffer,
widget_area1: Rect,
widget_area2: Rect,
style: Style,
nav_style: Option<Style>,
divider_style: Option<Style>,
}
#[derive(Debug)]
pub struct DualPagerWidget {
style: Style,
nav_style: Option<Style>,
divider_style: Option<Style>,
}
#[derive(Debug, Clone)]
pub struct DualPagerState {
pub area: Rect,
pub widget_area1: Rect,
pub widget_area2: Rect,
pub scroll_area: Rect,
pub prev_area: Rect,
pub next_area: Rect,
pub divider_area: Rect,
pub layout: PagerLayout,
pub page: usize,
pub c_focus: ContainerFlag,
pub mouse: MouseFlagsN,
pub non_exhaustive: NonExhaustive,
}
impl<'a> DualPager<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn layout(mut self, page_layout: PagerLayout) -> Self {
self.layout = page_layout;
self
}
pub fn struct_layout(mut self, page_layout: StructuredLayout) -> Self {
self.layout = PagerLayout::with_layout(page_layout);
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self.block = self.block.map(|v| v.style(style));
self
}
pub fn nav_style(mut self, nav_style: Style) -> Self {
self.nav_style = Some(nav_style);
self
}
pub fn divider_style(mut self, divider_style: Style) -> Self {
self.divider_style = Some(divider_style);
self
}
pub fn title_style(mut self, title_style: Style) -> Self {
self.title_style = Some(title_style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block.style(self.style));
self
}
pub fn styles(mut self, styles: PagerStyle) -> Self {
self.style = styles.style;
if let Some(nav) = styles.nav {
self.nav_style = Some(nav);
}
if let Some(divider) = styles.divider {
self.divider_style = Some(divider);
}
if let Some(title) = styles.title {
self.title_style = Some(title);
}
if let Some(block) = styles.block {
self.block = Some(block);
}
self.block = self.block.map(|v| v.style(styles.style));
self
}
pub fn layout_width(&self, area: Rect) -> u16 {
min(self.inner_left(area).width, self.inner_right(area).width)
}
pub fn inner_left(&self, area: Rect) -> Rect {
let mut inner = if let Some(block) = &self.block {
block.inner(area)
} else {
Rect::new(
area.x,
area.y + 1,
area.width,
area.height.saturating_sub(1),
)
};
inner.width = inner.width.saturating_sub(1) / 2;
inner
}
pub fn inner_right(&self, area: Rect) -> Rect {
let mut inner = if let Some(block) = &self.block {
block.inner(area)
} else {
Rect::new(
area.x,
area.y + 1,
area.width,
area.height.saturating_sub(1),
)
};
inner.width = inner
.width
.saturating_sub(1 + inner.width.saturating_sub(1) / 2);
inner
}
pub fn into_buffer<'b>(
self,
area: Rect,
buf: &'b mut Buffer,
state: &mut DualPagerState,
) -> DualPagerBuffer<'b> {
state.area = area;
let widget_area = if let Some(block) = &self.block {
block.inner(area)
} else {
Rect::new(
area.x,
area.y + 1,
area.width,
area.height.saturating_sub(1),
)
};
let p1 = 5;
let p4 = widget_area.width - p1;
state.prev_area = Rect::new(widget_area.x, area.y, p1, 1);
state.next_area = Rect::new(widget_area.x + p4, area.y, p1, 1);
state.scroll_area = Rect::new(area.x + 1, area.y, area.width.saturating_sub(2), 1);
let p1 = widget_area.width.saturating_sub(1) / 2;
let p2 = widget_area.width.saturating_sub(1).saturating_sub(p1);
state.widget_area1 = Rect::new(widget_area.x, widget_area.y, p1, widget_area.height);
state.divider_area = Rect::new(widget_area.x + p1, widget_area.y, 1, widget_area.height);
state.widget_area2 = Rect::new(
widget_area.x + p1 + 1,
widget_area.y,
p2,
widget_area.height,
);
state.layout = self.layout;
state.layout.layout(state.widget_area1);
state.set_page(state.page);
let title = format!(" {}/{} ", state.page + 1, state.layout.num_pages());
let block = self
.block
.unwrap_or_else(|| Block::new().borders(Borders::TOP).style(self.style))
.title_bottom(title)
.title_alignment(Alignment::Right);
let block = if let Some(title_style) = self.title_style {
block.title_style(title_style)
} else {
block
};
block.render(area, buf);
DualPagerBuffer {
layout: state.layout.clone(),
page: state.page,
buffer: buf,
widget_area1: state.widget_area1,
widget_area2: state.widget_area2,
style: self.style,
nav_style: self.nav_style,
divider_style: self.divider_style,
}
}
}
impl<'a> DualPagerBuffer<'a> {
#[inline(always)]
pub fn render_widget<W>(&mut self, widget: W, area: Rect)
where
W: Widget,
{
if let Some(buffer_area) = self.locate_area(area) {
widget.render(buffer_area, self.buffer);
} else {
}
}
#[inline(always)]
pub fn render_stateful<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
where
W: StatefulWidget<State = S>,
S: RelocatableState,
{
if let Some(buffer_area) = self.locate_area(area) {
widget.render(buffer_area, self.buffer, state);
} else {
self.hidden(state);
}
}
#[inline(always)]
pub fn render_widget_handle<W, Idx>(&mut self, widget: W, area: AreaHandle, tag: Idx)
where
W: Widget,
[Rect]: Index<Idx, Output = Rect>,
{
if let Some(buffer_areas) = self.locate_handle(area) {
widget.render(buffer_areas[tag], self.buffer);
} else {
}
}
#[inline(always)]
pub fn render_stateful_handle<W, S, Idx>(
&mut self,
widget: W,
area: AreaHandle,
tag: Idx,
state: &mut S,
) where
W: StatefulWidget<State = S>,
S: RelocatableState,
[Rect]: Index<Idx, Output = Rect>,
{
if let Some(buffer_areas) = self.locate_handle(area) {
widget.render(buffer_areas[tag], self.buffer, state);
} else {
self.hidden(state);
}
}
pub fn layout(&self) -> &PagerLayout {
&self.layout
}
pub fn is_visible_area(&self, area: Rect) -> bool {
self.layout.buf_area(area).0 == self.page
}
pub fn is_visible_handle(&self, handle: AreaHandle) -> bool {
self.layout.buf_handle(handle).0 == self.page
}
pub fn shift(&self) -> (i16, i16) {
(0, 0)
}
pub fn locate_handle(&self, handle: AreaHandle) -> Option<Box<[Rect]>> {
let (page, mut areas) = self.layout.buf_handle(handle);
if self.page == page {
for area in &mut areas {
*area = Rect::new(
area.x + self.widget_area1.x,
area.y + self.widget_area1.y,
area.width,
area.height,
);
}
Some(areas)
} else if self.page + 1 == page {
for area in &mut areas {
*area = Rect::new(
area.x + self.widget_area2.x,
area.y + self.widget_area2.y,
area.width,
area.height,
);
}
Some(areas)
} else {
None
}
}
pub fn locate_area(&self, layout_area: Rect) -> Option<Rect> {
let (page, area) = self.layout.buf_area(layout_area);
if self.page == page {
Some(Rect::new(
area.x + self.widget_area1.x,
area.y + self.widget_area1.y,
area.width,
area.height,
))
} else if self.page + 1 == page {
Some(Rect::new(
area.x + self.widget_area2.x,
area.y + self.widget_area2.y,
area.width,
area.height,
))
} else {
None
}
}
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_mut(&mut self) -> &mut Buffer {
self.buffer
}
pub fn into_widget(self) -> DualPagerWidget {
DualPagerWidget {
style: self.style,
nav_style: self.nav_style,
divider_style: self.divider_style,
}
}
}
impl StatefulWidget for DualPagerWidget {
type State = DualPagerState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
assert_eq!(area, state.area);
let divider_style = self.divider_style.unwrap_or(self.style);
if let Some(cell) = buf.cell_mut((state.divider_area.x, area.top())) {
cell.set_style(divider_style);
cell.set_symbol("\u{239E}");
}
for y in state.divider_area.top()..area.bottom().saturating_sub(1) {
if let Some(cell) = buf.cell_mut((state.divider_area.x, y)) {
cell.set_style(divider_style);
cell.set_symbol("\u{239C}");
}
}
if let Some(cell) = buf.cell_mut((state.divider_area.x, area.bottom().saturating_sub(1))) {
cell.set_style(divider_style);
cell.set_symbol("\u{239D}");
}
let nav_style = self.nav_style.unwrap_or(self.style);
if matches!(state.mouse.hover.get(), Some(0)) {
buf.set_style(state.prev_area, revert_style(nav_style));
} else {
buf.set_style(state.prev_area, nav_style);
}
if state.page > 0 {
Span::from(" <<< ").render(state.prev_area, buf);
} else {
Span::from(" [·] ").render(state.prev_area, buf);
}
if matches!(state.mouse.hover.get(), Some(1)) {
buf.set_style(state.next_area, revert_style(nav_style));
} else {
buf.set_style(state.next_area, nav_style);
}
if state.page + 2 < state.layout.num_pages() {
Span::from(" >>> ").render(state.next_area, buf);
} else {
Span::from(" [·] ").render(state.next_area, buf);
}
}
}
impl Default for DualPagerState {
fn default() -> Self {
Self {
area: Default::default(),
widget_area1: Default::default(),
divider_area: Default::default(),
widget_area2: Default::default(),
scroll_area: Default::default(),
prev_area: Default::default(),
next_area: Default::default(),
layout: Default::default(),
page: 0,
c_focus: Default::default(),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl DualPagerState {
pub fn new() -> Self {
Self::default()
}
pub fn show_handle(&mut self, handle: AreaHandle) {
let (page, _) = self.layout.buf_handle(handle);
self.page = page & !1;
}
pub fn show_area(&mut self, area: Rect) {
let (page, _) = self.layout.buf_area(area);
self.page = page & !1;
}
pub fn first_handle(&self, page: usize) -> Option<AreaHandle> {
self.layout.first_layout_handle(page)
}
pub fn set_page(&mut self, page: usize) -> bool {
let old_page = self.page;
if page >= self.layout.num_pages() {
self.page = (self.layout.num_pages() - 1) & !1;
} else {
self.page = page & !1;
}
old_page != self.page
}
pub fn next_page(&mut self) -> bool {
let old_page = self.page;
if self.page + 2 >= self.layout.num_pages() {
self.page = (self.layout.num_pages() - 1) & !1;
} else {
self.page = (self.page + 2) & !1;
}
old_page != self.page
}
pub fn prev_page(&mut self) -> bool {
if self.page >= 2 {
self.page = (self.page - 2) & !1;
true
} else {
false
}
}
}
impl HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for DualPagerState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
self.handle(event, MouseOnly)
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for DualPagerState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
match event {
ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
if self.prev_page() {
PagerOutcome::Page(self.page)
} else {
PagerOutcome::Unchanged
}
}
ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
if self.next_page() {
PagerOutcome::Page(self.page)
} else {
PagerOutcome::Unchanged
}
}
ct_event!(scroll down for x,y) => {
if self.scroll_area.contains((*x, *y).into()) {
if self.next_page() {
PagerOutcome::Page(self.page)
} else {
PagerOutcome::Unchanged
}
} else {
PagerOutcome::Continue
}
}
ct_event!(scroll up for x,y) => {
if self.scroll_area.contains((*x, *y).into()) {
if self.prev_page() {
PagerOutcome::Page(self.page)
} else {
PagerOutcome::Unchanged
}
} else {
PagerOutcome::Continue
}
}
ct_event!(mouse any for m)
if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
{
PagerOutcome::Changed
}
_ => PagerOutcome::Continue,
}
}
}