1use crate::color::{BLACK, READING_PROGRESS, WHITE};
2use crate::color::{TEXT_INVERTED_HARD, TEXT_NORMAL};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::document::HumanSize;
6use crate::font::{font_from_style, Fonts};
7use crate::font::{MD_AUTHOR, MD_KIND, MD_SIZE, MD_TITLE, MD_YEAR};
8use crate::framebuffer::{Framebuffer, UpdateMode};
9use crate::geom::{halves, BorderSpec, CornerSpec, Rectangle};
10use crate::gesture::GestureEvent;
11use crate::metadata::{Info, Status};
12use crate::settings::{FirstColumn, SecondColumn};
13use crate::unit::scale_by_dpi;
14use crate::view::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER, THICKNESS_SMALL};
15
16const PROGRESS_HEIGHT: f32 = 13.0;
17
18pub struct Book {
19 id: Id,
20 rect: Rectangle,
21 children: Vec<Box<dyn View>>,
22 info: Info,
23 index: usize,
24 first_column: FirstColumn,
25 second_column: SecondColumn,
26 preview: Option<crate::framebuffer::Pixmap>,
27 active: bool,
28}
29
30impl Book {
31 pub fn new(
32 rect: Rectangle,
33 info: Info,
34 index: usize,
35 first_column: FirstColumn,
36 second_column: SecondColumn,
37 preview: Option<crate::framebuffer::Pixmap>,
38 ) -> Book {
39 Book {
40 id: ID_FEEDER.next(),
41 rect,
42 children: Vec::new(),
43 info,
44 index,
45 first_column,
46 second_column,
47 preview,
48 active: false,
49 }
50 }
51}
52
53impl View for Book {
54 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
55 fn handle_event(
56 &mut self,
57 evt: &Event,
58 hub: &Hub,
59 bus: &mut Bus,
60 rq: &mut RenderQueue,
61 context: &mut Context,
62 ) -> bool {
63 match *evt {
64 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
65 self.active = true;
66 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
67
68 hub.send(Event::Open(Box::new(self.info.clone()))).ok();
69 true
70 }
71 Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
72 if self.rect.includes(center) =>
73 {
74 let pt = pt!(center.x, self.rect.center().y);
75 bus.push_back(Event::ToggleBookMenu(Rectangle::from_point(pt), self.index));
76 true
77 }
78 Event::RefreshBookPreview(ref path) => {
79 if self.info.file.path == *path {
80 self.preview = context.library.thumbnail_preview(path);
81 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
82 true
83 } else {
84 false
85 }
86 }
87 Event::Invalid(ref path) => {
88 if self.info.file.path == *path {
89 self.active = false;
90 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
91 true
92 } else {
93 false
94 }
95 }
96 _ => false,
97 }
98 }
99 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
100 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
101 let dpi = CURRENT_DEVICE.dpi;
102
103 let scheme = if self.active {
104 TEXT_INVERTED_HARD
105 } else {
106 TEXT_NORMAL
107 };
108
109 fb.draw_rectangle(&self.rect, scheme[0]);
110
111 let (title, author) = if self.first_column == FirstColumn::TitleAndAuthor {
112 (self.info.title(), self.info.author.as_str())
113 } else {
114 let filename = self
115 .info
116 .file
117 .path
118 .file_stem()
119 .map(|v| v.to_string_lossy().into_owned())
120 .unwrap_or_default();
121 (filename, "")
122 };
123
124 let year = &self.info.year;
125 let file_info = &self.info.file;
126
127 let (x_height, padding, baseline) = {
128 let font = font_from_style(fonts, &MD_TITLE, dpi);
129 let x_height = font.x_heights.0 as i32;
130 (
131 x_height,
132 font.em() as i32,
133 (self.rect.height() as i32 - 2 * x_height) / 3,
134 )
135 };
136
137 let (small_half_padding, big_half_padding) = halves(padding);
138 let third_width = 6 * x_height;
139 let second_width = 8 * x_height;
140 let first_width = self.rect.width() as i32 - second_width - third_width;
141 let mut width = first_width - padding - small_half_padding;
142 let mut start_x = self.rect.min.x + padding;
143
144 if let Some(preview) = self.preview.as_ref() {
146 let th = self.rect.height() as i32 - x_height;
147 let tw = 3 * th / 4;
148
149 let dx = (tw - preview.width as i32) / 2;
150 let dy = (th - preview.height as i32) / 2;
151 let pt = pt!(
152 self.rect.min.x + padding + dx,
153 self.rect.min.y + x_height / 2 + dy
154 );
155 fb.draw_pixmap(preview, pt);
156 if fb.inverted() {
157 let rect = preview.rect() + pt;
158 fb.invert_region(&rect);
159 }
160
161 width -= tw + padding;
162 start_x += tw + padding;
163 }
164
165 let author_width = {
167 let font = font_from_style(fonts, &MD_AUTHOR, dpi);
168 let plan = font.plan(author, Some(width), None);
169 let pt = pt!(start_x, self.rect.max.y - baseline);
170 font.render(fb, scheme[1], &plan, pt);
171 plan.width
172 };
173
174 {
176 let font = font_from_style(fonts, &MD_TITLE, dpi);
177 let mut plan = font.plan(&title, None, None);
178 let mut title_lines = 1;
179
180 if plan.width > width {
181 let available = width - author_width;
182 if available > 3 * padding {
183 let (index, usable_width) = font.cut_point(&plan, width);
184 let leftover = plan.width - usable_width;
185 if leftover > 2 * padding {
186 let mut plan2 = plan.split_off(index, usable_width);
187 let max_width = available - if author_width > 0 { padding } else { 0 };
188 font.trim_left(&mut plan2);
189 font.crop_right(&mut plan2, max_width);
190 let pt = pt!(
191 self.rect.min.x + first_width - small_half_padding - plan2.width,
192 self.rect.max.y - baseline
193 );
194 font.render(fb, scheme[1], &plan2, pt);
195 title_lines += 1;
196 } else {
197 font.crop_right(&mut plan, width);
198 }
199 } else {
200 font.crop_right(&mut plan, width);
201 }
202 }
203
204 let dy = if author_width == 0 && title_lines == 1 {
205 (self.rect.height() as i32 - x_height) / 2 + x_height
206 } else {
207 baseline + x_height
208 };
209
210 let pt = pt!(start_x, self.rect.min.y + dy);
211 font.render(fb, scheme[1], &plan, pt);
212 }
213
214 match self.second_column {
216 SecondColumn::Year => {
217 let font = font_from_style(fonts, &MD_YEAR, dpi);
218 let plan = font.plan(year, None, None);
219 let dx = (second_width - padding - plan.width) / 2;
220 let dy = (self.rect.height() as i32 - font.x_heights.1 as i32) / 2;
221 let pt = pt!(
222 self.rect.min.x + first_width + big_half_padding + dx,
223 self.rect.max.y - dy
224 );
225 font.render(fb, scheme[1], &plan, pt);
226 }
227 SecondColumn::Progress => {
228 let progress_height = scale_by_dpi(PROGRESS_HEIGHT, dpi) as i32;
229 let thickness = scale_by_dpi(THICKNESS_SMALL, dpi) as u16;
230 let (small_radius, big_radius) = halves(progress_height);
231 let center = pt!(
232 self.rect.min.x + first_width + second_width / 2,
233 self.rect.min.y + self.rect.height() as i32 / 2
234 );
235 match self.info.status() {
236 Status::New | Status::Finished => {
237 let color = if self.info.reader.is_none() {
238 WHITE
239 } else {
240 BLACK
241 };
242 fb.draw_rounded_rectangle_with_border(
243 &rect![
244 center - pt!(small_radius, small_radius),
245 center + pt!(big_radius, big_radius)
246 ],
247 &CornerSpec::Uniform(small_radius),
248 &BorderSpec {
249 thickness,
250 color: BLACK,
251 },
252 &color,
253 );
254 }
255 Status::Reading(progress) => {
256 let progress_width = 2 * (second_width - padding) / 3;
257 let (small_progress_width, big_progress_width) = halves(progress_width);
258 let x_offset = center.x - progress_width / 2
259 + (progress_width as f32 * progress.min(1.0)) as i32;
260 fb.draw_rounded_rectangle_with_border(
261 &rect![
262 center - pt!(small_progress_width, small_radius),
263 center + pt!(big_progress_width, big_radius)
264 ],
265 &CornerSpec::Uniform(small_radius),
266 &BorderSpec {
267 thickness,
268 color: BLACK,
269 },
270 &|x, _| {
271 if x < x_offset {
272 READING_PROGRESS
273 } else {
274 WHITE
275 }
276 },
277 );
278 }
279 }
280 }
281 }
282
283 {
285 let kind = file_info.kind.to_uppercase();
286 let font = font_from_style(fonts, &MD_KIND, dpi);
287 let mut plan = font.plan(&kind, None, None);
288 let letter_spacing = scale_by_dpi(3.0, dpi) as i32;
289 plan.space_out(letter_spacing);
290 let pt = pt!(
291 self.rect.max.x - padding - plan.width,
292 self.rect.min.y + baseline + x_height
293 );
294 font.render(fb, scheme[1], &plan, pt);
295 }
296
297 {
299 let size = file_info.size.human_size();
300 let font = font_from_style(fonts, &MD_SIZE, dpi);
301 let plan = font.plan(&size, None, None);
302 let pt = pt!(
303 self.rect.max.x - padding - plan.width,
304 self.rect.max.y - baseline
305 );
306 font.render(fb, scheme[1], &plan, pt);
307 }
308 }
309
310 fn rect(&self) -> &Rectangle {
311 &self.rect
312 }
313
314 fn rect_mut(&mut self) -> &mut Rectangle {
315 &mut self.rect
316 }
317
318 fn children(&self) -> &Vec<Box<dyn View>> {
319 &self.children
320 }
321
322 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
323 &mut self.children
324 }
325
326 fn id(&self) -> Id {
327 self.id
328 }
329}