Skip to main content

yog_book/
state.rs

1//! Per-book navigation state.
2
3use crate::Book;
4
5/// Max entries shown on the first list spread (right page only).
6pub const ENTRIES_FIRST: usize = 11;
7/// Max entries shown on each subsequent list spread (right page only).
8pub const ENTRIES_PER: usize = 13;
9
10#[derive(Debug, Clone)]
11pub struct BookViewState {
12    pub cat:         usize,
13    pub entry:       usize,   // absolute index within entries_in_cat
14    pub page:        usize,
15    pub at_home:     bool,
16    pub list_spread: usize,   // which page of the entry list we're on
17}
18
19impl Default for BookViewState {
20    fn default() -> Self {
21        Self { cat: 0, entry: 0, page: 0, at_home: true, list_spread: 0 }
22    }
23}
24
25impl BookViewState {
26    /// Handle a navigation event string. Returns true if state changed.
27    pub fn handle(&mut self, ev: &str, book: &Book) -> bool {
28        if ev == "home" {
29            if !self.at_home { self.at_home = true; return true; }
30        } else if let Some(n) = ev.strip_prefix("cat:") {
31            if let Ok(i) = n.parse::<usize>() {
32                if i < book.categories.len() {
33                    let changed = self.at_home || i != self.cat;
34                    self.cat         = i;
35                    self.entry       = 0;
36                    self.page        = 0;
37                    self.list_spread = 0;
38                    self.at_home     = false;
39                    return changed;
40                }
41            }
42        } else if let Some(n) = ev.strip_prefix("entry:") {
43            // N is the absolute index within entries_in_cat.
44            if let Ok(i) = n.parse::<usize>() {
45                let total = self.entries_in_cat(book).len();
46                if i < total && (i != self.entry || self.page != 0) {
47                    self.entry = i;
48                    self.page  = 0;
49                    return true;
50                }
51            }
52        } else if ev == "prev_page" {
53            if self.page > 0 { self.page -= 1; return true; }
54        } else if ev == "next_page" {
55            if self.page + 1 < self.page_count(book) {
56                self.page += 1; return true;
57            }
58        } else if ev == "prev_list" {
59            if self.list_spread > 0 { self.list_spread -= 1; return true; }
60        } else if ev == "next_list" {
61            if self.list_spread + 1 < self.list_spread_count(book) {
62                self.list_spread += 1; return true;
63            }
64        }
65        false
66    }
67
68    /// All entries in the current category, unfiltered.
69    pub fn entries_in_cat<'b>(&self, book: &'b Book) -> Vec<&'b crate::BookEntry> {
70        let cat_id = book.categories.get(self.cat).map(|c| c.id.as_str()).unwrap_or("");
71        book.entries.iter().filter(|e| e.category == cat_id).collect()
72    }
73
74    /// How many list spreads the current category needs.
75    pub fn list_spread_count(&self, book: &Book) -> usize {
76        let total = self.entries_in_cat(book).len();
77        if total <= ENTRIES_FIRST { return 1; }
78        1 + (total - ENTRIES_FIRST + ENTRIES_PER - 1) / ENTRIES_PER
79    }
80
81    /// Absolute index of the first entry on the current list spread.
82    pub fn list_spread_start(&self) -> usize {
83        if self.list_spread == 0 { 0 }
84        else { ENTRIES_FIRST + (self.list_spread - 1) * ENTRIES_PER }
85    }
86
87    /// The slice of entries visible on the current list spread.
88    pub fn entries_visible<'b>(&self, book: &'b Book) -> Vec<&'b crate::BookEntry> {
89        let all = self.entries_in_cat(book);
90        let start = self.list_spread_start();
91        let count = if self.list_spread == 0 { ENTRIES_FIRST } else { ENTRIES_PER };
92        all.into_iter().skip(start).take(count).collect()
93    }
94
95    pub fn current_entry<'b>(&self, book: &'b Book) -> Option<&'b crate::BookEntry> {
96        self.entries_in_cat(book).into_iter().nth(self.entry)
97    }
98
99    pub fn page_count(&self, book: &Book) -> usize {
100        self.current_entry(book).map(|e| e.pages.len().max(1)).unwrap_or(1)
101    }
102}