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() { ENTRIES_FIRST } else { ENTRIES_PER };
114 if used + rows > room && i > start {
115 spreads.push(start..i);
116 (start, used) = (i, 0);
117 }
118 used += rows;
119 }
120 spreads.push(start..all.len());
121 spreads
122 }
123
124 pub fn list_spread_count(&self, book: &Book) -> usize {
126 self.list_spreads(book).len()
127 }
128
129 pub fn list_spread_start(&self, book: &Book) -> usize {
131 self.list_spreads(book).get(self.list_spread).map_or(0, |r| r.start)
132 }
133
134 pub fn entries_visible<'b>(&self, book: &'b Book) -> Vec<&'b yog_formats::book::BookEntry> {
136 let range = self.list_spreads(book).get(self.list_spread).cloned().unwrap_or_default();
137 let all = self.entries_in_cat(book);
138 all.into_iter().skip(range.start).take(range.len()).collect()
139 }
140
141 pub fn current_entry<'b>(&self, book: &'b Book) -> Option<&'b yog_formats::book::BookEntry> {
142 self.entries_in_cat(book).into_iter().nth(self.entry)
143 }
144
145 pub fn page_count(&self, book: &Book) -> usize {
146 self.current_entry(book)
147 .map(|e| e.pages.len().max(1))
148 .unwrap_or(1)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 #[test]
155 fn a_long_entry_name_takes_more_rows() {
156 assert_eq!(super::list_rows("Типы"), 1);
157 assert!(super::list_rows(&"Продвинутое Экранирование ".repeat(3)) > 1);
158 }
159}