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 {
22            cat: 0,
23            entry: 0,
24            page: 0,
25            at_home: true,
26            list_spread: 0,
27        }
28    }
29}
30
31impl BookViewState {
32    /// Handle a navigation event string. Returns true if state changed.
33    pub fn handle(&mut self, ev: &str, book: &Book) -> bool {
34        if ev == "home" {
35            if !self.at_home {
36                self.at_home = true;
37                return true;
38            }
39        } else if let Some(n) = ev.strip_prefix("cat:") {
40            if let Ok(i) = n.parse::<usize>() {
41                if i < book.categories.len() {
42                    let changed = self.at_home || i != self.cat;
43                    self.cat = i;
44                    self.entry = 0;
45                    self.page = 0;
46                    self.list_spread = 0;
47                    self.at_home = false;
48                    return changed;
49                }
50            }
51        } else if let Some(n) = ev.strip_prefix("entry:") {
52            // N is the absolute index within entries_in_cat.
53            if let Ok(i) = n.parse::<usize>() {
54                let total = self.entries_in_cat(book).len();
55                if i < total && (i != self.entry || self.page != 0) {
56                    self.entry = i;
57                    self.page = 0;
58                    return true;
59                }
60            }
61        } else if ev == "prev_page" {
62            if self.page > 0 {
63                self.page -= 1;
64                return true;
65            }
66        } else if ev == "next_page" {
67            if self.page + 1 < self.page_count(book) {
68                self.page += 1;
69                return true;
70            }
71        } else if ev == "prev_list" {
72            if self.list_spread > 0 {
73                self.list_spread -= 1;
74                return true;
75            }
76        } else if ev == "next_list" && self.list_spread + 1 < self.list_spread_count(book) {
77            self.list_spread += 1;
78            return true;
79        }
80        false
81    }
82
83    /// All entries in the current category, unfiltered.
84    pub fn entries_in_cat<'b>(&self, book: &'b Book) -> Vec<&'b crate::BookEntry> {
85        let cat_id = book
86            .categories
87            .get(self.cat)
88            .map(|c| c.id.as_str())
89            .unwrap_or("");
90        book.entries
91            .iter()
92            .filter(|e| e.category == cat_id)
93            .collect()
94    }
95
96    /// How many list spreads the current category needs.
97    pub fn list_spread_count(&self, book: &Book) -> usize {
98        let total = self.entries_in_cat(book).len();
99        if total <= ENTRIES_FIRST {
100            return 1;
101        }
102        1 + (total - ENTRIES_FIRST).div_ceil(ENTRIES_PER)
103    }
104
105    /// Absolute index of the first entry on the current list spread.
106    pub fn list_spread_start(&self) -> usize {
107        if self.list_spread == 0 {
108            0
109        } else {
110            ENTRIES_FIRST + (self.list_spread - 1) * ENTRIES_PER
111        }
112    }
113
114    /// The slice of entries visible on the current list spread.
115    pub fn entries_visible<'b>(&self, book: &'b Book) -> Vec<&'b crate::BookEntry> {
116        let all = self.entries_in_cat(book);
117        let start = self.list_spread_start();
118        let count = if self.list_spread == 0 {
119            ENTRIES_FIRST
120        } else {
121            ENTRIES_PER
122        };
123        all.into_iter().skip(start).take(count).collect()
124    }
125
126    pub fn current_entry<'b>(&self, book: &'b Book) -> Option<&'b crate::BookEntry> {
127        self.entries_in_cat(book).into_iter().nth(self.entry)
128    }
129
130    pub fn page_count(&self, book: &Book) -> usize {
131        self.current_entry(book)
132            .map(|e| e.pages.len().max(1))
133            .unwrap_or(1)
134    }
135}