1use super::book::Book;
2use crate::color::{SEPARATOR_NORMAL, WHITE};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::document::open;
6use crate::font::Fonts;
7use crate::framebuffer::{Framebuffer, UpdateMode};
8use crate::geom::divide;
9use crate::geom::{halves, CycleDir, Dir, Rectangle};
10use crate::gesture::GestureEvent;
11use crate::helpers::Fingerprint;
12use crate::metadata::Info;
13use crate::settings::{FirstColumn, SecondColumn};
14use crate::unit::scale_by_dpi;
15use crate::view::filler::Filler;
16use crate::view::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
17use crate::view::{BIG_BAR_HEIGHT, THICKNESS_MEDIUM};
18use lazy_static::lazy_static;
19use std::sync::Mutex;
20use std::thread;
21
22lazy_static! {
23 static ref EXCLUSIVE_ACCESS: Mutex<u8> = Mutex::new(0);
24}
25
26pub struct Shelf {
27 id: Id,
28 pub rect: Rectangle,
29 children: Vec<Box<dyn View>>,
30 pub max_lines: usize,
31 first_column: FirstColumn,
32 second_column: SecondColumn,
33 thumbnail_previews: bool,
34}
35
36impl Shelf {
37 pub fn new(
38 rect: Rectangle,
39 first_column: FirstColumn,
40 second_column: SecondColumn,
41 thumbnail_previews: bool,
42 ) -> Shelf {
43 let dpi = CURRENT_DEVICE.dpi;
44 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
45 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
46 let max_lines = ((rect.height() as i32 + thickness) / big_height) as usize;
47 Shelf {
48 id: ID_FEEDER.next(),
49 rect,
50 children: Vec::new(),
51 max_lines,
52 first_column,
53 second_column,
54 thumbnail_previews,
55 }
56 }
57
58 pub fn set_first_column(&mut self, first_column: FirstColumn) {
59 self.first_column = first_column;
60 }
61
62 pub fn set_second_column(&mut self, second_column: SecondColumn) {
63 self.second_column = second_column;
64 }
65
66 pub fn set_thumbnail_previews(&mut self, thumbnail_previews: bool) {
67 self.thumbnail_previews = thumbnail_previews;
68 }
69
70 pub fn update(
71 &mut self,
72 metadata: &[Info],
73 hub: &Hub,
74 rq: &mut RenderQueue,
75 context: &Context,
76 ) {
77 self.children.clear();
78 let dpi = CURRENT_DEVICE.dpi;
79 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
80 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
81 let (small_thickness, big_thickness) = halves(thickness);
82 let max_lines = ((self.rect.height() as i32 + thickness) / big_height) as usize;
83 let book_heights = divide(self.rect.height() as i32, max_lines as i32);
84 let mut y_pos = self.rect.min.y;
85 let th = big_height;
86 let tw = 3 * th / 4;
87
88 for (index, info) in metadata.iter().enumerate() {
89 let y_min = y_pos + if index > 0 { big_thickness } else { 0 };
90 let y_max = y_pos + book_heights[index]
91 - if index < max_lines - 1 {
92 small_thickness
93 } else {
94 0
95 };
96
97 let preview = if self.thumbnail_previews {
98 let existing = context.library.thumbnail_preview(&info.file.path);
99 if existing.is_none() {
100 let hub2 = hub.clone();
101 let path = info.file.path.clone();
102 let full_path = context.library.home.join(&info.file.path);
103 let database = context.library.db.clone();
104 let fp = full_path
105 .metadata()
106 .ok()
107 .and_then(|md| md.fingerprint(context.library.fat32_epoch).ok());
108
109 if let Some(fp) = fp {
110 thread::spawn(move || {
111 let _guard = EXCLUSIVE_ACCESS.lock().unwrap();
112 if let Some(bytes) = open(full_path)
113 .and_then(|mut doc| {
114 doc.preview_pixmap(
115 tw as f32,
116 th as f32,
117 CURRENT_DEVICE.color_samples(),
118 )
119 })
120 .and_then(|pixmap| pixmap.to_png_bytes().ok())
121 {
122 database.save_thumbnail(fp, &bytes).ok();
123 hub2.send(Event::RefreshBookPreview(path)).ok();
124 }
125 });
126 }
127 }
128 existing
129 } else {
130 None
131 };
132
133 let book = Book::new(
134 rect![self.rect.min.x, y_min, self.rect.max.x, y_max],
135 info.clone(),
136 index,
137 self.first_column,
138 self.second_column,
139 preview,
140 );
141 self.children.push(Box::new(book) as Box<dyn View>);
142
143 if index < max_lines - 1 {
144 let separator = Filler::new(
145 rect![self.rect.min.x, y_max, self.rect.max.x, y_max + thickness],
146 SEPARATOR_NORMAL,
147 );
148 self.children.push(Box::new(separator) as Box<dyn View>);
149 }
150
151 y_pos += book_heights[index];
152 }
153
154 if metadata.len() < max_lines {
155 let y_start = y_pos + if metadata.is_empty() { 0 } else { thickness };
156 let filler = Filler::new(
157 rect![self.rect.min.x, y_start, self.rect.max.x, self.rect.max.y],
158 WHITE,
159 );
160 self.children.push(Box::new(filler) as Box<dyn View>);
161 }
162
163 self.max_lines = max_lines;
164 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Partial));
165 }
166}
167
168impl View for Shelf {
169 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
170 fn handle_event(
171 &mut self,
172 evt: &Event,
173 _hub: &Hub,
174 bus: &mut Bus,
175 _rq: &mut RenderQueue,
176 _context: &mut Context,
177 ) -> bool {
178 match *evt {
179 Event::Gesture(GestureEvent::Swipe { dir, start, .. }) if self.rect.includes(start) => {
180 match dir {
181 Dir::West => {
182 bus.push_back(Event::Page(CycleDir::Next));
183 true
184 }
185 Dir::East => {
186 bus.push_back(Event::Page(CycleDir::Previous));
187 true
188 }
189 _ => false,
190 }
191 }
192 _ => false,
193 }
194 }
195
196 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
197 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
198
199 fn rect(&self) -> &Rectangle {
200 &self.rect
201 }
202
203 fn rect_mut(&mut self) -> &mut Rectangle {
204 &mut self.rect
205 }
206
207 fn children(&self) -> &Vec<Box<dyn View>> {
208 &self.children
209 }
210
211 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
212 &mut self.children
213 }
214
215 fn id(&self) -> Id {
216 self.id
217 }
218}