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