use crate::key::{self, KeyMap as KeyMapTrait};
use bubbletea_rs::{KeyMsg, Msg};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Type {
#[default]
Arabic,
Dots,
}
#[derive(Debug, Clone)]
pub struct PaginatorKeyMap {
pub prev_page: key::Binding,
pub next_page: key::Binding,
}
impl Default for PaginatorKeyMap {
fn default() -> Self {
Self {
prev_page: key::new_binding(vec![
key::with_keys_str(&["pgup", "left", "h"]),
key::with_help("←/h", "prev page"),
]),
next_page: key::new_binding(vec![
key::with_keys_str(&["pgdown", "right", "l"]),
key::with_help("→/l", "next page"),
]),
}
}
}
impl KeyMapTrait for PaginatorKeyMap {
fn short_help(&self) -> Vec<&key::Binding> {
vec![&self.prev_page, &self.next_page]
}
fn full_help(&self) -> Vec<Vec<&key::Binding>> {
vec![vec![&self.prev_page, &self.next_page]]
}
}
#[derive(Debug, Clone)]
pub struct Model {
pub paginator_type: Type,
pub page: usize,
pub per_page: usize,
pub total_pages: usize,
pub active_dot: String,
pub inactive_dot: String,
pub arabic_format: String,
pub keymap: PaginatorKeyMap,
}
impl Default for Model {
fn default() -> Self {
Self {
paginator_type: Type::default(),
page: 0,
per_page: 1,
total_pages: 1,
active_dot: "•".to_string(),
inactive_dot: "○".to_string(),
arabic_format: "%d/%d".to_string(),
keymap: PaginatorKeyMap::default(),
}
}
}
impl Model {
pub fn new() -> Self {
Self::default()
}
pub fn with_total_items(mut self, items: usize) -> Self {
self.set_total_items(items);
self
}
pub fn with_per_page(mut self, per_page: usize) -> Self {
self.per_page = per_page.max(1);
self
}
pub fn set_per_page(&mut self, per_page: usize) {
self.per_page = per_page.max(1);
}
pub fn with_active_dot(mut self, dot: &str) -> Self {
self.active_dot = dot.to_string();
self
}
pub fn with_inactive_dot(mut self, dot: &str) -> Self {
self.inactive_dot = dot.to_string();
self
}
pub fn set_active_dot(&mut self, dot: &str) {
self.active_dot = dot.to_string();
}
pub fn set_inactive_dot(&mut self, dot: &str) {
self.inactive_dot = dot.to_string();
}
pub fn set_total_pages(&mut self, pages: usize) {
self.total_pages = pages.max(1);
if self.page >= self.total_pages {
self.page = self.total_pages.saturating_sub(1);
}
}
pub fn set_total_items(&mut self, items: usize) {
if items == 0 {
self.total_pages = 1;
} else {
self.total_pages = items.div_ceil(self.per_page);
}
if self.page >= self.total_pages {
self.page = self.total_pages.saturating_sub(1);
}
}
pub fn items_on_page(&self, total_items: usize) -> usize {
if total_items == 0 {
return 0;
}
let (start, end) = self.get_slice_bounds(total_items);
end - start
}
pub fn get_slice_bounds(&self, length: usize) -> (usize, usize) {
let start = self.page * self.per_page;
let end = (start + self.per_page).min(length);
(start, end)
}
pub fn start_index_end_index(&self) -> (usize, usize) {
self.get_slice_bounds(self.per_page * self.total_pages)
}
pub fn prev_page(&mut self) {
if self.page > 0 {
self.page -= 1;
}
}
pub fn next_page(&mut self) {
if !self.on_last_page() {
self.page += 1;
}
}
pub fn on_first_page(&self) -> bool {
self.page == 0
}
pub fn on_last_page(&self) -> bool {
self.page == self.total_pages.saturating_sub(1)
}
pub fn update(&mut self, msg: &Msg) {
if let Some(key_msg) = msg.downcast_ref::<KeyMsg>() {
if self.keymap.next_page.matches(key_msg) {
self.next_page();
} else if self.keymap.prev_page.matches(key_msg) {
self.prev_page();
}
}
}
pub fn view(&self) -> String {
match self.paginator_type {
Type::Arabic => self.arabic_view(),
Type::Dots => self.dots_view(),
}
}
fn arabic_view(&self) -> String {
self.arabic_format
.replacen("%d", &(self.page + 1).to_string(), 1)
.replacen("%d", &self.total_pages.to_string(), 1)
}
fn dots_view(&self) -> String {
let mut s = String::new();
for i in 0..self.total_pages {
if i == self.page {
s.push_str(&self.active_dot);
} else {
s.push_str(&self.inactive_dot);
}
}
s
}
}