Skip to main content

yog_book/
state.rs

1//! Per-book navigation state.
2
3use yog_formats::book::Book;
4
5pub mod action;
6
7/// Max entries shown on the first list spread (right page only).
8pub const ENTRIES_FIRST: usize = 10;
9/// Max entries shown on each subsequent list spread (right page only).
10pub const ENTRIES_PER: usize = 12;
11
12#[derive(Debug, Clone)]
13pub struct BookViewState {
14    pub cat: usize,
15    /// The entry's index among its category's (all of them, not just the spread's).
16    pub entry: usize,
17    pub page: usize,
18    pub at_home: bool,
19    /// Which spread of the entry list is shown.
20    pub list_spread: usize,
21}
22
23impl Default for BookViewState {
24    fn default() -> Self {
25        Self {
26            cat: 0,
27            entry: 0,
28            page: 0,
29            at_home: true,
30            list_spread: 0,
31        }
32    }
33}
34
35impl BookViewState {
36    /// Handle a navigation event string. Returns true if state changed.
37    pub fn handle(&mut self, ev: &str, book: &Book) -> bool {
38        if ev == action::HOME {
39            if !self.at_home {
40                self.at_home = true;
41                return true;
42            }
43        } else if let Some(n) = ev.strip_prefix(action::CATEGORY) {
44            if let Ok(i) = n.parse::<usize>()
45                && i < book.categories.len()
46            {
47                let changed = self.at_home || i != self.cat;
48                self.cat = i;
49                self.entry = 0;
50                self.page = 0;
51                self.list_spread = 0;
52                self.at_home = false;
53                return changed;
54            }
55        } else if let Some(n) = ev.strip_prefix(action::ENTRY) {
56            // N is the absolute index within entries_in_cat.
57            if let Ok(i) = n.parse::<usize>() {
58                let total = self.entries_in_cat(book).len();
59                if i < total && (i != self.entry || self.page != 0) {
60                    self.entry = i;
61                    self.page = 0;
62                    return true;
63                }
64            }
65        } else if ev == action::PREV_PAGE {
66            if self.page > 0 {
67                self.page -= 1;
68                return true;
69            }
70        } else if ev == action::NEXT_PAGE {
71            if self.page + 1 < self.page_count(book) {
72                self.page += 1;
73                return true;
74            }
75        } else if ev == action::PREV_LIST {
76            if self.list_spread > 0 {
77                self.list_spread -= 1;
78                return true;
79            }
80        } else if ev == action::NEXT_LIST && self.list_spread + 1 < self.list_spread_count(book) {
81            self.list_spread += 1;
82            return true;
83        }
84        false
85    }
86
87    /// All entries in the current category, unfiltered.
88    pub fn entries_in_cat<'b>(&self, book: &'b Book) -> Vec<&'b yog_formats::book::BookEntry> {
89        let cat_id = book
90            .categories
91            .get(self.cat)
92            .map(|c| c.id.as_str())
93            .unwrap_or("");
94        book.entries
95            .iter()
96            .filter(|e| e.category == cat_id && crate::shown(e))
97            .collect()
98    }
99
100    /// How many list spreads the current category needs.
101    pub fn list_spread_count(&self, book: &Book) -> usize {
102        let total = self.entries_in_cat(book).len();
103        if total <= ENTRIES_FIRST {
104            return 1;
105        }
106        1 + (total - ENTRIES_FIRST).div_ceil(ENTRIES_PER)
107    }
108
109    /// Absolute index of the first entry on the current list spread.
110    pub fn list_spread_start(&self) -> usize {
111        if self.list_spread == 0 {
112            0
113        } else {
114            ENTRIES_FIRST + (self.list_spread - 1) * ENTRIES_PER
115        }
116    }
117
118    /// The slice of entries visible on the current list spread.
119    pub fn entries_visible<'b>(&self, book: &'b Book) -> Vec<&'b yog_formats::book::BookEntry> {
120        let all = self.entries_in_cat(book);
121        let start = self.list_spread_start();
122        let count = if self.list_spread == 0 {
123            ENTRIES_FIRST
124        } else {
125            ENTRIES_PER
126        };
127        all.into_iter().skip(start).take(count).collect()
128    }
129
130    pub fn current_entry<'b>(&self, book: &'b Book) -> Option<&'b yog_formats::book::BookEntry> {
131        self.entries_in_cat(book).into_iter().nth(self.entry)
132    }
133
134    pub fn page_count(&self, book: &Book) -> usize {
135        self.current_entry(book)
136            .map(|e| e.pages.len().max(1))
137            .unwrap_or(1)
138    }
139}