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