use crate::_private::NonExhaustive;
use crate::event::PagerOutcome;
use crate::pager::PagerStyle;
use crate::util::revert_style;
use rat_event::util::MouseFlagsN;
use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Regular};
use rat_focus::{ContainerFlag, FocusContainer};
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect, Size};
use ratatui::style::Style;
use ratatui::text::Span;
use ratatui::widgets::{Block, Borders, StatefulWidget, Widget};
use std::cmp::min;
#[derive(Debug, Clone)]
pub struct PageNavigation<'a> {
pages: u8,
block: Option<Block<'a>>,
style: Style,
nav_style: Option<Style>,
title_style: Option<Style>,
}
#[derive(Debug, Clone)]
pub struct PageNavigationState {
pub area: Rect,
pub widget_areas: Vec<Rect>,
pub prev_area: Rect,
pub next_area: Rect,
pub page: usize,
pub page_count: usize,
pub container: ContainerFlag,
pub mouse: MouseFlagsN,
pub non_exhaustive: NonExhaustive,
}
impl Default for PageNavigation<'_> {
fn default() -> Self {
Self {
pages: 1,
block: Default::default(),
style: Default::default(),
nav_style: Default::default(),
title_style: Default::default(),
}
}
}
impl<'a> PageNavigation<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn pages(mut self, pages: u8) -> Self {
self.pages = pages;
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 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.navigation {
self.nav_style = Some(nav);
}
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_size(&self, area: Rect) -> Size {
let inner = self.inner(area);
Size::new(inner.width / self.pages as u16, inner.height)
}
pub fn inner(&self, area: Rect) -> Rect {
if let Some(block) = &self.block {
block.inner(area)
} else {
Rect::new(
area.x,
area.y + 1,
area.width,
area.height.saturating_sub(1),
)
}
}
}
impl StatefulWidget for PageNavigation<'_> {
type State = PageNavigationState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
state.area = area;
let widget_area = self.inner(area);
let width = widget_area.width / self.pages as u16;
let mut column_area = Rect::new(widget_area.x, widget_area.y, width, widget_area.height);
state.widget_areas.clear();
for _ in 0..self.pages {
state.widget_areas.push(column_area);
column_area.x += column_area.width;
}
let p1 = 5;
let p4 = widget_area.width.saturating_sub(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);
let title = format!(" {}/{} ", state.page + 1, state.page_count);
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);
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 + self.pages as usize) < state.page_count {
Span::from(" >>> ").render(state.next_area, buf);
} else {
Span::from(" [·] ").render(state.next_area, buf);
}
}
}
impl Default for PageNavigationState {
fn default() -> Self {
Self {
area: Default::default(),
widget_areas: Default::default(),
prev_area: Default::default(),
next_area: Default::default(),
page: Default::default(),
page_count: Default::default(),
container: Default::default(),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl PageNavigationState {
pub fn new() -> Self {
Self::default()
}
pub fn set_page(&mut self, page: usize) -> bool {
let old_page = self.page;
self.page = min(page, self.page_count.saturating_sub(1));
old_page != self.page
}
pub fn page(&self) -> usize {
self.page
}
pub fn set_page_count(&mut self, count: usize) {
self.page_count = count;
self.page = min(self.page, count.saturating_sub(1));
}
pub fn page_count(&self) -> usize {
self.page_count
}
pub fn next_page(&mut self) -> bool {
let old_page = self.page;
if self.page + 1 >= self.page_count {
self.page = self.page_count.saturating_sub(1);
} else {
self.page += 1;
}
old_page != self.page
}
pub fn prev_page(&mut self) -> bool {
if self.page > 0 {
self.page -= 1;
true
} else {
false
}
}
}
impl HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for PageNavigationState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
let r = if self.container.is_container_focused() {
match event {
ct_event!(keycode press ALT-PageUp) => {
if self.prev_page() {
PagerOutcome::Page(self.page())
} else {
PagerOutcome::Continue
}
}
ct_event!(keycode press ALT-PageDown) => {
if self.next_page() {
PagerOutcome::Page(self.page())
} else {
PagerOutcome::Continue
}
}
_ => PagerOutcome::Continue,
}
} else {
PagerOutcome::Continue
};
r.or_else(|| self.handle(event, MouseOnly))
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for PageNavigationState {
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.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.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,
}
}
}