1mod address_bar;
2mod book;
3mod bottom_bar;
4pub mod directories_bar;
5mod directory;
6mod library_label;
7mod shelf;
8
9use self::address_bar::AddressBar;
10use self::bottom_bar::BottomBar;
11use self::shelf::Shelf;
12use super::top_bar::{TopBar, TopBarVariant};
13use crate::color::BLACK;
14use crate::context::Context;
15use crate::device::CURRENT_DEVICE;
16use crate::font::Fonts;
17use crate::framebuffer::{Framebuffer, UpdateMode};
18use crate::geom::{halves, CycleDir, DiagDir, Dir, Rectangle};
19use crate::gesture::GestureEvent;
20use crate::input::{ButtonCode, ButtonStatus, DeviceEvent};
21use crate::library::Library;
22use crate::metadata::{sort, BookQuery, Info, Metadata, SimpleStatus, SortMethod};
23use crate::settings::{FirstColumn, Hook, LibraryMode, SecondColumn};
24use crate::unit::scale_by_dpi;
25use crate::view::common::{locate, locate_by_id, rlocate};
26use crate::view::common::{toggle_battery_menu, toggle_clock_menu, toggle_main_menu};
27use crate::view::filler::Filler;
28use crate::view::keyboard::Keyboard;
29use crate::view::menu::{Menu, MenuKind};
30use crate::view::menu_entry::MenuEntry;
31use crate::view::named_input::NamedInput;
32use crate::view::navigation::providers::directory::DirectoryNavigationProvider;
33use crate::view::navigation::StackNavigationBar;
34use crate::view::notification::Notification;
35use crate::view::search_bar::SearchBar;
36use crate::view::{Bus, Event, Hub, RenderData, RenderQueue, View};
37use crate::view::{EntryId, EntryKind, Id, ViewId, ID_FEEDER};
38use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
39use anyhow::{format_err, Error};
40use fxhash::FxHashMap;
41use rand_core::Rng;
42use serde_json::{json, Value as JsonValue};
43use std::fs;
44use std::io::Write;
45use std::io::{BufRead, BufReader};
46use std::mem;
47use std::path::{Path, PathBuf};
48use std::process::{Child, Command, Stdio};
49use std::thread;
50use tracing::error;
51
52pub const TRASH_DIRNAME: &str = ".trash";
53
54pub struct Home {
55 id: Id,
56 rect: Rectangle,
57 children: Vec<Box<dyn View>>,
58 current_page: usize,
59 pages_count: usize,
60 shelf_index: usize,
61 focus: Option<ViewId>,
62 query: Option<BookQuery>,
63 sort_method: SortMethod,
64 reverse_order: bool,
65 visible_books: Metadata,
66 current_directory: PathBuf,
67 target_document: Option<PathBuf>,
68 background_fetchers: FxHashMap<u32, Fetcher>,
69}
70
71struct Fetcher {
72 path: PathBuf,
73 full_path: PathBuf,
74 process: Child,
75 sort_method: Option<SortMethod>,
76 first_column: Option<FirstColumn>,
77 second_column: Option<SecondColumn>,
78}
79
80impl Home {
81 pub fn new(
82 rect: Rectangle,
83 hub: &Hub,
84 rq: &mut RenderQueue,
85 context: &mut Context,
86 ) -> Result<Home, Error> {
87 let id = ID_FEEDER.next();
88 let dpi = CURRENT_DEVICE.dpi;
89 let mut children = Vec::new();
90
91 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
92 let (small_thickness, big_thickness) = halves(thickness);
93 let (small_height, big_height) = (
94 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
95 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
96 );
97
98 let selected_library = context.settings.selected_library;
99 let library_settings = &context.settings.libraries[selected_library];
100
101 let current_directory = context.library.home.clone();
102 let sort_method = library_settings.sort_method;
103 let reverse_order = sort_method.reverse_order();
104
105 context.library.sort(sort_method, reverse_order);
106
107 let (visible_books, dirs) = context.library.list(¤t_directory, None, false);
108 let count = visible_books.len();
109 let current_page = 0;
110 let mut shelf_index = 2;
111
112 let top_bar = TopBar::new(
113 rect![
114 rect.min.x,
115 rect.min.y,
116 rect.max.x,
117 rect.min.y + small_height - small_thickness
118 ],
119 TopBarVariant::Search(Event::Toggle(ViewId::SearchBar)),
120 sort_method.title(),
121 context,
122 );
123 children.push(Box::new(top_bar) as Box<dyn View>);
124
125 let separator = Filler::new(
126 rect![
127 rect.min.x,
128 rect.min.y + small_height - small_thickness,
129 rect.max.x,
130 rect.min.y + small_height + big_thickness
131 ],
132 BLACK,
133 );
134 children.push(Box::new(separator) as Box<dyn View>);
135
136 let mut y_start = rect.min.y + small_height + big_thickness;
137
138 if context.settings.home.address_bar {
139 let addr_bar = AddressBar::new(
140 rect![
141 rect.min.x,
142 y_start,
143 rect.max.x,
144 y_start + small_height - thickness
145 ],
146 current_directory.to_string_lossy(),
147 context,
148 );
149 children.push(Box::new(addr_bar) as Box<dyn View>);
150 y_start += small_height - thickness;
151
152 let separator = Filler::new(
153 rect![rect.min.x, y_start, rect.max.x, y_start + thickness],
154 BLACK,
155 );
156 children.push(Box::new(separator) as Box<dyn View>);
157 y_start += thickness;
158 shelf_index += 2;
159 }
160
161 if context.settings.home.navigation_bar {
162 let provider = DirectoryNavigationProvider;
163 let mut nav_bar = StackNavigationBar::new(
164 rect![
165 rect.min.x,
166 y_start,
167 rect.max.x,
168 y_start + small_height - thickness
169 ],
170 rect.max.y - small_height - big_height - small_thickness,
171 context.settings.home.max_levels,
172 provider,
173 current_directory.clone(),
174 );
175
176 nav_bar.set_selected(current_directory.clone(), &mut RenderQueue::new(), context);
177 y_start = nav_bar.rect().max.y;
178
179 children.push(Box::new(nav_bar) as Box<dyn View>);
180
181 let separator = Filler::new(
182 rect![rect.min.x, y_start, rect.max.x, y_start + thickness],
183 BLACK,
184 );
185 children.push(Box::new(separator) as Box<dyn View>);
186 y_start += thickness;
187 shelf_index += 2;
188 }
189
190 let selected_library = context.settings.selected_library;
191 let library_settings = &context.settings.libraries[selected_library];
192
193 let mut shelf = Shelf::new(
194 rect![
195 rect.min.x,
196 y_start,
197 rect.max.x,
198 rect.max.y - small_height - small_thickness
199 ],
200 library_settings.first_column,
201 library_settings.second_column,
202 library_settings.thumbnail_previews,
203 );
204
205 let max_lines = shelf.max_lines;
206 let pages_count = (visible_books.len() as f32 / max_lines as f32).ceil() as usize;
207 let index_lower = current_page * max_lines;
208 let index_upper = (index_lower + max_lines).min(visible_books.len());
209
210 shelf.update(
211 &visible_books[index_lower..index_upper],
212 hub,
213 &mut RenderQueue::new(),
214 context,
215 );
216
217 children.push(Box::new(shelf) as Box<dyn View>);
218
219 let separator = Filler::new(
220 rect![
221 rect.min.x,
222 rect.max.y - small_height - small_thickness,
223 rect.max.x,
224 rect.max.y - small_height + big_thickness
225 ],
226 BLACK,
227 );
228 children.push(Box::new(separator) as Box<dyn View>);
229
230 let bottom_bar = BottomBar::new(
231 rect![
232 rect.min.x,
233 rect.max.y - small_height + big_thickness,
234 rect.max.x,
235 rect.max.y
236 ],
237 current_page,
238 pages_count,
239 &library_settings.name,
240 count,
241 false,
242 );
243 children.push(Box::new(bottom_bar) as Box<dyn View>);
244
245 rq.add(RenderData::new(id, rect, UpdateMode::Full));
246
247 Ok(Home {
248 id,
249 rect,
250 children,
251 current_page,
252 pages_count,
253 shelf_index,
254 focus: None,
255 query: None,
256 sort_method,
257 reverse_order,
258 visible_books,
259 current_directory,
260 target_document: None,
261 background_fetchers: FxHashMap::default(),
262 })
263 }
264
265 fn select_directory(
266 &mut self,
267 path: &Path,
268 hub: &Hub,
269 rq: &mut RenderQueue,
270 context: &mut Context,
271 ) {
272 if self.current_directory == path {
273 return;
274 }
275
276 let old_path = mem::replace(&mut self.current_directory, path.to_path_buf());
277 self.terminate_fetchers(&old_path, true, hub, context);
278
279 let selected_library = context.settings.selected_library;
280 for hook in &context.settings.libraries[selected_library].hooks {
281 if context.library.home.join(&hook.path) == path {
282 self.insert_fetcher(hook, hub, context);
283 }
284 }
285
286 let (files, dirs) =
287 context
288 .library
289 .list(&self.current_directory, self.query.as_ref(), false);
290 self.visible_books = files;
291 self.current_page = 0;
292
293 let mut index = 2;
294
295 if context.settings.home.address_bar {
296 let addr_bar = self.children[index]
297 .as_mut()
298 .downcast_mut::<AddressBar>()
299 .unwrap();
300 addr_bar.set_text(self.current_directory.to_string_lossy(), rq, context);
301 index += 2;
302 }
303
304 if context.settings.home.navigation_bar {
305 let nav_bar = self.children[index]
306 .as_mut()
307 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
308 .unwrap();
309 nav_bar.set_selected(self.current_directory.clone(), rq, context);
310 self.adjust_shelf_top_edge();
311 rq.add(RenderData::new(
312 self.child(index + 1).id(),
313 *self.child(index + 1).rect(),
314 UpdateMode::Partial,
315 ));
316 rq.add(RenderData::new(
317 self.child(index).id(),
318 *self.child(index).rect(),
319 UpdateMode::Partial,
320 ));
321 }
322
323 self.update_shelf(true, hub, rq, context);
324 self.update_bottom_bar(rq, context);
325 }
326
327 fn adjust_shelf_top_edge(&mut self) {
328 let separator_index = self.shelf_index - 1;
329 let shelf_index = self.shelf_index;
330
331 let target_separator_min_y = if let Some(nav_bar_index) =
332 locate::<StackNavigationBar<DirectoryNavigationProvider>>(self)
333 {
334 self.children[nav_bar_index].rect().max.y
335 } else {
336 self.children[separator_index].rect().min.y
337 };
338 let current_separator_min_y = self.children[separator_index].rect().min.y;
339 let y_shift = target_separator_min_y - current_separator_min_y;
340
341 *self.children[separator_index].rect_mut() += pt!(0, y_shift);
342 self.children[shelf_index].rect_mut().min.y = self.children[separator_index].rect().max.y;
343 }
344
345 fn toggle_select_directory(
346 &mut self,
347 path: &Path,
348 hub: &Hub,
349 rq: &mut RenderQueue,
350 context: &mut Context,
351 ) {
352 if self.current_directory.starts_with(path) {
353 if let Some(parent) = path.parent() {
354 self.select_directory(parent, hub, rq, context);
355 }
356 } else {
357 self.select_directory(path, hub, rq, context);
358 }
359 }
360
361 fn go_to_page(&mut self, index: usize, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
362 if index >= self.pages_count {
363 return;
364 }
365 self.current_page = index;
366 self.update_shelf(false, hub, rq, context);
367 self.update_bottom_bar(rq, context);
368 }
369
370 fn go_to_neighbor(
371 &mut self,
372 dir: CycleDir,
373 hub: &Hub,
374 rq: &mut RenderQueue,
375 context: &mut Context,
376 ) {
377 match dir {
378 CycleDir::Next if self.current_page < self.pages_count.saturating_sub(1) => {
379 self.current_page += 1;
380 }
381 CycleDir::Previous if self.current_page > 0 => {
382 self.current_page -= 1;
383 }
384 _ => return,
385 }
386
387 self.update_shelf(false, hub, rq, context);
388 self.update_bottom_bar(rq, context);
389 }
390
391 fn go_to_status_change(
392 &mut self,
393 dir: CycleDir,
394 hub: &Hub,
395 rq: &mut RenderQueue,
396 context: &mut Context,
397 ) {
398 if self.pages_count < 2 {
399 return;
400 }
401
402 let max_lines = self.children[self.shelf_index]
403 .as_ref()
404 .downcast_ref::<Shelf>()
405 .unwrap()
406 .max_lines;
407 let index_lower = self.current_page * max_lines;
408 let index_upper = (index_lower + max_lines).min(self.visible_books.len());
409 let book_index = match dir {
410 CycleDir::Next => index_upper.saturating_sub(1),
411 CycleDir::Previous => index_lower,
412 };
413 let status = self.visible_books[book_index].simple_status();
414
415 let page = match dir {
416 CycleDir::Next => self.visible_books[book_index + 1..]
417 .iter()
418 .position(|info| info.simple_status() != status)
419 .map(|delta| self.current_page + 1 + delta / max_lines),
420 CycleDir::Previous => self.visible_books[..book_index]
421 .iter()
422 .rev()
423 .position(|info| info.simple_status() != status)
424 .map(|delta| self.current_page - 1 - delta / max_lines),
425 };
426
427 if let Some(page) = page {
428 self.current_page = page;
429 self.update_shelf(false, hub, rq, context);
430 self.update_bottom_bar(rq, context);
431 }
432 }
433
434 fn refresh_visibles(
436 &mut self,
437 update: bool,
438 reset_page: bool,
439 hub: &Hub,
440 rq: &mut RenderQueue,
441 context: &mut Context,
442 ) {
443 let (files, _) = context
444 .library
445 .list(&self.current_directory, self.query.as_ref(), false);
446 self.visible_books = files;
447
448 let max_lines = {
449 let shelf = self
450 .child(self.shelf_index)
451 .downcast_ref::<Shelf>()
452 .unwrap();
453 shelf.max_lines
454 };
455
456 self.pages_count = (self.visible_books.len() as f32 / max_lines as f32).ceil() as usize;
457
458 if reset_page {
459 self.current_page = 0;
460 } else if self.current_page >= self.pages_count {
461 self.current_page = self.pages_count.saturating_sub(1);
462 }
463
464 if update {
465 self.update_shelf(false, hub, rq, context);
466 self.update_bottom_bar(rq, context);
467 }
468 }
469
470 fn update_first_column(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
471 let selected_library = context.settings.selected_library;
472 self.children[self.shelf_index]
473 .as_mut()
474 .downcast_mut::<Shelf>()
475 .unwrap()
476 .set_first_column(context.settings.libraries[selected_library].first_column);
477 self.update_shelf(false, hub, rq, context);
478 }
479
480 fn update_second_column(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
481 let selected_library = context.settings.selected_library;
482 self.children[self.shelf_index]
483 .as_mut()
484 .downcast_mut::<Shelf>()
485 .unwrap()
486 .set_second_column(context.settings.libraries[selected_library].second_column);
487 self.update_shelf(false, hub, rq, context);
488 }
489
490 fn update_thumbnail_previews(
491 &mut self,
492 hub: &Hub,
493 rq: &mut RenderQueue,
494 context: &mut Context,
495 ) {
496 let selected_library = context.settings.selected_library;
497 self.children[self.shelf_index]
498 .as_mut()
499 .downcast_mut::<Shelf>()
500 .unwrap()
501 .set_thumbnail_previews(
502 context.settings.libraries[selected_library].thumbnail_previews,
503 );
504 self.update_shelf(false, hub, rq, context);
505 }
506
507 fn update_shelf(
508 &mut self,
509 was_resized: bool,
510 hub: &Hub,
511 rq: &mut RenderQueue,
512 context: &mut Context,
513 ) {
514 let dpi = CURRENT_DEVICE.dpi;
515 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
516 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
517 let shelf = self.children[self.shelf_index]
518 .as_mut()
519 .downcast_mut::<Shelf>()
520 .unwrap();
521 let max_lines = ((shelf.rect.height() as i32 + thickness) / big_height) as usize;
522
523 if was_resized {
524 let page_position = if self.visible_books.is_empty() {
525 0.0
526 } else {
527 self.current_page as f32
528 * (shelf.max_lines as f32 / self.visible_books.len() as f32)
529 };
530
531 let mut page_guess = page_position * self.visible_books.len() as f32 / max_lines as f32;
532 let page_ceil = page_guess.ceil();
533
534 if (page_ceil - page_guess).abs() < f32::EPSILON {
535 page_guess = page_ceil;
536 }
537
538 self.pages_count = (self.visible_books.len() as f32 / max_lines as f32).ceil() as usize;
539 self.current_page = (page_guess as usize).min(self.pages_count.saturating_sub(1));
540 }
541
542 let index_lower = self.current_page * max_lines;
543 let index_upper = (index_lower + max_lines).min(self.visible_books.len());
544
545 shelf.update(
546 &self.visible_books[index_lower..index_upper],
547 hub,
548 rq,
549 context,
550 );
551 }
552
553 fn update_top_bar(&mut self, search_visible: bool, rq: &mut RenderQueue) {
554 if let Some(index) = locate::<TopBar>(self) {
555 let top_bar = self.children[index]
556 .as_mut()
557 .downcast_mut::<TopBar>()
558 .unwrap();
559 let name = if search_visible { "back" } else { "search" };
560 top_bar.update_root_icon(name, rq);
561 top_bar.update_title_label(&self.sort_method.title(), rq);
562 }
563 }
564
565 fn update_bottom_bar(&mut self, rq: &mut RenderQueue, context: &Context) {
566 if let Some(index) = rlocate::<BottomBar>(self) {
567 let bottom_bar = self.children[index]
568 .as_mut()
569 .downcast_mut::<BottomBar>()
570 .unwrap();
571 let filter = self.query.is_some() || self.current_directory != context.library.home;
572 let selected_library = context.settings.selected_library;
573 let library_settings = &context.settings.libraries[selected_library];
574 bottom_bar.update_library_label(
575 &library_settings.name,
576 self.visible_books.len(),
577 filter,
578 rq,
579 );
580 bottom_bar.update_page_label(self.current_page, self.pages_count, rq);
581 bottom_bar.update_icons(self.current_page, self.pages_count, rq);
582 }
583 }
584
585 fn toggle_keyboard(
586 &mut self,
587 enable: bool,
588 update: bool,
589 id: Option<ViewId>,
590 hub: &Hub,
591 rq: &mut RenderQueue,
592 context: &mut Context,
593 ) {
594 let dpi = CURRENT_DEVICE.dpi;
595 let (small_height, big_height) = (
596 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
597 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
598 );
599 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
600 let (small_thickness, big_thickness) = halves(thickness);
601 let has_search_bar = self.children[self.shelf_index + 2].is::<SearchBar>();
602
603 if let Some(index) = rlocate::<Keyboard>(self) {
604 if enable {
605 return;
606 }
607
608 let y_min = self.child(self.shelf_index + 1).rect().min.y;
609 let mut rect = *self.child(index).rect();
610 rect.absorb(self.child(index - 1).rect());
611
612 self.children.drain(index - 1..=index);
613
614 let delta_y = rect.height() as i32;
615
616 if has_search_bar {
617 for i in self.shelf_index + 1..=self.shelf_index + 2 {
618 let shifted_rect = *self.child(i).rect() + pt!(0, delta_y);
619 self.child_mut(i).resize(shifted_rect, hub, rq, context);
620 }
621 }
622
623 context.kb_rect = Rectangle::default();
624 hub.send(Event::Focus(None)).ok();
625 if update {
626 let rect = rect![self.rect.min.x, y_min, self.rect.max.x, y_min + delta_y];
627 rq.add(RenderData::expose(rect, UpdateMode::Gui));
628 }
629 } else {
630 if !enable {
631 return;
632 }
633
634 let index = rlocate::<BottomBar>(self).unwrap() - 1;
635 let mut kb_rect = rect![
636 self.rect.min.x,
637 self.rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
638 self.rect.max.x,
639 self.rect.max.y - small_height - small_thickness
640 ];
641
642 let number = matches!(id, Some(ViewId::GoToPageInput));
643 let keyboard = Keyboard::new(&mut kb_rect, number, context);
644 self.children
645 .insert(index, Box::new(keyboard) as Box<dyn View>);
646
647 let separator = Filler::new(
648 rect![
649 self.rect.min.x,
650 kb_rect.min.y - thickness,
651 self.rect.max.x,
652 kb_rect.min.y
653 ],
654 BLACK,
655 );
656 self.children
657 .insert(index, Box::new(separator) as Box<dyn View>);
658
659 let delta_y = kb_rect.height() as i32 + thickness;
660
661 if has_search_bar {
662 for i in self.shelf_index + 1..=self.shelf_index + 2 {
663 let shifted_rect = *self.child(i).rect() + pt!(0, -delta_y);
664 self.child_mut(i).resize(shifted_rect, hub, rq, context);
665 }
666 }
667 }
668
669 if update {
670 if enable {
671 if has_search_bar {
672 for i in self.shelf_index + 1..=self.shelf_index + 4 {
673 let update_mode = if (i - self.shelf_index) == 1 {
674 UpdateMode::Partial
675 } else {
676 UpdateMode::Gui
677 };
678 rq.add(RenderData::new(
679 self.child(i).id(),
680 *self.child(i).rect(),
681 update_mode,
682 ));
683 }
684 } else {
685 for i in self.shelf_index + 1..=self.shelf_index + 2 {
686 rq.add(RenderData::new(
687 self.child(i).id(),
688 *self.child(i).rect(),
689 UpdateMode::Gui,
690 ));
691 }
692 }
693 } else if has_search_bar {
694 for i in self.shelf_index + 1..=self.shelf_index + 2 {
695 rq.add(RenderData::new(
696 self.child(i).id(),
697 *self.child(i).rect(),
698 UpdateMode::Gui,
699 ));
700 }
701 }
702 }
703 }
704
705 fn toggle_address_bar(
706 &mut self,
707 enable: Option<bool>,
708 update: bool,
709 hub: &Hub,
710 rq: &mut RenderQueue,
711 context: &mut Context,
712 ) {
713 let dpi = CURRENT_DEVICE.dpi;
714 let (small_height, big_height) = (
715 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
716 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
717 );
718 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
719
720 if let Some(index) = locate::<AddressBar>(self) {
721 if let Some(true) = enable {
722 return;
723 }
724
725 if let Some(ViewId::AddressBarInput) = self.focus {
726 self.toggle_keyboard(
727 false,
728 false,
729 Some(ViewId::AddressBarInput),
730 hub,
731 rq,
732 context,
733 );
734 }
735
736 self.children.drain(index..=index + 1);
738 self.shelf_index -= 2;
739 context.settings.home.address_bar = false;
740
741 if context.settings.home.navigation_bar {
743 let nav_bar = self.children[self.shelf_index - 2]
744 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
745 .unwrap();
746 nav_bar.shift(pt!(0, -small_height));
747 }
748
749 *self.children[self.shelf_index - 1].rect_mut() += pt!(0, -small_height);
751
752 self.children[self.shelf_index].rect_mut().min.y -= small_height;
754
755 if context.settings.home.navigation_bar {
756 self.adjust_shelf_top_edge();
757 }
758 } else {
759 if let Some(false) = enable {
760 return;
761 }
762
763 let sp_rect = *self.child(1).rect() + pt!(0, small_height);
764
765 let separator = Filler::new(sp_rect, BLACK);
766 self.children
767 .insert(2, Box::new(separator) as Box<dyn View>);
768
769 let addr_bar = AddressBar::new(
770 rect![
771 self.rect.min.x,
772 sp_rect.min.y - small_height + thickness,
773 self.rect.max.x,
774 sp_rect.min.y
775 ],
776 self.current_directory.to_string_lossy(),
777 context,
778 );
779 self.children.insert(2, Box::new(addr_bar) as Box<dyn View>);
780
781 self.shelf_index += 2;
782 context.settings.home.address_bar = true;
783
784 *self.children[self.shelf_index - 1].rect_mut() += pt!(0, small_height);
786
787 self.children[self.shelf_index].rect_mut().min.y += small_height;
789
790 if context.settings.home.navigation_bar {
791 let rect = *self.children[self.shelf_index].rect();
792 let y_shift = rect.height() as i32 - (big_height - thickness);
793 let nav_bar = self.children[self.shelf_index - 2]
794 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
795 .unwrap();
796 nav_bar.shift(pt!(0, small_height));
798
799 if y_shift < 0 {
801 let y_shift = nav_bar.shrink(y_shift, &mut context.fonts);
802 self.children[self.shelf_index].rect_mut().min.y += y_shift;
803 *self.children[self.shelf_index - 1].rect_mut() += pt!(0, y_shift);
804 }
805
806 self.adjust_shelf_top_edge();
807 }
808 }
809
810 if update {
811 for i in 2..self.shelf_index {
812 rq.add(RenderData::new(
813 self.child(i).id(),
814 *self.child(i).rect(),
815 UpdateMode::Gui,
816 ));
817 }
818
819 self.update_shelf(true, hub, rq, context);
820 self.update_bottom_bar(rq, context);
821 }
822 }
823
824 fn toggle_navigation_bar(
825 &mut self,
826 enable: Option<bool>,
827 update: bool,
828 hub: &Hub,
829 rq: &mut RenderQueue,
830 context: &mut Context,
831 ) {
832 let dpi = CURRENT_DEVICE.dpi;
833 let (small_height, big_height) = (
834 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
835 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
836 );
837 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
838 let (small_thickness, _) = halves(thickness);
839
840 if let Some(index) = locate::<StackNavigationBar<DirectoryNavigationProvider>>(self) {
841 if let Some(true) = enable {
842 return;
843 }
844
845 let mut rect = *self.child(index).rect();
846 rect.absorb(self.child(index + 1).rect());
847 let delta_y = rect.height() as i32;
848
849 self.children.drain(index..=index + 1);
851 self.shelf_index -= 2;
852 context.settings.home.navigation_bar = false;
853
854 self.children[self.shelf_index].rect_mut().min.y -= delta_y;
856 } else {
857 if let Some(false) = enable {
858 return;
859 }
860
861 let sep_index = if context.settings.home.address_bar {
862 3
863 } else {
864 1
865 };
866 let sp_rect = *self.child(sep_index).rect() + pt!(0, small_height);
867
868 let separator = Filler::new(sp_rect, BLACK);
869 self.children
870 .insert(sep_index + 1, Box::new(separator) as Box<dyn View>);
871
872 let provider = DirectoryNavigationProvider;
873 let mut nav_bar = StackNavigationBar::new(
874 rect![
875 self.rect.min.x,
876 sp_rect.min.y - small_height + thickness,
877 self.rect.max.x,
878 sp_rect.min.y
879 ],
880 self.rect.max.y - small_height - big_height - small_thickness,
881 context.settings.home.max_levels,
882 provider,
883 self.current_directory.clone(),
884 );
885
886 nav_bar.set_selected(self.current_directory.clone(), rq, context);
887 self.children
888 .insert(sep_index + 1, Box::new(nav_bar) as Box<dyn View>);
889
890 self.shelf_index += 2;
891 context.settings.home.navigation_bar = true;
892
893 self.adjust_shelf_top_edge();
894 }
895
896 if update {
897 for i in 2..self.shelf_index {
898 rq.add(RenderData::new(
899 self.child(i).id(),
900 *self.child(i).rect(),
901 UpdateMode::Gui,
902 ));
903 }
904
905 self.update_shelf(true, hub, rq, context);
906 self.update_bottom_bar(rq, context);
907 }
908 }
909
910 fn toggle_search_bar(
911 &mut self,
912 enable: Option<bool>,
913 update: bool,
914 hub: &Hub,
915 rq: &mut RenderQueue,
916 context: &mut Context,
917 ) {
918 let dpi = CURRENT_DEVICE.dpi;
919 let (small_height, big_height) = (
920 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
921 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
922 );
923 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
924 let delta_y = small_height;
925 let search_visible: bool;
926 let mut has_keyboard = false;
927
928 if let Some(index) = rlocate::<SearchBar>(self) {
929 if let Some(true) = enable {
930 return;
931 }
932
933 if let Some(ViewId::HomeSearchInput) = self.focus {
934 self.toggle_keyboard(
935 false,
936 false,
937 Some(ViewId::HomeSearchInput),
938 hub,
939 rq,
940 context,
941 );
942 }
943
944 self.children.drain(index - 1..=index);
946
947 self.children[self.shelf_index].rect_mut().max.y += delta_y;
949
950 if context.settings.home.navigation_bar {
951 let nav_bar = self.children[self.shelf_index - 2]
952 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
953 .unwrap();
954 nav_bar.vertical_limit += delta_y;
955 }
956
957 self.query = None;
958 search_visible = false;
959 } else {
960 if let Some(false) = enable {
961 return;
962 }
963
964 let sp_rect = *self.child(self.shelf_index + 1).rect() - pt!(0, delta_y);
965 let search_bar = SearchBar::new(
966 rect![
967 self.rect.min.x,
968 sp_rect.max.y,
969 self.rect.max.x,
970 sp_rect.max.y + delta_y - thickness
971 ],
972 ViewId::HomeSearchInput,
973 "Title, author, series",
974 "",
975 context,
976 );
977 self.children
978 .insert(self.shelf_index + 1, Box::new(search_bar) as Box<dyn View>);
979
980 let separator = Filler::new(sp_rect, BLACK);
981 self.children
982 .insert(self.shelf_index + 1, Box::new(separator) as Box<dyn View>);
983
984 self.children[self.shelf_index].rect_mut().max.y -= delta_y;
986
987 if context.settings.home.navigation_bar {
988 let rect = *self.children[self.shelf_index].rect();
989 let y_shift = rect.height() as i32 - (big_height - thickness);
990 let nav_bar = self.children[self.shelf_index - 2]
991 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
992 .unwrap();
993 nav_bar.vertical_limit -= delta_y;
994
995 if y_shift < 0 {
997 let y_shift = nav_bar.shrink(y_shift, &mut context.fonts);
998 self.children[self.shelf_index].rect_mut().min.y += y_shift;
999 *self.children[self.shelf_index - 1].rect_mut() += pt!(0, y_shift);
1000 }
1001 }
1002
1003 if self.query.is_none() {
1004 if rlocate::<Keyboard>(self).is_none() {
1005 self.toggle_keyboard(
1006 true,
1007 false,
1008 Some(ViewId::HomeSearchInput),
1009 hub,
1010 rq,
1011 context,
1012 );
1013 has_keyboard = true;
1014 }
1015
1016 hub.send(Event::Focus(Some(ViewId::HomeSearchInput))).ok();
1017 }
1018
1019 search_visible = true;
1020 }
1021
1022 if update {
1023 if !search_visible {
1024 self.refresh_visibles(false, true, hub, rq, context);
1025 }
1026
1027 self.update_top_bar(search_visible, rq);
1028
1029 if search_visible {
1030 rq.add(RenderData::new(
1031 self.child(self.shelf_index - 1).id(),
1032 *self.child(self.shelf_index - 1).rect(),
1033 UpdateMode::Partial,
1034 ));
1035 let mut rect = *self.child(self.shelf_index).rect();
1036 rect.max.y = self.child(self.shelf_index + 1).rect().min.y;
1037 self.update_shelf(true, hub, &mut RenderQueue::new(), context);
1039 rq.add(RenderData::new(
1040 self.child(self.shelf_index).id(),
1041 rect,
1042 UpdateMode::Partial,
1043 ));
1044 rect.min.y = rect.max.y;
1046 let end_index = self.shelf_index + if has_keyboard { 4 } else { 2 };
1047 rect.max.y = self.child(end_index).rect().max.y;
1048 rq.add(RenderData::expose(rect, UpdateMode::Partial));
1049 } else {
1050 for i in self.shelf_index - 1..=self.shelf_index + 1 {
1051 if i == self.shelf_index {
1052 self.update_shelf(true, hub, rq, context);
1053 continue;
1054 }
1055 rq.add(RenderData::new(
1056 self.child(i).id(),
1057 *self.child(i).rect(),
1058 UpdateMode::Partial,
1059 ));
1060 }
1061 }
1062
1063 self.update_bottom_bar(rq, context);
1064 }
1065 }
1066
1067 fn toggle_rename_document(
1068 &mut self,
1069 enable: Option<bool>,
1070 hub: &Hub,
1071 rq: &mut RenderQueue,
1072 context: &mut Context,
1073 ) {
1074 if let Some(index) = locate_by_id(self, ViewId::RenameDocument) {
1075 if let Some(true) = enable {
1076 return;
1077 }
1078 self.target_document = None;
1079 rq.add(RenderData::expose(
1080 *self.child(index).rect(),
1081 UpdateMode::Gui,
1082 ));
1083 self.children.remove(index);
1084 if let Some(ViewId::RenameDocumentInput) = self.focus {
1085 self.toggle_keyboard(
1086 false,
1087 true,
1088 Some(ViewId::RenameDocumentInput),
1089 hub,
1090 rq,
1091 context,
1092 );
1093 }
1094 } else {
1095 if let Some(false) = enable {
1096 return;
1097 }
1098 let mut ren_doc = NamedInput::new(
1099 "Rename document".to_string(),
1100 ViewId::RenameDocument,
1101 ViewId::RenameDocumentInput,
1102 21,
1103 context,
1104 );
1105 if let Some(text) = self
1106 .target_document
1107 .as_ref()
1108 .and_then(|path| path.file_name())
1109 .and_then(|file_name| file_name.to_str())
1110 {
1111 ren_doc.set_text(text, rq, context);
1112 }
1113 rq.add(RenderData::new(
1114 ren_doc.id(),
1115 *ren_doc.rect(),
1116 UpdateMode::Gui,
1117 ));
1118 hub.send(Event::Focus(Some(ViewId::RenameDocumentInput)))
1119 .ok();
1120 self.children.push(Box::new(ren_doc) as Box<dyn View>);
1121 }
1122 }
1123
1124 fn toggle_go_to_page(
1125 &mut self,
1126 enable: Option<bool>,
1127 hub: &Hub,
1128 rq: &mut RenderQueue,
1129 context: &mut Context,
1130 ) {
1131 if let Some(index) = locate_by_id(self, ViewId::GoToPage) {
1132 if let Some(true) = enable {
1133 return;
1134 }
1135 rq.add(RenderData::expose(
1136 *self.child(index).rect(),
1137 UpdateMode::Gui,
1138 ));
1139 self.children.remove(index);
1140 if let Some(ViewId::GoToPageInput) = self.focus {
1141 self.toggle_keyboard(false, true, Some(ViewId::GoToPageInput), hub, rq, context);
1142 }
1143 } else {
1144 if let Some(false) = enable {
1145 return;
1146 }
1147 if self.pages_count < 2 {
1148 return;
1149 }
1150 let go_to_page = NamedInput::new(
1151 "Go to page".to_string(),
1152 ViewId::GoToPage,
1153 ViewId::GoToPageInput,
1154 4,
1155 context,
1156 );
1157 rq.add(RenderData::new(
1158 go_to_page.id(),
1159 *go_to_page.rect(),
1160 UpdateMode::Gui,
1161 ));
1162 hub.send(Event::Focus(Some(ViewId::GoToPageInput))).ok();
1163 self.children.push(Box::new(go_to_page) as Box<dyn View>);
1164 }
1165 }
1166
1167 fn toggle_sort_menu(
1168 &mut self,
1169 rect: Rectangle,
1170 enable: Option<bool>,
1171 rq: &mut RenderQueue,
1172 context: &mut Context,
1173 ) {
1174 if let Some(index) = locate_by_id(self, ViewId::SortMenu) {
1175 if let Some(true) = enable {
1176 return;
1177 }
1178 rq.add(RenderData::expose(
1179 *self.child(index).rect(),
1180 UpdateMode::Gui,
1181 ));
1182 self.children.remove(index);
1183 } else {
1184 if let Some(false) = enable {
1185 return;
1186 }
1187 let entries = vec![
1188 EntryKind::RadioButton(
1189 "Date Opened".to_string(),
1190 EntryId::Sort(SortMethod::Opened),
1191 self.sort_method == SortMethod::Opened,
1192 ),
1193 EntryKind::RadioButton(
1194 "Date Added".to_string(),
1195 EntryId::Sort(SortMethod::Added),
1196 self.sort_method == SortMethod::Added,
1197 ),
1198 EntryKind::RadioButton(
1199 "Status".to_string(),
1200 EntryId::Sort(SortMethod::Status),
1201 self.sort_method == SortMethod::Status,
1202 ),
1203 EntryKind::RadioButton(
1204 "Progress".to_string(),
1205 EntryId::Sort(SortMethod::Progress),
1206 self.sort_method == SortMethod::Progress,
1207 ),
1208 EntryKind::RadioButton(
1209 "Author".to_string(),
1210 EntryId::Sort(SortMethod::Author),
1211 self.sort_method == SortMethod::Author,
1212 ),
1213 EntryKind::RadioButton(
1214 "Title".to_string(),
1215 EntryId::Sort(SortMethod::Title),
1216 self.sort_method == SortMethod::Title,
1217 ),
1218 EntryKind::RadioButton(
1219 "Year".to_string(),
1220 EntryId::Sort(SortMethod::Year),
1221 self.sort_method == SortMethod::Year,
1222 ),
1223 EntryKind::RadioButton(
1224 "Series".to_string(),
1225 EntryId::Sort(SortMethod::Series),
1226 self.sort_method == SortMethod::Series,
1227 ),
1228 EntryKind::RadioButton(
1229 "File Size".to_string(),
1230 EntryId::Sort(SortMethod::Size),
1231 self.sort_method == SortMethod::Size,
1232 ),
1233 EntryKind::RadioButton(
1234 "File Type".to_string(),
1235 EntryId::Sort(SortMethod::Kind),
1236 self.sort_method == SortMethod::Kind,
1237 ),
1238 EntryKind::RadioButton(
1239 "File Name".to_string(),
1240 EntryId::Sort(SortMethod::FileName),
1241 self.sort_method == SortMethod::FileName,
1242 ),
1243 EntryKind::RadioButton(
1244 "File Path".to_string(),
1245 EntryId::Sort(SortMethod::FilePath),
1246 self.sort_method == SortMethod::FilePath,
1247 ),
1248 EntryKind::Separator,
1249 EntryKind::CheckBox(
1250 "Reverse Order".to_string(),
1251 EntryId::ReverseOrder,
1252 self.reverse_order,
1253 ),
1254 ];
1255 let sort_menu = Menu::new(rect, ViewId::SortMenu, MenuKind::DropDown, entries, context);
1256 rq.add(RenderData::new(
1257 sort_menu.id(),
1258 *sort_menu.rect(),
1259 UpdateMode::Gui,
1260 ));
1261 self.children.push(Box::new(sort_menu) as Box<dyn View>);
1262 }
1263 }
1264
1265 fn book_index(&self, index: usize) -> usize {
1266 let max_lines = self
1267 .child(self.shelf_index)
1268 .downcast_ref::<Shelf>()
1269 .unwrap()
1270 .max_lines;
1271 let index_lower = self.current_page * max_lines;
1272 (index_lower + index).min(self.visible_books.len())
1273 }
1274
1275 fn toggle_book_menu(
1276 &mut self,
1277 index: usize,
1278 rect: Rectangle,
1279 enable: Option<bool>,
1280 rq: &mut RenderQueue,
1281 context: &mut Context,
1282 ) {
1283 if let Some(index) = locate_by_id(self, ViewId::BookMenu) {
1284 if let Some(true) = enable {
1285 return;
1286 }
1287 rq.add(RenderData::expose(
1288 *self.child(index).rect(),
1289 UpdateMode::Gui,
1290 ));
1291 self.children.remove(index);
1292 } else {
1293 if let Some(false) = enable {
1294 return;
1295 }
1296
1297 let book_index = self.book_index(index);
1298 let info = &self.visible_books[book_index];
1299 let path = &info.file.path;
1300
1301 let mut entries = Vec::new();
1302
1303 if let Some(parent) = path.parent() {
1304 entries.push(EntryKind::Command(
1305 "Select Parent".to_string(),
1306 EntryId::SelectDirectory(context.library.home.join(parent)),
1307 ));
1308 }
1309
1310 if !info.author.is_empty() {
1311 entries.push(EntryKind::Command(
1312 "Search Author".to_string(),
1313 EntryId::SearchAuthor(info.author.clone()),
1314 ));
1315 }
1316
1317 if !entries.is_empty() {
1318 entries.push(EntryKind::Separator);
1319 }
1320
1321 let submenu: &[SimpleStatus] = match info.simple_status() {
1322 SimpleStatus::New => &[SimpleStatus::Reading, SimpleStatus::Finished],
1323 SimpleStatus::Reading => &[SimpleStatus::New, SimpleStatus::Finished],
1324 SimpleStatus::Finished => &[SimpleStatus::New, SimpleStatus::Reading],
1325 };
1326
1327 let submenu = submenu
1328 .iter()
1329 .map(|s| EntryKind::Command(s.to_string(), EntryId::SetStatus(path.clone(), *s)))
1330 .collect();
1331 entries.push(EntryKind::SubMenu("Mark As".to_string(), submenu));
1332 entries.push(EntryKind::Separator);
1333
1334 let selected_library = context.settings.selected_library;
1335 let libraries = context
1336 .settings
1337 .libraries
1338 .iter()
1339 .enumerate()
1340 .filter(|(index, _)| *index != selected_library)
1341 .map(|(index, lib)| (index, lib.name.clone()))
1342 .collect::<Vec<(usize, String)>>();
1343 if !libraries.is_empty() {
1344 let copy_to = libraries
1345 .iter()
1346 .map(|(index, name)| {
1347 EntryKind::Command(name.clone(), EntryId::CopyTo(path.clone(), *index))
1348 })
1349 .collect::<Vec<EntryKind>>();
1350 let move_to = libraries
1351 .iter()
1352 .map(|(index, name)| {
1353 EntryKind::Command(name.clone(), EntryId::MoveTo(path.clone(), *index))
1354 })
1355 .collect::<Vec<EntryKind>>();
1356 entries.push(EntryKind::SubMenu("Copy To".to_string(), copy_to));
1357 entries.push(EntryKind::SubMenu("Move To".to_string(), move_to));
1358 }
1359
1360 entries.push(EntryKind::Command(
1361 "Rename".to_string(),
1362 EntryId::Rename(path.clone()),
1363 ));
1364 entries.push(EntryKind::Command(
1365 "Remove".to_string(),
1366 EntryId::Remove(path.clone()),
1367 ));
1368
1369 let book_menu = Menu::new(
1370 rect,
1371 ViewId::BookMenu,
1372 MenuKind::Contextual,
1373 entries,
1374 context,
1375 );
1376 rq.add(RenderData::new(
1377 book_menu.id(),
1378 *book_menu.rect(),
1379 UpdateMode::Gui,
1380 ));
1381 self.children.push(Box::new(book_menu) as Box<dyn View>);
1382 }
1383 }
1384
1385 fn toggle_library_menu(
1386 &mut self,
1387 rect: Rectangle,
1388 enable: Option<bool>,
1389 rq: &mut RenderQueue,
1390 context: &mut Context,
1391 ) {
1392 if let Some(index) = locate_by_id(self, ViewId::LibraryMenu) {
1393 if let Some(true) = enable {
1394 return;
1395 }
1396
1397 rq.add(RenderData::expose(
1398 *self.child(index).rect(),
1399 UpdateMode::Gui,
1400 ));
1401 self.children.remove(index);
1402 } else {
1403 if let Some(false) = enable {
1404 return;
1405 }
1406
1407 let selected_library = context.settings.selected_library;
1408 let library_settings = &context.settings.libraries[selected_library];
1409
1410 let libraries: Vec<EntryKind> = context
1411 .settings
1412 .libraries
1413 .iter()
1414 .enumerate()
1415 .map(|(index, lib)| {
1416 EntryKind::RadioButton(
1417 lib.name.clone(),
1418 EntryId::LoadLibrary(index),
1419 index == selected_library,
1420 )
1421 })
1422 .collect();
1423
1424 let database = if library_settings.mode == LibraryMode::Database {
1425 vec![
1426 EntryKind::Command("Import".to_string(), EntryId::Import),
1427 EntryKind::Command("Flush".to_string(), EntryId::Flush),
1428 ]
1429 } else {
1430 Vec::new()
1431 };
1432
1433 let filesystem = if library_settings.mode == LibraryMode::Filesystem {
1434 vec![
1435 EntryKind::CheckBox(
1436 "Show Hidden".to_string(),
1437 EntryId::ToggleShowHidden,
1438 context.library.show_hidden,
1439 ),
1440 EntryKind::Separator,
1441 EntryKind::Command("Clean Up".to_string(), EntryId::CleanUp),
1442 EntryKind::Command("Flush".to_string(), EntryId::Flush),
1443 ]
1444 } else {
1445 Vec::new()
1446 };
1447
1448 let mut entries = vec![EntryKind::SubMenu("Library".to_string(), libraries)];
1449
1450 if !database.is_empty() {
1451 entries.push(EntryKind::SubMenu("Database".to_string(), database));
1452 }
1453
1454 if !filesystem.is_empty() {
1455 entries.push(EntryKind::SubMenu("Filesystem".to_string(), filesystem));
1456 }
1457
1458 let hooks: Vec<EntryKind> = context.settings.libraries[selected_library]
1459 .hooks
1460 .iter()
1461 .map(|v| {
1462 EntryKind::Command(
1463 v.path.to_string_lossy().into_owned(),
1464 EntryId::ToggleSelectDirectory(context.library.home.join(&v.path)),
1465 )
1466 })
1467 .collect();
1468
1469 if !hooks.is_empty() {
1470 entries.push(EntryKind::SubMenu("Toggle Select".to_string(), hooks));
1471 }
1472
1473 entries.push(EntryKind::Separator);
1474
1475 let first_column = library_settings.first_column;
1476 entries.push(EntryKind::SubMenu(
1477 "First Column".to_string(),
1478 vec![
1479 EntryKind::RadioButton(
1480 "Title and Author".to_string(),
1481 EntryId::FirstColumn(FirstColumn::TitleAndAuthor),
1482 first_column == FirstColumn::TitleAndAuthor,
1483 ),
1484 EntryKind::RadioButton(
1485 "File Name".to_string(),
1486 EntryId::FirstColumn(FirstColumn::FileName),
1487 first_column == FirstColumn::FileName,
1488 ),
1489 ],
1490 ));
1491
1492 let second_column = library_settings.second_column;
1493 entries.push(EntryKind::SubMenu(
1494 "Second Column".to_string(),
1495 vec![
1496 EntryKind::RadioButton(
1497 "Progress".to_string(),
1498 EntryId::SecondColumn(SecondColumn::Progress),
1499 second_column == SecondColumn::Progress,
1500 ),
1501 EntryKind::RadioButton(
1502 "Year".to_string(),
1503 EntryId::SecondColumn(SecondColumn::Year),
1504 second_column == SecondColumn::Year,
1505 ),
1506 ],
1507 ));
1508
1509 entries.push(EntryKind::CheckBox(
1510 "Thumbnail Previews".to_string(),
1511 EntryId::ThumbnailPreviews,
1512 library_settings.thumbnail_previews,
1513 ));
1514
1515 let trash_path = context.library.home.join(TRASH_DIRNAME);
1516 if let Ok(trash) = Library::new(trash_path, LibraryMode::Database)
1517 .map_err(|e| error!("Can't inspect trash: {:#?}.", e))
1518 {
1519 if trash.is_empty() == Some(false) {
1520 entries.push(EntryKind::Separator);
1521 entries.push(EntryKind::Command(
1522 "Empty Trash".to_string(),
1523 EntryId::EmptyTrash,
1524 ));
1525 }
1526 }
1527
1528 let library_menu = Menu::new(
1529 rect,
1530 ViewId::LibraryMenu,
1531 MenuKind::DropDown,
1532 entries,
1533 context,
1534 );
1535 rq.add(RenderData::new(
1536 library_menu.id(),
1537 *library_menu.rect(),
1538 UpdateMode::Gui,
1539 ));
1540 self.children.push(Box::new(library_menu) as Box<dyn View>);
1541 }
1542 }
1543
1544 fn add_document(&mut self, info: Info, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1545 context.library.add_document(info);
1546 self.sort(false, hub, rq, context);
1547 self.refresh_visibles(true, false, hub, rq, context);
1548 }
1549
1550 fn set_status(
1551 &mut self,
1552 path: &Path,
1553 status: SimpleStatus,
1554 hub: &Hub,
1555 rq: &mut RenderQueue,
1556 context: &mut Context,
1557 ) {
1558 context.library.set_status(path, status);
1559
1560 if self.sort_method.is_status_related() {
1562 self.sort(false, hub, rq, context);
1563 }
1564
1565 self.refresh_visibles(true, false, hub, rq, context);
1566 }
1567
1568 fn empty_trash(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1569 let trash_path = context.library.home.join(TRASH_DIRNAME);
1570
1571 let trash = Library::new(trash_path, LibraryMode::Database)
1572 .map_err(|e| error!("Can't load trash: {:#}.", e));
1573 if trash.is_err() {
1574 return;
1575 }
1576
1577 let mut trash = trash.unwrap();
1578
1579 let (files, _) = trash.list(&trash.home, None, false);
1580 if files.is_empty() {
1581 return;
1582 }
1583
1584 let mut count = 0;
1585 for info in files {
1586 match trash.remove(&info.file.path) {
1587 Err(e) => error!("Can't erase {}: {:#}.", info.file.path.display(), e),
1588 Ok(()) => count += 1,
1589 }
1590 }
1591 trash.flush();
1592 let message = format!(
1593 "Removed {} book{}.",
1594 count,
1595 if count != 1 { "s" } else { "" }
1596 );
1597 let notif = Notification::new(None, message, false, hub, rq, context);
1598 self.children.push(Box::new(notif) as Box<dyn View>);
1599 }
1600
1601 fn rename(
1602 &mut self,
1603 path: &Path,
1604 file_name: &str,
1605 hub: &Hub,
1606 rq: &mut RenderQueue,
1607 context: &mut Context,
1608 ) -> Result<(), Error> {
1609 context.library.rename(path, file_name)?;
1610 self.refresh_visibles(true, false, hub, rq, context);
1611 Ok(())
1612 }
1613
1614 fn remove(
1615 &mut self,
1616 path: &Path,
1617 hub: &Hub,
1618 rq: &mut RenderQueue,
1619 context: &mut Context,
1620 ) -> Result<(), Error> {
1621 let full_path = context.library.home.join(path);
1622 if full_path.exists() {
1623 let trash_path = context.library.home.join(TRASH_DIRNAME);
1624 if !trash_path.is_dir() {
1625 fs::create_dir(&trash_path)?;
1626 }
1627 let mut trash = Library::new(trash_path, LibraryMode::Database)?;
1628 context.library.move_to(path, &mut trash)?;
1629 let (mut files, _) = trash.list(&trash.home, None, false);
1630 let mut size = files.iter().map(|info| info.file.size).sum::<u64>();
1631 if size > context.settings.home.max_trash_size {
1632 sort(&mut files, SortMethod::Added, true);
1633 while size > context.settings.home.max_trash_size {
1634 let info = files.pop().unwrap();
1635 if let Err(e) = trash.remove(&info.file.path) {
1636 error!("Can't erase {}: {:#}", info.file.path.display(), e);
1637 break;
1638 }
1639 size -= info.file.size;
1640 }
1641 }
1642 trash.flush();
1643 } else {
1644 context.library.remove(path)?;
1645 }
1646 self.refresh_visibles(true, false, hub, rq, context);
1647 Ok(())
1648 }
1649
1650 fn copy_to(&mut self, path: &Path, index: usize, context: &mut Context) -> Result<(), Error> {
1651 let library_settings = &context.settings.libraries[index];
1652 let mut library = Library::new(&library_settings.path, library_settings.mode)?;
1653 context.library.copy_to(path, &mut library)?;
1654 library.flush();
1655 Ok(())
1656 }
1657
1658 fn move_to(
1659 &mut self,
1660 path: &Path,
1661 index: usize,
1662 hub: &Hub,
1663 rq: &mut RenderQueue,
1664 context: &mut Context,
1665 ) -> Result<(), Error> {
1666 let library_settings = &context.settings.libraries[index];
1667 let mut library = Library::new(&library_settings.path, library_settings.mode)?;
1668 context.library.move_to(path, &mut library)?;
1669 library.flush();
1670 self.refresh_visibles(true, false, hub, rq, context);
1671 Ok(())
1672 }
1673
1674 fn set_reverse_order(
1675 &mut self,
1676 value: bool,
1677 hub: &Hub,
1678 rq: &mut RenderQueue,
1679 context: &mut Context,
1680 ) {
1681 self.reverse_order = value;
1682 self.current_page = 0;
1683 self.sort(true, hub, rq, context);
1684 }
1685
1686 fn set_sort_method(
1687 &mut self,
1688 sort_method: SortMethod,
1689 hub: &Hub,
1690 rq: &mut RenderQueue,
1691 context: &mut Context,
1692 ) {
1693 self.sort_method = sort_method;
1694 self.reverse_order = sort_method.reverse_order();
1695
1696 if let Some(index) = locate_by_id(self, ViewId::SortMenu) {
1697 self.child_mut(index)
1698 .children_mut()
1699 .last_mut()
1700 .unwrap()
1701 .downcast_mut::<MenuEntry>()
1702 .unwrap()
1703 .update(sort_method.reverse_order(), rq);
1704 }
1705
1706 self.current_page = 0;
1707 self.sort(true, hub, rq, context);
1708 }
1709
1710 fn sort(&mut self, update: bool, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1711 context.library.sort(self.sort_method, self.reverse_order);
1712 sort(
1713 &mut self.visible_books,
1714 self.sort_method,
1715 self.reverse_order,
1716 );
1717
1718 if update {
1719 self.update_shelf(false, hub, rq, context);
1720 let search_visible = rlocate::<SearchBar>(self).is_some();
1721 self.update_top_bar(search_visible, rq);
1722 self.update_bottom_bar(rq, context);
1723 }
1724 }
1725
1726 fn load_library(
1727 &mut self,
1728 index: usize,
1729 hub: &Hub,
1730 rq: &mut RenderQueue,
1731 context: &mut Context,
1732 ) {
1733 if index == context.settings.selected_library {
1734 return;
1735 }
1736
1737 let library_settings = context.settings.libraries[index].clone();
1738 let library = Library::new(&library_settings.path, library_settings.mode)
1739 .map_err(|e| error!("Can't load library: {:#}.", e));
1740
1741 if library.is_err() {
1742 return;
1743 }
1744
1745 let library = library.unwrap();
1746
1747 let old_path = mem::take(&mut self.current_directory);
1748 self.terminate_fetchers(&old_path, false, hub, context);
1749
1750 let mut update_top_bar = false;
1751
1752 if self.query.is_some() {
1753 self.toggle_search_bar(Some(false), false, hub, rq, context);
1754 update_top_bar = true;
1755 }
1756
1757 context.library.flush();
1758
1759 context.library = library;
1760 context.settings.selected_library = index;
1761
1762 if self.sort_method != library_settings.sort_method {
1763 self.sort_method = library_settings.sort_method;
1764 self.reverse_order = library_settings.sort_method.reverse_order();
1765 update_top_bar = true;
1766 }
1767
1768 context.library.sort(self.sort_method, self.reverse_order);
1769
1770 if update_top_bar {
1771 let search_visible = rlocate::<SearchBar>(self).is_some();
1772 self.update_top_bar(search_visible, rq);
1773 }
1774
1775 if let Some(shelf) = self.children[self.shelf_index]
1776 .as_mut()
1777 .downcast_mut::<Shelf>()
1778 {
1779 shelf.set_first_column(library_settings.first_column);
1780 shelf.set_second_column(library_settings.second_column);
1781 shelf.set_thumbnail_previews(library_settings.thumbnail_previews);
1782 }
1783
1784 let home = context.library.home.clone();
1785 self.select_directory(&home, hub, rq, context);
1786 }
1787
1788 fn import(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1789 context.library.import(&context.settings.import);
1790 context.library.sort(self.sort_method, self.reverse_order);
1791 self.refresh_visibles(true, false, hub, rq, context);
1792 }
1793
1794 fn clean_up(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1795 context.library.clean_up();
1796 self.refresh_visibles(true, false, hub, rq, context);
1797 }
1798
1799 fn flush(&mut self, context: &mut Context) {
1800 context.library.flush();
1801 }
1802
1803 fn terminate_fetchers(&mut self, path: &Path, update: bool, hub: &Hub, context: &mut Context) {
1804 self.background_fetchers.retain(|id, fetcher| {
1805 if fetcher.full_path == path {
1806 unsafe { libc::kill(*id as libc::pid_t, libc::SIGTERM) };
1807 fetcher.process.wait().ok();
1808 if update {
1809 if let Some(sort_method) = fetcher.sort_method {
1810 hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1811 }
1812 if let Some(first_column) = fetcher.first_column {
1813 hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1814 .ok();
1815 }
1816 if let Some(second_column) = fetcher.second_column {
1817 hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1818 .ok();
1819 }
1820 } else {
1821 let selected_library = context.settings.selected_library;
1822 if let Some(sort_method) = fetcher.sort_method {
1823 context.settings.libraries[selected_library].sort_method = sort_method;
1824 }
1825 if let Some(first_column) = fetcher.first_column {
1826 context.settings.libraries[selected_library].first_column = first_column;
1827 }
1828 if let Some(second_column) = fetcher.second_column {
1829 context.settings.libraries[selected_library].second_column = second_column;
1830 }
1831 }
1832 false
1833 } else {
1834 true
1835 }
1836 });
1837 }
1838
1839 fn insert_fetcher(&mut self, hook: &Hook, hub: &Hub, context: &Context) {
1840 let library_path = &context.library.home;
1841 let save_path = context.library.home.join(&hook.path);
1842 match self.spawn_child(
1843 library_path,
1844 &save_path,
1845 &hook.program,
1846 context.settings.wifi,
1847 context.online,
1848 hub,
1849 ) {
1850 Ok(process) => {
1851 let mut sort_method = hook.sort_method;
1852 let mut first_column = hook.first_column;
1853 let mut second_column = hook.second_column;
1854 if let Some(sort_method) = sort_method.replace(self.sort_method) {
1855 hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1856 }
1857 let selected_library = context.settings.selected_library;
1858 if let Some(first_column) =
1859 first_column.replace(context.settings.libraries[selected_library].first_column)
1860 {
1861 hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1862 .ok();
1863 }
1864 if let Some(second_column) = second_column
1865 .replace(context.settings.libraries[selected_library].second_column)
1866 {
1867 hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1868 .ok();
1869 }
1870 self.background_fetchers.insert(
1871 process.id(),
1872 Fetcher {
1873 path: hook.path.clone(),
1874 full_path: save_path,
1875 process,
1876 sort_method,
1877 first_column,
1878 second_column,
1879 },
1880 );
1881 }
1882 Err(e) => error!("Can't spawn child: {:#}.", e),
1883 }
1884 }
1885
1886 fn spawn_child(
1887 &mut self,
1888 library_path: &Path,
1889 save_path: &Path,
1890 program: &Path,
1891 wifi: bool,
1892 online: bool,
1893 hub: &Hub,
1894 ) -> Result<Child, Error> {
1895 let path = program.canonicalize()?;
1896 let parent = path.parent().unwrap_or_else(|| Path::new(""));
1897 let mut process = Command::new(&path)
1898 .current_dir(parent)
1899 .arg(library_path)
1900 .arg(save_path)
1901 .arg(wifi.to_string())
1902 .arg(online.to_string())
1903 .stdin(Stdio::piped())
1904 .stdout(Stdio::piped())
1905 .spawn()?;
1906 let stdout = process
1907 .stdout
1908 .take()
1909 .ok_or_else(|| format_err!("can't take stdout"))?;
1910 let id = process.id();
1911 let hub2 = hub.clone();
1912 thread::spawn(move || {
1913 let reader = BufReader::new(stdout);
1914 for line_res in reader.lines() {
1915 if let Ok(line) = line_res {
1916 if let Ok(event) = serde_json::from_str::<JsonValue>(&line) {
1917 match event.get("type").and_then(JsonValue::as_str) {
1918 Some("notify") => {
1919 if let Some(msg) = event.get("message").and_then(JsonValue::as_str)
1920 {
1921 hub2.send(Event::Notify(msg.to_string())).ok();
1922 }
1923 }
1924 Some("setWifi") => {
1925 if let Some(enable) =
1926 event.get("enable").and_then(JsonValue::as_bool)
1927 {
1928 hub2.send(Event::SetWifi(enable)).ok();
1929 }
1930 }
1931 Some("addDocument") => {
1932 if let Some(info) = event
1933 .get("info")
1934 .map(ToString::to_string)
1935 .and_then(|v| serde_json::from_str(&v).ok())
1936 {
1937 hub2.send(Event::FetcherAddDocument(id, Box::new(info)))
1938 .ok();
1939 }
1940 }
1941 Some("removeDocument") => {
1942 if let Some(path) = event.get("path").and_then(JsonValue::as_str) {
1943 hub2.send(Event::FetcherRemoveDocument(
1944 id,
1945 PathBuf::from(path),
1946 ))
1947 .ok();
1948 }
1949 }
1950 Some("search") => {
1951 let path = event
1952 .get("path")
1953 .and_then(JsonValue::as_str)
1954 .map(PathBuf::from);
1955 let query = event
1956 .get("query")
1957 .and_then(JsonValue::as_str)
1958 .map(String::from);
1959 let sort_by = event
1960 .get("sortBy")
1961 .map(ToString::to_string)
1962 .and_then(|v| serde_json::from_str(&v).ok());
1963 hub2.send(Event::FetcherSearch {
1964 id,
1965 path,
1966 query,
1967 sort_by,
1968 })
1969 .ok();
1970 }
1971 _ => (),
1972 }
1973 }
1974 } else {
1975 break;
1976 }
1977 }
1978 hub2.send(Event::CheckFetcher(id)).ok();
1979 });
1980 Ok(process)
1981 }
1982
1983 fn reseed(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1984 context.library.sort(self.sort_method, self.reverse_order);
1985 self.refresh_visibles(true, false, hub, &mut RenderQueue::new(), context);
1986
1987 if let Some(top_bar) = self.child_mut(0).downcast_mut::<TopBar>() {
1988 top_bar.reseed(&mut RenderQueue::new(), context);
1989 }
1990
1991 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
1992 }
1993}
1994
1995impl View for Home {
1996 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
1997 fn handle_event(
1998 &mut self,
1999 evt: &Event,
2000 hub: &Hub,
2001 _bus: &mut Bus,
2002 rq: &mut RenderQueue,
2003 context: &mut Context,
2004 ) -> bool {
2005 match *evt {
2006 Event::Gesture(GestureEvent::Swipe {
2007 dir, start, end, ..
2008 }) => {
2009 match dir {
2010 Dir::South
2011 if self.children[0].rect().includes(start)
2012 && self.children[self.shelf_index].rect().includes(end) =>
2013 {
2014 if !context.settings.home.navigation_bar {
2015 self.toggle_navigation_bar(Some(true), true, hub, rq, context);
2016 } else if !context.settings.home.address_bar {
2017 self.toggle_address_bar(Some(true), true, hub, rq, context);
2018 }
2019 }
2020 Dir::North
2021 if self.children[self.shelf_index].rect().includes(start)
2022 && self.children[0].rect().includes(end) =>
2023 {
2024 if context.settings.home.address_bar {
2025 self.toggle_address_bar(Some(false), true, hub, rq, context);
2026 } else if context.settings.home.navigation_bar {
2027 self.toggle_navigation_bar(Some(false), true, hub, rq, context);
2028 }
2029 }
2030 _ => (),
2031 }
2032 true
2033 }
2034 Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
2035 let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
2036 let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
2037 hub.send(Event::Select(EntryId::Rotate(n))).ok();
2038 true
2039 }
2040 Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
2041 match dir {
2042 Dir::West => self.go_to_page(0, hub, rq, context),
2043 Dir::East => {
2044 let pages_count = self.pages_count;
2045 self.go_to_page(pages_count.saturating_sub(1), hub, rq, context);
2046 }
2047 Dir::North => {
2048 let path = context.library.home.clone();
2049 self.select_directory(&path, hub, rq, context);
2050 }
2051 Dir::South => self.toggle_search_bar(None, true, hub, rq, context),
2052 };
2053 true
2054 }
2055 Event::Gesture(GestureEvent::Corner { dir, .. }) => {
2056 match dir {
2057 DiagDir::NorthWest | DiagDir::SouthWest => {
2058 self.go_to_status_change(CycleDir::Previous, hub, rq, context)
2059 }
2060 DiagDir::NorthEast | DiagDir::SouthEast => {
2061 self.go_to_status_change(CycleDir::Next, hub, rq, context)
2062 }
2063 };
2064 true
2065 }
2066 Event::Focus(v) => {
2067 if self.focus != v {
2068 self.focus = v;
2069 if v.is_some() {
2070 self.toggle_keyboard(true, true, v, hub, rq, context);
2071 }
2072 }
2073 true
2074 }
2075 Event::Show(ViewId::Keyboard) => {
2076 self.toggle_keyboard(true, true, None, hub, rq, context);
2077 true
2078 }
2079 Event::Toggle(ViewId::GoToPage) => {
2080 self.toggle_go_to_page(None, hub, rq, context);
2081 true
2082 }
2083 Event::Toggle(ViewId::SearchBar) => {
2084 self.toggle_search_bar(None, true, hub, rq, context);
2085 true
2086 }
2087 Event::ToggleNear(ViewId::TitleMenu, rect) => {
2088 self.toggle_sort_menu(rect, None, rq, context);
2089 true
2090 }
2091 Event::ToggleBookMenu(rect, index) => {
2092 self.toggle_book_menu(index, rect, None, rq, context);
2093 true
2094 }
2095 Event::ToggleNear(ViewId::MainMenu, rect) => {
2096 toggle_main_menu(self, rect, None, rq, context);
2097 true
2098 }
2099 Event::ToggleNear(ViewId::BatteryMenu, rect) => {
2100 toggle_battery_menu(self, rect, None, rq, context);
2101 true
2102 }
2103 Event::ToggleNear(ViewId::ClockMenu, rect) => {
2104 toggle_clock_menu(self, rect, None, rq, context);
2105 true
2106 }
2107 Event::ToggleNear(ViewId::LibraryMenu, rect) => {
2108 self.toggle_library_menu(rect, None, rq, context);
2109 true
2110 }
2111 Event::Close(ViewId::AddressBar) => {
2112 self.toggle_address_bar(Some(false), true, hub, rq, context);
2113 true
2114 }
2115 Event::Close(ViewId::SearchBar) => {
2116 self.toggle_search_bar(Some(false), true, hub, rq, context);
2117 true
2118 }
2119 Event::Close(ViewId::SortMenu) => {
2120 self.toggle_sort_menu(Rectangle::default(), Some(false), rq, context);
2121 true
2122 }
2123 Event::Close(ViewId::LibraryMenu) => {
2124 self.toggle_library_menu(Rectangle::default(), Some(false), rq, context);
2125 true
2126 }
2127 Event::Close(ViewId::MainMenu) => {
2128 toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
2129 true
2130 }
2131 Event::Close(ViewId::GoToPage) => {
2132 self.toggle_go_to_page(Some(false), hub, rq, context);
2133 true
2134 }
2135 Event::Close(ViewId::RenameDocument) => {
2136 self.toggle_rename_document(Some(false), hub, rq, context);
2137 true
2138 }
2139 Event::Select(EntryId::Sort(sort_method)) => {
2140 let selected_library = context.settings.selected_library;
2141 context.settings.libraries[selected_library].sort_method = sort_method;
2142 self.set_sort_method(sort_method, hub, rq, context);
2143 true
2144 }
2145 Event::Select(EntryId::ReverseOrder) => {
2146 let next_value = !self.reverse_order;
2147 self.set_reverse_order(next_value, hub, rq, context);
2148 true
2149 }
2150 Event::Select(EntryId::LoadLibrary(index)) => {
2151 self.load_library(index, hub, rq, context);
2152 true
2153 }
2154 Event::Select(EntryId::Import) => {
2155 self.import(hub, rq, context);
2156 true
2157 }
2158 Event::Select(EntryId::CleanUp) => {
2159 self.clean_up(hub, rq, context);
2160 true
2161 }
2162 Event::Select(EntryId::Flush) => {
2163 self.flush(context);
2164 true
2165 }
2166 Event::FetcherAddDocument(_, ref info) => {
2167 self.add_document(*info.clone(), hub, rq, context);
2168 true
2169 }
2170 Event::Select(EntryId::SetStatus(ref path, status)) => {
2171 self.set_status(path, status, hub, rq, context);
2172 true
2173 }
2174 Event::Select(EntryId::FirstColumn(first_column)) => {
2175 let selected_library = context.settings.selected_library;
2176 context.settings.libraries[selected_library].first_column = first_column;
2177 self.update_first_column(hub, rq, context);
2178 true
2179 }
2180 Event::Select(EntryId::SecondColumn(second_column)) => {
2181 let selected_library = context.settings.selected_library;
2182 context.settings.libraries[selected_library].second_column = second_column;
2183 self.update_second_column(hub, rq, context);
2184 true
2185 }
2186 Event::Select(EntryId::ThumbnailPreviews) => {
2187 let selected_library = context.settings.selected_library;
2188 context.settings.libraries[selected_library].thumbnail_previews =
2189 !context.settings.libraries[selected_library].thumbnail_previews;
2190 self.update_thumbnail_previews(hub, rq, context);
2191 true
2192 }
2193 Event::Submit(ViewId::AddressBarInput, ref addr) => {
2194 self.toggle_keyboard(false, true, None, hub, rq, context);
2195 self.select_directory(Path::new(addr), hub, rq, context);
2196 true
2197 }
2198 Event::Submit(ViewId::HomeSearchInput, ref text) => {
2199 self.query = BookQuery::new(text);
2200 if self.query.is_some() {
2201 self.toggle_keyboard(false, false, None, hub, rq, context);
2202 for i in self.shelf_index + 1..=self.shelf_index + 2 {
2204 rq.add(RenderData::new(
2205 self.child(i).id(),
2206 *self.child(i).rect(),
2207 UpdateMode::Gui,
2208 ));
2209 }
2210 self.refresh_visibles(true, true, hub, rq, context);
2211 } else {
2212 let notif = Notification::new(
2213 None,
2214 "Invalid search query.".to_string(),
2215 false,
2216 hub,
2217 rq,
2218 context,
2219 );
2220 self.children.push(Box::new(notif) as Box<dyn View>);
2221 }
2222 true
2223 }
2224 Event::Submit(ViewId::GoToPageInput, ref text) => {
2225 if text == "(" {
2226 self.go_to_page(0, hub, rq, context);
2227 } else if text == ")" {
2228 self.go_to_page(self.pages_count.saturating_sub(1), hub, rq, context);
2229 } else if text == "_" {
2230 let index = (context.rng.next_u64() % self.pages_count as u64) as usize;
2231 self.go_to_page(index, hub, rq, context);
2232 } else if let Ok(index) = text.parse::<usize>() {
2233 self.go_to_page(index.saturating_sub(1), hub, rq, context);
2234 }
2235 true
2236 }
2237 Event::Submit(ViewId::RenameDocumentInput, ref file_name) => {
2238 if let Some(ref path) = self.target_document.take() {
2239 self.rename(path, file_name, hub, rq, context)
2240 .map_err(|e| error!("Can't rename document: {:#}.", e))
2241 .ok();
2242 }
2243 true
2244 }
2245 Event::NavigationBarResized(_) => {
2246 self.adjust_shelf_top_edge();
2247 self.update_shelf(true, hub, rq, context);
2248 self.update_bottom_bar(rq, context);
2249 for i in self.shelf_index - 2..=self.shelf_index - 1 {
2250 rq.add(RenderData::new(
2251 self.child(i).id(),
2252 *self.child(i).rect(),
2253 UpdateMode::Gui,
2254 ));
2255 }
2256 true
2257 }
2258 Event::Select(EntryId::EmptyTrash) => {
2259 self.empty_trash(hub, rq, context);
2260 true
2261 }
2262 Event::Select(EntryId::Rename(ref path)) => {
2263 self.target_document = Some(path.clone());
2264 self.toggle_rename_document(Some(true), hub, rq, context);
2265 true
2266 }
2267 Event::Select(EntryId::Remove(ref path))
2268 | Event::FetcherRemoveDocument(_, ref path) => {
2269 self.remove(path, hub, rq, context)
2270 .map_err(|e| error!("Can't remove document: {:#}.", e))
2271 .ok();
2272 true
2273 }
2274 Event::Select(EntryId::CopyTo(ref path, index)) => {
2275 self.copy_to(path, index, context)
2276 .map_err(|e| error!("Can't copy document: {:#}.", e))
2277 .ok();
2278 true
2279 }
2280 Event::Select(EntryId::MoveTo(ref path, index)) => {
2281 self.move_to(path, index, hub, rq, context)
2282 .map_err(|e| error!("Can't move document: {:#}.", e))
2283 .ok();
2284 true
2285 }
2286 Event::Select(EntryId::ToggleShowHidden) => {
2287 context.library.show_hidden = !context.library.show_hidden;
2288 self.refresh_visibles(true, false, hub, rq, context);
2289 true
2290 }
2291 Event::SelectDirectory(ref path)
2292 | Event::Select(EntryId::SelectDirectory(ref path)) => {
2293 self.select_directory(path, hub, rq, context);
2294 true
2295 }
2296 Event::ToggleSelectDirectory(ref path)
2297 | Event::Select(EntryId::ToggleSelectDirectory(ref path)) => {
2298 self.toggle_select_directory(path, hub, rq, context);
2299 true
2300 }
2301 Event::Select(EntryId::SearchAuthor(ref author)) => {
2302 let text = format!("'a {}", author);
2303 let query = BookQuery::new(&text);
2304 if query.is_some() {
2305 self.query = query;
2306 self.toggle_search_bar(Some(true), false, hub, rq, context);
2307 self.toggle_keyboard(false, false, None, hub, rq, context);
2308 if let Some(search_bar) =
2309 self.children[self.shelf_index + 2].downcast_mut::<SearchBar>()
2310 {
2311 search_bar.set_text(&text, rq, context);
2312 }
2313 for i in self.shelf_index + 1..=self.shelf_index + 2 {
2315 rq.add(RenderData::new(
2316 self.child(i).id(),
2317 *self.child(i).rect(),
2318 UpdateMode::Gui,
2319 ));
2320 }
2321 self.refresh_visibles(true, true, hub, rq, context);
2322 }
2323 true
2324 }
2325 Event::GoTo(location) => {
2326 self.go_to_page(location as usize, hub, rq, context);
2327 true
2328 }
2329 Event::Chapter(dir) => {
2330 let pages_count = self.pages_count;
2331 match dir {
2332 CycleDir::Previous => self.go_to_page(0, hub, rq, context),
2333 CycleDir::Next => {
2334 self.go_to_page(pages_count.saturating_sub(1), hub, rq, context)
2335 }
2336 }
2337 true
2338 }
2339 Event::Page(dir) => {
2340 self.go_to_neighbor(dir, hub, rq, context);
2341 true
2342 }
2343 Event::Device(DeviceEvent::Button {
2344 code: ButtonCode::Backward,
2345 status: ButtonStatus::Pressed,
2346 ..
2347 }) => {
2348 self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
2349 true
2350 }
2351 Event::Device(DeviceEvent::Button {
2352 code: ButtonCode::Forward,
2353 status: ButtonStatus::Pressed,
2354 ..
2355 }) => {
2356 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
2357 true
2358 }
2359 Event::Device(DeviceEvent::NetUp) => {
2360 for fetcher in self.background_fetchers.values_mut() {
2361 if let Some(stdin) = fetcher.process.stdin.as_mut() {
2362 writeln!(stdin, "{}", json!({"type": "network", "status": "up"})).ok();
2363 }
2364 }
2365 true
2366 }
2367 Event::FetcherSearch {
2368 id,
2369 ref path,
2370 ref query,
2371 ref sort_by,
2372 } => {
2373 let path = path.as_ref().unwrap_or(&context.library.home);
2374 let query = query.as_ref().and_then(|text| BookQuery::new(text));
2375 let (mut files, _) = context.library.list(path, query.as_ref(), false);
2376 if let Some((sort_method, reverse_order)) = *sort_by {
2377 sort(&mut files, sort_method, reverse_order);
2378 }
2379 for entry in &mut files {
2380 mem::swap(&mut entry.reader, &mut entry.reader_info);
2382 }
2383 if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2384 if let Some(stdin) = fetcher.process.stdin.as_mut() {
2385 writeln!(
2386 stdin,
2387 "{}",
2388 json!({"type": "search",
2389 "results": files})
2390 )
2391 .ok();
2392 }
2393 }
2394 true
2395 }
2396 Event::CheckFetcher(id) => {
2397 if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2398 if let Ok(exit_status) = fetcher.process.wait() {
2399 if !exit_status.success() {
2400 let msg = format!(
2401 "{}: abnormal process termination.",
2402 fetcher.path.display()
2403 );
2404 let notif = Notification::new(None, msg, false, hub, rq, context);
2405 self.children.push(Box::new(notif) as Box<dyn View>);
2406 }
2407 }
2408 }
2409 true
2410 }
2411 Event::ToggleFrontlight => {
2412 if let Some(index) = locate::<TopBar>(self) {
2413 self.child_mut(index)
2414 .downcast_mut::<TopBar>()
2415 .unwrap()
2416 .update_frontlight_icon(rq, context);
2417 }
2418 true
2419 }
2420 Event::Reseed => {
2421 self.reseed(hub, rq, context);
2422 true
2423 }
2424 _ => false,
2425 }
2426 }
2427
2428 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
2429 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
2430
2431 fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
2432 let dpi = CURRENT_DEVICE.dpi;
2433 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
2434 let (small_thickness, big_thickness) = halves(thickness);
2435 let (small_height, big_height) = (
2436 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
2437 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2438 );
2439
2440 self.children.retain(|child| !child.is::<Menu>());
2441
2442 let top_bar_rect = rect![
2444 rect.min.x,
2445 rect.min.y,
2446 rect.max.x,
2447 rect.min.y + small_height - small_thickness
2448 ];
2449 self.children[0].resize(top_bar_rect, hub, rq, context);
2450
2451 let separator_rect = rect![
2452 rect.min.x,
2453 rect.min.y + small_height - small_thickness,
2454 rect.max.x,
2455 rect.min.y + small_height + big_thickness
2456 ];
2457 self.children[1].resize(separator_rect, hub, rq, context);
2458
2459 let mut shelf_min_y = rect.min.y + small_height + big_thickness;
2460 let mut index = 2;
2461
2462 if context.settings.home.address_bar {
2464 self.children[index].resize(
2465 rect![
2466 rect.min.x,
2467 shelf_min_y,
2468 rect.max.x,
2469 shelf_min_y + small_height - thickness
2470 ],
2471 hub,
2472 rq,
2473 context,
2474 );
2475 shelf_min_y += small_height - thickness;
2476 index += 1;
2477
2478 self.children[index].resize(
2479 rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2480 hub,
2481 rq,
2482 context,
2483 );
2484 shelf_min_y += thickness;
2485 index += 1;
2486 }
2487
2488 if context.settings.home.navigation_bar {
2490 let count = if self.children[self.shelf_index + 2].is::<SearchBar>() {
2491 2
2492 } else {
2493 1
2494 };
2495 let nav_bar = self.children[index]
2496 .as_mut()
2497 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
2498 .unwrap();
2499 nav_bar.clear();
2500 nav_bar.resize(
2501 rect![
2502 rect.min.x,
2503 shelf_min_y,
2504 rect.max.x,
2505 shelf_min_y + small_height - thickness
2506 ],
2507 hub,
2508 rq,
2509 context,
2510 );
2511 nav_bar.vertical_limit =
2512 rect.max.y - count * small_height - big_height - small_thickness;
2513 nav_bar.set_selected(
2514 self.current_directory.clone(),
2515 &mut RenderQueue::new(),
2516 context,
2517 );
2518 shelf_min_y += nav_bar.rect().height() as i32;
2519 index += 1;
2520
2521 self.children[index].resize(
2522 rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2523 hub,
2524 rq,
2525 context,
2526 );
2527 shelf_min_y += thickness;
2528 }
2529
2530 let bottom_bar_index = rlocate::<BottomBar>(self).unwrap();
2532 index = bottom_bar_index;
2533
2534 let separator_rect = rect![
2535 rect.min.x,
2536 rect.max.y - small_height - small_thickness,
2537 rect.max.x,
2538 rect.max.y - small_height + big_thickness
2539 ];
2540 self.children[index - 1].resize(separator_rect, hub, rq, context);
2541
2542 let bottom_bar_rect = rect![
2543 rect.min.x,
2544 rect.max.y - small_height + big_thickness,
2545 rect.max.x,
2546 rect.max.y
2547 ];
2548 self.children[index].resize(bottom_bar_rect, hub, rq, context);
2549
2550 let mut shelf_max_y = rect.max.y - small_height - small_thickness;
2551
2552 if index - self.shelf_index > 2 {
2553 index -= 2;
2554 if self.children[index].is::<Keyboard>() {
2556 let kb_rect = rect![
2557 rect.min.x,
2558 rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
2559 rect.max.x,
2560 rect.max.y - small_height - small_thickness
2561 ];
2562 self.children[index].resize(kb_rect, hub, rq, context);
2563 let s_max_y = self.children[index].rect().min.y;
2564 self.children[index - 1].resize(
2565 rect![rect.min.x, s_max_y - thickness, rect.max.x, s_max_y],
2566 hub,
2567 rq,
2568 context,
2569 );
2570 index -= 2;
2571 }
2572 if self.children[index].is::<SearchBar>() {
2574 let sp_rect = *self.children[index + 1].rect() - pt!(0, small_height);
2575 self.children[index].resize(
2576 rect![
2577 rect.min.x,
2578 sp_rect.max.y,
2579 rect.max.x,
2580 sp_rect.max.y + small_height - thickness
2581 ],
2582 hub,
2583 rq,
2584 context,
2585 );
2586 self.children[index - 1].resize(sp_rect, hub, rq, context);
2587 shelf_max_y -= small_height;
2588 }
2589 }
2590
2591 let shelf_rect = rect![rect.min.x, shelf_min_y, rect.max.x, shelf_max_y];
2593 self.children[self.shelf_index].resize(shelf_rect, hub, rq, context);
2594
2595 self.update_shelf(true, hub, &mut RenderQueue::new(), context);
2596 self.update_bottom_bar(&mut RenderQueue::new(), context);
2597
2598 for i in bottom_bar_index + 1..self.children.len() {
2600 self.children[i].resize(rect, hub, rq, context);
2601 }
2602
2603 self.rect = rect;
2604 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Full));
2605 }
2606
2607 fn rect(&self) -> &Rectangle {
2608 &self.rect
2609 }
2610
2611 fn rect_mut(&mut self) -> &mut Rectangle {
2612 &mut self.rect
2613 }
2614
2615 fn children(&self) -> &Vec<Box<dyn View>> {
2616 &self.children
2617 }
2618
2619 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
2620 &mut self.children
2621 }
2622
2623 fn id(&self) -> Id {
2624 self.id
2625 }
2626}
2627
2628#[cfg(test)]
2629mod tests {
2630 use super::*;
2631 use crate::battery::{Battery, FakeBattery};
2632 use crate::framebuffer::Pixmap;
2633 use crate::frontlight::{Frontlight, LightLevels};
2634 use crate::lightsensor::LightSensor;
2635 use std::env;
2636 use std::path::Path;
2637
2638 fn create_test_context() -> Context {
2639 let fb = Box::new(Pixmap::new(600, 800, 1)) as Box<dyn Framebuffer>;
2640 let battery = Box::new(FakeBattery::new()) as Box<dyn Battery>;
2641 let frontlight = Box::new(LightLevels::default()) as Box<dyn Frontlight>;
2642 let lightsensor = Box::new(0u16) as Box<dyn LightSensor>;
2643
2644 let fonts = crate::font::Fonts::load_from(
2645 Path::new(
2646 &env::var("TEST_ROOT_DIR").expect("TEST_ROOT_DIR must be set for this test."),
2647 )
2648 .to_path_buf(),
2649 )
2650 .expect(
2651 "Failed to load fonts. Tests require font files to be present. \
2652 Run tests from the project root directory.",
2653 );
2654
2655 Context::new(
2656 fb,
2657 None,
2658 crate::library::Library::new(Path::new("/tmp"), crate::settings::LibraryMode::Database)
2659 .unwrap(),
2660 crate::settings::Settings::default(),
2661 fonts,
2662 battery,
2663 frontlight,
2664 lightsensor,
2665 )
2666 }
2667
2668 #[test]
2669 fn test_toggle_address_bar_with_navigation_bar_maintains_separator_alignment() {
2670 let mut context = create_test_context();
2671 let (tx, _rx) = std::sync::mpsc::channel();
2672 let hub = tx;
2673 let mut rq = RenderQueue::new();
2674
2675 context.settings.home.navigation_bar = false;
2676 context.settings.home.address_bar = false;
2677
2678 let rect = rect![0, 0, 600, 800];
2679 let mut home = Home::new(rect, &hub, &mut rq, &mut context).unwrap();
2680
2681 home.toggle_navigation_bar(Some(true), false, &hub, &mut rq, &mut context);
2682 assert!(context.settings.home.navigation_bar);
2683
2684 let nav_bar_index =
2685 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2686 let separator_index = home.shelf_index - 1;
2687
2688 let nav_bar_bottom_before = home.children[nav_bar_index].rect().max.y;
2689 let separator_top_before = home.children[separator_index].rect().min.y;
2690 assert_eq!(
2691 nav_bar_bottom_before, separator_top_before,
2692 "Navigation bar and separator should be aligned before toggling address bar"
2693 );
2694
2695 home.toggle_address_bar(Some(true), false, &hub, &mut rq, &mut context);
2696 assert!(context.settings.home.address_bar);
2697
2698 let nav_bar_index =
2699 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2700 let separator_index = home.shelf_index - 1;
2701
2702 let nav_bar_bottom_after_enable = home.children[nav_bar_index].rect().max.y;
2703 let separator_top_after_enable = home.children[separator_index].rect().min.y;
2704 assert_eq!(
2705 nav_bar_bottom_after_enable, separator_top_after_enable,
2706 "Navigation bar and separator should remain aligned after enabling address bar"
2707 );
2708
2709 home.toggle_address_bar(Some(false), false, &hub, &mut rq, &mut context);
2710 assert!(!context.settings.home.address_bar);
2711
2712 let nav_bar_index =
2713 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2714 let separator_index = home.shelf_index - 1;
2715
2716 let nav_bar_bottom_after_disable = home.children[nav_bar_index].rect().max.y;
2717 let separator_top_after_disable = home.children[separator_index].rect().min.y;
2718 assert_eq!(
2719 nav_bar_bottom_after_disable, separator_top_after_disable,
2720 "Navigation bar and separator should remain aligned after disabling address bar"
2721 );
2722 }
2723}