Skip to main content

cadmus_core/view/home/
book.rs

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        // Preview
145        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        // Author
166        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        // Title
175        {
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        // Year or Progress
215        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        // File kind
284        {
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        // File size
298        {
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}