1use yog_formats::book::Book;
4
5pub mod action;
6
7pub const ENTRIES_FIRST: usize = 10;
9pub const ENTRIES_PER: usize = 12;
11
12pub fn list_rows(name: &str) -> usize {
14 yog_ui::text::line_count(name, crate::renderer::geometry::LIST_NAME_W, 1.0).max(1)
15}
16
17#[derive(Debug, Clone)]
18pub struct BookViewState {
19 pub cat: usize,
20 pub entry: usize,
22 pub page: usize,
23 pub at_home: bool,
24 pub list_spread: usize,
26}
27
28impl Default for BookViewState {
29 fn default() -> Self {
30 Self {
31 cat: 0,
32 entry: 0,
33 page: 0,
34 at_home: true,
35 list_spread: 0,
36 }
37 }
38}
39
40impl BookViewState {
41 pub fn handle(&mut self, ev: &str, book: &Book) -> bool {
43 if ev == action::HOME {
44 if !self.at_home {
45 self.at_home = true;
46 return true;
47 }
48 } else if let Some(n) = ev.strip_prefix(action::CATEGORY) {
49 if let Ok(i) = n.parse::<usize>()
50 && i < book.categories.len()
51 {
52 let changed = self.at_home || i != self.cat;
53 self.cat = i;
54 self.entry = 0;
55 self.page = 0;
56 self.list_spread = 0;
57 self.at_home = false;
58 return changed;
59 }
60 } else if let Some(n) = ev.strip_prefix(action::ENTRY) {
61 if let Ok(i) = n.parse::<usize>() {
63 let total = self.entries_in_cat(book).len();
64 if i < total && (i != self.entry || self.page != 0) {
65 self.entry = i;
66 self.page = 0;
67 return true;
68 }
69 }
70 } else if ev == action::PREV_PAGE {
71 if self.page > 0 {
72 self.page -= 1;
73 return true;
74 }
75 } else if ev == action::NEXT_PAGE {
76 if self.page + 1 < self.page_count(book) {
77 self.page += 1;
78 return true;
79 }
80 } else if ev == action::PREV_LIST {
81 if self.list_spread > 0 {
82 self.list_spread -= 1;
83 return true;
84 }
85 } else if ev == action::NEXT_LIST && self.list_spread + 1 < self.list_spread_count(book) {
86 self.list_spread += 1;
87 return true;
88 }
89 false
90 }
91
92 pub fn entries_in_cat<'b>(&self, book: &'b Book) -> Vec<&'b yog_formats::book::BookEntry> {
94 let cat_id = book
95 .categories
96 .get(self.cat)
97 .map(|c| c.id.as_str())
98 .unwrap_or("");
99 book.entries
100 .iter()
101 .filter(|e| e.category == cat_id && crate::shown(e))
102 .collect()
103 }
104
105 fn list_spreads(&self, book: &Book) -> Vec<std::ops::Range<usize>> {
108 let all = self.entries_in_cat(book);
109 let mut spreads = Vec::new();
110 let (mut start, mut used) = (0, 0);
111 for (i, e) in all.iter().enumerate() {
112 let rows = list_rows(&e.name);
113 let room = if spreads.is_empty() {
114 ENTRIES_FIRST
115 } else {
116 ENTRIES_PER
117 };
118 if used + rows > room && i > start {
119 spreads.push(start..i);
120 (start, used) = (i, 0);
121 }
122 used += rows;
123 }
124 spreads.push(start..all.len());
125 spreads
126 }
127
128 pub fn list_spread_count(&self, book: &Book) -> usize {
130 self.list_spreads(book).len()
131 }
132
133 pub fn list_spread_start(&self, book: &Book) -> usize {
135 self.list_spreads(book)
136 .get(self.list_spread)
137 .map_or(0, |r| r.start)
138 }
139
140 pub fn entries_visible<'b>(&self, book: &'b Book) -> Vec<&'b yog_formats::book::BookEntry> {
142 let range = self
143 .list_spreads(book)
144 .get(self.list_spread)
145 .cloned()
146 .unwrap_or_default();
147 let all = self.entries_in_cat(book);
148 all.into_iter()
149 .skip(range.start)
150 .take(range.len())
151 .collect()
152 }
153
154 pub fn current_entry<'b>(&self, book: &'b Book) -> Option<&'b yog_formats::book::BookEntry> {
155 self.entries_in_cat(book).into_iter().nth(self.entry)
156 }
157
158 pub fn page_count(&self, book: &Book) -> usize {
159 self.current_entry(book)
160 .map(|e| e.pages.len().max(1))
161 .unwrap_or(1)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 #[test]
168 fn a_long_entry_name_takes_more_rows() {
169 assert_eq!(super::list_rows("Типы"), 1);
170 assert!(super::list_rows(&"Продвинутое Экранирование ".repeat(3)) > 1);
171 }
172}