use crate::key::{self, Binding};
use rusty_bubbletea::key::{Key, KeyPressMsg};
use rusty_bubbletea::model::{Cmd, Msg};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Type {
#[default]
Arabic,
Dots,
}
#[derive(Debug, Clone)]
pub struct KeyMap {
pub prev_page: Binding,
pub next_page: Binding,
}
pub fn default_key_map() -> KeyMap {
KeyMap {
prev_page: key::new_binding(vec![key::with_keys(&["pgup", "left", "h"])]),
next_page: key::new_binding(vec![key::with_keys(&["pgdown", "right", "l"])]),
}
}
#[derive(Debug, Clone)]
pub struct Model {
pub 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 key_map: KeyMap,
}
impl Model {
pub fn set_total_pages(&mut self, items: usize) -> usize {
if items < 1 {
return self.total_pages;
}
let mut n = items / self.per_page;
if !items.is_multiple_of(self.per_page) {
n += 1;
}
self.total_pages = n;
n
}
pub fn items_on_page(&self, total_items: usize) -> usize {
if total_items < 1 {
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 = (self.page * self.per_page + self.per_page).min(length);
(start, end)
}
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_last_page(&self) -> bool {
self.page == self.total_pages - 1
}
pub fn on_first_page(&self) -> bool {
self.page == 0
}
pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
if let Some(m) = msg.as_any().downcast_ref::<KeyPressMsg>() {
let k: &Key = &m.0;
if key::matches(k, std::slice::from_ref(&self.key_map.next_page)) {
self.next_page();
} else if key::matches(k, std::slice::from_ref(&self.key_map.prev_page)) {
self.prev_page();
}
}
None
}
pub fn view(&self) -> String {
match self.type_ {
Type::Dots => self.dots_view(),
Type::Arabic => self.arabic_view(),
}
}
fn dots_view(&self) -> String {
let mut s = String::new();
for i in 0..self.total_pages {
if i == self.page {
s += &self.active_dot;
continue;
}
s += &self.inactive_dot;
}
s
}
fn arabic_view(&self) -> String {
let format = self.arabic_format.clone();
if format == "%d/%d" {
format!("{}/{}", self.page + 1, self.total_pages)
} else {
let s = format.replace("%d", &(self.page + 1).to_string());
s.replace("%d", &self.total_pages.to_string())
}
}
}
pub type Option = Box<dyn FnOnce(&mut Model)>;
pub fn new(opts: Vec<Option>) -> Model {
let mut m = Model {
type_: Type::Arabic,
page: 0,
per_page: 1,
total_pages: 1,
key_map: default_key_map(),
active_dot: "•".to_string(),
inactive_dot: "○".to_string(),
arabic_format: "%d/%d".to_string(),
};
for opt in opts {
opt(&mut m);
}
m
}
pub fn with_total_pages(total_pages: usize) -> Option {
Box::new(move |m: &mut Model| {
m.total_pages = total_pages;
})
}
pub fn with_per_page(per_page: usize) -> Option {
Box::new(move |m: &mut Model| {
m.per_page = per_page;
})
}