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::{CycleDir, DiagDir, Dir, Rectangle, halves};
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::StackNavigationBar;
33use crate::view::navigation::providers::directory::DirectoryNavigationProvider;
34use crate::view::notification::Notification;
35use crate::view::search_bar::SearchBar;
36use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
37use crate::view::{Bus, Event, Hub, NotificationEvent, RenderData, RenderQueue, ToggleEvent, View};
38use crate::view::{EntryId, EntryKind, ID_FEEDER, Id, ViewId};
39use anyhow::{Error, format_err};
40use fxhash::FxHashMap;
41use rand_core::Rng;
42use serde_json::{Value as JsonValue, json};
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![EntryKind::SubMenu("Library".to_string(), libraries)];
1408
1409 let hooks: Vec<EntryKind> = context.settings.libraries[selected_library]
1410 .hooks
1411 .iter()
1412 .map(|v| {
1413 EntryKind::Command(
1414 v.path.to_string_lossy().into_owned(),
1415 EntryId::ToggleSelectDirectory(context.library.home.join(&v.path)),
1416 )
1417 })
1418 .collect();
1419
1420 if !hooks.is_empty() {
1421 entries.push(EntryKind::SubMenu("Toggle Select".to_string(), hooks));
1422 }
1423
1424 entries.push(EntryKind::Separator);
1425
1426 let first_column = library_settings.first_column;
1427 entries.push(EntryKind::SubMenu(
1428 "First Column".to_string(),
1429 vec![
1430 EntryKind::RadioButton(
1431 "Title and Author".to_string(),
1432 EntryId::FirstColumn(FirstColumn::TitleAndAuthor),
1433 first_column == FirstColumn::TitleAndAuthor,
1434 ),
1435 EntryKind::RadioButton(
1436 "File Name".to_string(),
1437 EntryId::FirstColumn(FirstColumn::FileName),
1438 first_column == FirstColumn::FileName,
1439 ),
1440 ],
1441 ));
1442
1443 let second_column = library_settings.second_column;
1444 entries.push(EntryKind::SubMenu(
1445 "Second Column".to_string(),
1446 vec![
1447 EntryKind::RadioButton(
1448 "Progress".to_string(),
1449 EntryId::SecondColumn(SecondColumn::Progress),
1450 second_column == SecondColumn::Progress,
1451 ),
1452 EntryKind::RadioButton(
1453 "Year".to_string(),
1454 EntryId::SecondColumn(SecondColumn::Year),
1455 second_column == SecondColumn::Year,
1456 ),
1457 ],
1458 ));
1459
1460 entries.push(EntryKind::CheckBox(
1461 "Thumbnail Previews".to_string(),
1462 EntryId::ThumbnailPreviews,
1463 library_settings.thumbnail_previews,
1464 ));
1465
1466 let trash_path = context.library.home.join(TRASH_DIRNAME);
1467 if let Ok(trash) = Library::new(trash_path, &context.database, "Trash")
1468 .map_err(|e| error!("Can't inspect trash: {:#?}.", e))
1469 {
1470 if trash.is_empty() == Some(false) {
1471 entries.push(EntryKind::Separator);
1472 entries.push(EntryKind::Command(
1473 "Empty Trash".to_string(),
1474 EntryId::EmptyTrash,
1475 ));
1476 }
1477 }
1478
1479 let library_menu = Menu::new(
1480 rect,
1481 ViewId::LibraryMenu,
1482 MenuKind::DropDown,
1483 entries,
1484 context,
1485 );
1486 rq.add(RenderData::new(
1487 library_menu.id(),
1488 *library_menu.rect(),
1489 UpdateMode::Gui,
1490 ));
1491 self.children.push(Box::new(library_menu) as Box<dyn View>);
1492 }
1493 }
1494
1495 fn add_document(&mut self, info: Info, rq: &mut RenderQueue, context: &mut Context) {
1496 context.library.add_document(info);
1497 self.sort(false, rq, context);
1498 self.refresh_visibles(true, false, rq, context);
1499 }
1500
1501 fn set_status(
1502 &mut self,
1503 path: &Path,
1504 status: SimpleStatus,
1505 rq: &mut RenderQueue,
1506 context: &mut Context,
1507 ) {
1508 context.library.set_status(path, status);
1509
1510 if self.sort_method.is_status_related() {
1512 self.sort(false, rq, context);
1513 }
1514
1515 self.refresh_visibles(true, false, rq, context);
1516 }
1517
1518 fn empty_trash(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1519 let trash_path = context.library.home.join(TRASH_DIRNAME);
1520
1521 let trash = Library::new(trash_path, &context.database, "Trash")
1522 .map_err(|e| error!("Can't load trash: {:#}.", e));
1523 if trash.is_err() {
1524 return;
1525 }
1526
1527 let mut trash = trash.unwrap();
1528
1529 let (files, _) = trash.list(&trash.home, None, false);
1530 if files.is_empty() {
1531 return;
1532 }
1533
1534 let mut count = 0;
1535 for info in files {
1536 match trash.remove(&info.file.path) {
1537 Err(e) => error!("Can't erase {}: {:#}.", info.file.path.display(), e),
1538 Ok(()) => count += 1,
1539 }
1540 }
1541 let message = format!(
1542 "Removed {} book{}.",
1543 count,
1544 if count != 1 { "s" } else { "" }
1545 );
1546 let notif = Notification::new(None, message, false, hub, rq, context);
1547 self.children.push(Box::new(notif) as Box<dyn View>);
1548 }
1549
1550 fn rename(
1551 &mut self,
1552 path: &Path,
1553 file_name: &str,
1554 rq: &mut RenderQueue,
1555 context: &mut Context,
1556 ) -> Result<(), Error> {
1557 context.library.rename(path, file_name)?;
1558 self.refresh_visibles(true, false, rq, context);
1559 Ok(())
1560 }
1561
1562 fn remove(
1563 &mut self,
1564 path: &Path,
1565 rq: &mut RenderQueue,
1566 context: &mut Context,
1567 ) -> Result<(), Error> {
1568 let full_path = context.library.home.join(path);
1569 if full_path.exists() {
1570 let trash_path = context.library.home.join(TRASH_DIRNAME);
1571 if !trash_path.is_dir() {
1572 fs::create_dir(&trash_path)?;
1573 }
1574 let mut trash = Library::new(trash_path, &context.database, "Trash")?;
1575 trash.sort_method = SortMethod::Added;
1576 trash.reverse_order = true;
1577 context.library.move_to(path, &mut trash)?;
1578 let (mut files, _) = trash.list(&trash.home, None, false);
1579 let mut size = files.iter().map(|info| info.file.size).sum::<u64>();
1580 while size > context.settings.home.max_trash_size {
1581 let Some(info) = files.pop() else { break };
1582 if let Err(e) = trash.remove(&info.file.path) {
1583 error!("Can't erase {}: {:#}", info.file.path.display(), e);
1584 break;
1585 }
1586 size -= info.file.size;
1587 }
1588 } else {
1589 context.library.remove(path)?;
1590 }
1591 self.refresh_visibles(true, false, rq, context);
1592 Ok(())
1593 }
1594
1595 fn copy_to(&mut self, path: &Path, index: usize, context: &mut Context) -> Result<(), Error> {
1596 let library_settings = &context.settings.libraries[index];
1597 let mut library = Library::new(
1598 &library_settings.path,
1599 &context.database,
1600 &library_settings.name,
1601 )?;
1602 context.library.copy_to(path, &mut library)?;
1603 Ok(())
1604 }
1605
1606 fn move_to(
1607 &mut self,
1608 path: &Path,
1609 index: usize,
1610 rq: &mut RenderQueue,
1611 context: &mut Context,
1612 ) -> Result<(), Error> {
1613 let library_settings = &context.settings.libraries[index];
1614 let mut library = Library::new(
1615 &library_settings.path,
1616 &context.database,
1617 &library_settings.name,
1618 )?;
1619 context.library.move_to(path, &mut library)?;
1620 self.refresh_visibles(true, false, rq, context);
1621 Ok(())
1622 }
1623
1624 fn set_reverse_order(&mut self, value: bool, rq: &mut RenderQueue, context: &mut Context) {
1625 self.reverse_order = value;
1626 self.current_page = 0;
1627 self.sort(true, rq, context);
1628 }
1629
1630 fn set_sort_method(
1631 &mut self,
1632 sort_method: SortMethod,
1633 rq: &mut RenderQueue,
1634 context: &mut Context,
1635 ) {
1636 self.sort_method = sort_method;
1637 self.reverse_order = sort_method.reverse_order();
1638
1639 if let Some(index) = locate_by_id(self, ViewId::SortMenu) {
1640 self.child_mut(index)
1641 .children_mut()
1642 .last_mut()
1643 .unwrap()
1644 .downcast_mut::<MenuEntry>()
1645 .unwrap()
1646 .update(sort_method.reverse_order(), rq);
1647 }
1648
1649 self.current_page = 0;
1650 self.sort(true, rq, context);
1651 }
1652
1653 fn sort(&mut self, update: bool, rq: &mut RenderQueue, context: &mut Context) {
1654 context
1655 .library
1656 .set_sort(self.sort_method, self.reverse_order);
1657
1658 if update {
1659 self.update_shelf(false, rq, context);
1660 let search_visible = rlocate::<SearchBar>(self).is_some();
1661 self.update_top_bar(search_visible, rq);
1662 self.update_bottom_bar(rq, context);
1663 }
1664 }
1665
1666 fn load_library(
1667 &mut self,
1668 index: usize,
1669 hub: &Hub,
1670 rq: &mut RenderQueue,
1671 context: &mut Context,
1672 ) {
1673 if index == context.settings.selected_library {
1674 return;
1675 }
1676
1677 let library_settings = context.settings.libraries[index].clone();
1678 let library = Library::new(
1679 &library_settings.path,
1680 &context.database,
1681 &library_settings.name,
1682 )
1683 .map_err(|e| error!("Can't load library: {:#}.", e));
1684
1685 if library.is_err() {
1686 return;
1687 }
1688
1689 let library = library.unwrap();
1690
1691 let old_path = mem::take(&mut self.current_directory);
1692 self.terminate_fetchers(&old_path, false, hub, context);
1693
1694 let mut update_top_bar = false;
1695
1696 if self.query.is_some() {
1697 self.toggle_search_bar(Some(false), false, hub, rq, context);
1698 update_top_bar = true;
1699 }
1700
1701 context.library = library;
1702 context.settings.selected_library = index;
1703
1704 if self.sort_method != library_settings.sort_method {
1705 self.sort_method = library_settings.sort_method;
1706 self.reverse_order = library_settings.sort_method.reverse_order();
1707 update_top_bar = true;
1708 }
1709
1710 context
1711 .library
1712 .set_sort(self.sort_method, self.reverse_order);
1713
1714 if update_top_bar {
1715 let search_visible = rlocate::<SearchBar>(self).is_some();
1716 self.update_top_bar(search_visible, rq);
1717 }
1718
1719 if let Some(shelf) = self.children[self.shelf_index]
1720 .as_mut()
1721 .downcast_mut::<Shelf>()
1722 {
1723 shelf.set_first_column(library_settings.first_column);
1724 shelf.set_second_column(library_settings.second_column);
1725 shelf.set_thumbnail_previews(library_settings.thumbnail_previews);
1726 }
1727
1728 let home = context.library.home.clone();
1729
1730 if context.settings.home.navigation_bar {
1731 let nav_bar = self.children[self.shelf_index - 2]
1732 .as_mut()
1733 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
1734 .unwrap();
1735 nav_bar.provider_mut().set_root(home.clone());
1736 nav_bar.clear();
1737 }
1738
1739 self.select_directory(&home, hub, rq, context);
1740 }
1741
1742 fn clean_up(&mut self, rq: &mut RenderQueue, context: &mut Context) {
1743 context.library.clean_up();
1744 self.refresh_visibles(true, false, rq, context);
1745 }
1746
1747 fn terminate_fetchers(&mut self, path: &Path, update: bool, hub: &Hub, context: &mut Context) {
1748 self.background_fetchers.retain(|id, fetcher| {
1749 if fetcher.full_path == path {
1750 unsafe { libc::kill(*id as libc::pid_t, libc::SIGTERM) };
1751 fetcher.process.wait().ok();
1752 if update {
1753 if let Some(sort_method) = fetcher.sort_method {
1754 hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1755 }
1756 if let Some(first_column) = fetcher.first_column {
1757 hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1758 .ok();
1759 }
1760 if let Some(second_column) = fetcher.second_column {
1761 hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1762 .ok();
1763 }
1764 } else {
1765 let selected_library = context.settings.selected_library;
1766 if let Some(sort_method) = fetcher.sort_method {
1767 context.settings.libraries[selected_library].sort_method = sort_method;
1768 }
1769 if let Some(first_column) = fetcher.first_column {
1770 context.settings.libraries[selected_library].first_column = first_column;
1771 }
1772 if let Some(second_column) = fetcher.second_column {
1773 context.settings.libraries[selected_library].second_column = second_column;
1774 }
1775 }
1776 false
1777 } else {
1778 true
1779 }
1780 });
1781 }
1782
1783 fn insert_fetcher(&mut self, hook: &Hook, hub: &Hub, context: &Context) {
1784 let library_path = &context.library.home;
1785 let save_path = context.library.home.join(&hook.path);
1786 let program = CURRENT_DEVICE.install_path(&hook.program);
1787 match self.spawn_child(
1788 library_path,
1789 &save_path,
1790 &program,
1791 context.settings.wifi,
1792 context.online,
1793 hub,
1794 ) {
1795 Ok(process) => {
1796 let mut sort_method = hook.sort_method;
1797 let mut first_column = hook.first_column;
1798 let mut second_column = hook.second_column;
1799 if let Some(sort_method) = sort_method.replace(self.sort_method) {
1800 hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1801 }
1802 let selected_library = context.settings.selected_library;
1803 if let Some(first_column) =
1804 first_column.replace(context.settings.libraries[selected_library].first_column)
1805 {
1806 hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1807 .ok();
1808 }
1809 if let Some(second_column) = second_column
1810 .replace(context.settings.libraries[selected_library].second_column)
1811 {
1812 hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1813 .ok();
1814 }
1815 self.background_fetchers.insert(
1816 process.id(),
1817 Fetcher {
1818 path: hook.path.clone(),
1819 full_path: save_path,
1820 process,
1821 sort_method,
1822 first_column,
1823 second_column,
1824 },
1825 );
1826 }
1827 Err(e) => error!("Can't spawn child: {:#}.", e),
1828 }
1829 }
1830
1831 fn spawn_child(
1832 &mut self,
1833 library_path: &Path,
1834 save_path: &Path,
1835 program: &Path,
1836 wifi: bool,
1837 online: bool,
1838 hub: &Hub,
1839 ) -> Result<Child, Error> {
1840 let path = program.canonicalize()?;
1841 let parent = path.parent().unwrap_or_else(|| Path::new(""));
1842 let mut process = Command::new(&path)
1843 .current_dir(parent)
1844 .arg(library_path)
1845 .arg(save_path)
1846 .arg(wifi.to_string())
1847 .arg(online.to_string())
1848 .stdin(Stdio::piped())
1849 .stdout(Stdio::piped())
1850 .spawn()?;
1851 let stdout = process
1852 .stdout
1853 .take()
1854 .ok_or_else(|| format_err!("can't take stdout"))?;
1855 let id = process.id();
1856 let hub2 = hub.clone();
1857 thread::spawn(move || {
1858 let reader = BufReader::new(stdout);
1859 for line_res in reader.lines() {
1860 if let Ok(line) = line_res {
1861 if let Ok(event) = serde_json::from_str::<JsonValue>(&line) {
1862 match event.get("type").and_then(JsonValue::as_str) {
1863 Some("notify") => {
1864 if let Some(msg) = event.get("message").and_then(JsonValue::as_str)
1865 {
1866 hub2.send(Event::Notification(NotificationEvent::Show(
1867 msg.to_string(),
1868 )))
1869 .ok();
1870 }
1871 }
1872 Some("setWifi") => {
1873 if let Some(enable) =
1874 event.get("enable").and_then(JsonValue::as_bool)
1875 {
1876 hub2.send(Event::SetWifi(enable)).ok();
1877 }
1878 }
1879 Some("addDocument") => {
1880 if let Some(info) = event
1881 .get("info")
1882 .map(ToString::to_string)
1883 .and_then(|v| serde_json::from_str(&v).ok())
1884 {
1885 hub2.send(Event::FetcherAddDocument(id, Box::new(info)))
1886 .ok();
1887 }
1888 }
1889 Some("removeDocument") => {
1890 if let Some(path) = event.get("path").and_then(JsonValue::as_str) {
1891 hub2.send(Event::FetcherRemoveDocument(
1892 id,
1893 PathBuf::from(path),
1894 ))
1895 .ok();
1896 }
1897 }
1898 Some("search") => {
1899 let path = event
1900 .get("path")
1901 .and_then(JsonValue::as_str)
1902 .map(PathBuf::from);
1903 let query = event
1904 .get("query")
1905 .and_then(JsonValue::as_str)
1906 .map(String::from);
1907 let sort_by = event
1908 .get("sortBy")
1909 .map(ToString::to_string)
1910 .and_then(|v| serde_json::from_str(&v).ok());
1911 hub2.send(Event::FetcherSearch {
1912 id,
1913 path,
1914 query,
1915 sort_by,
1916 })
1917 .ok();
1918 }
1919 _ => (),
1920 }
1921 }
1922 } else {
1923 break;
1924 }
1925 }
1926 hub2.send(Event::CheckFetcher(id)).ok();
1927 });
1928 Ok(process)
1929 }
1930
1931 fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
1932 context
1933 .library
1934 .set_sort(self.sort_method, self.reverse_order);
1935 self.refresh_visibles(true, false, &mut RenderQueue::new(), context);
1936
1937 if let Some(top_bar) = self.child_mut(0).downcast_mut::<TopBar>() {
1938 top_bar.reseed(&mut RenderQueue::new(), context);
1939 }
1940
1941 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
1942 }
1943}
1944
1945impl View for Home {
1946 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
1947 fn handle_event(
1948 &mut self,
1949 evt: &Event,
1950 hub: &Hub,
1951 _bus: &mut Bus,
1952 rq: &mut RenderQueue,
1953 context: &mut Context,
1954 ) -> bool {
1955 match *evt {
1956 Event::Gesture(GestureEvent::Swipe {
1957 dir, start, end, ..
1958 }) => {
1959 match dir {
1960 Dir::South
1961 if self.children[0].rect().includes(start)
1962 && self.children[self.shelf_index].rect().includes(end) =>
1963 {
1964 if !context.settings.home.navigation_bar {
1965 self.toggle_navigation_bar(Some(true), true, rq, context);
1966 } else if !context.settings.home.address_bar {
1967 self.toggle_address_bar(Some(true), true, hub, rq, context);
1968 }
1969 }
1970 Dir::North
1971 if self.children[self.shelf_index].rect().includes(start)
1972 && self.children[0].rect().includes(end) =>
1973 {
1974 if context.settings.home.address_bar {
1975 self.toggle_address_bar(Some(false), true, hub, rq, context);
1976 } else if context.settings.home.navigation_bar {
1977 self.toggle_navigation_bar(Some(false), true, rq, context);
1978 }
1979 }
1980 _ => (),
1981 }
1982 true
1983 }
1984 Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
1985 let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
1986 let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
1987 hub.send(Event::Select(EntryId::Rotate(n))).ok();
1988 true
1989 }
1990 Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
1991 match dir {
1992 Dir::West => self.go_to_page(0, rq, context),
1993 Dir::East => {
1994 let pages_count = self.pages_count;
1995 self.go_to_page(pages_count.saturating_sub(1), rq, context);
1996 }
1997 Dir::North => {
1998 let path = context.library.home.clone();
1999 self.select_directory(&path, hub, rq, context);
2000 }
2001 Dir::South => self.toggle_search_bar(None, true, hub, rq, context),
2002 };
2003 true
2004 }
2005 Event::Gesture(GestureEvent::Corner { dir, .. }) => {
2006 match dir {
2007 DiagDir::NorthWest | DiagDir::SouthWest => {
2008 self.go_to_status_change(CycleDir::Previous, rq, context)
2009 }
2010 DiagDir::NorthEast | DiagDir::SouthEast => {
2011 self.go_to_status_change(CycleDir::Next, rq, context)
2012 }
2013 };
2014 true
2015 }
2016 Event::Focus(v) => {
2017 if self.focus != v {
2018 self.focus = v;
2019 if v.is_some() {
2020 self.toggle_keyboard(true, true, v, hub, rq, context);
2021 }
2022 }
2023 true
2024 }
2025 Event::Show(ViewId::Keyboard) => {
2026 self.toggle_keyboard(true, true, None, hub, rq, context);
2027 true
2028 }
2029 Event::Toggle(ToggleEvent::View(ViewId::GoToPage)) => {
2030 self.toggle_go_to_page(None, hub, rq, context);
2031 true
2032 }
2033 Event::Toggle(ToggleEvent::View(ViewId::SearchBar)) => {
2034 self.toggle_search_bar(None, true, hub, rq, context);
2035 true
2036 }
2037 Event::ToggleNear(ViewId::TitleMenu, rect) => {
2038 self.toggle_sort_menu(rect, None, rq, context);
2039 true
2040 }
2041 Event::ToggleBookMenu(rect, index) => {
2042 self.toggle_book_menu(index, rect, None, rq, context);
2043 true
2044 }
2045 Event::ToggleNear(ViewId::MainMenu, rect) => {
2046 toggle_main_menu(self, rect, None, rq, context);
2047 true
2048 }
2049 Event::ToggleNear(ViewId::BatteryMenu, rect) => {
2050 toggle_battery_menu(self, rect, None, rq, context);
2051 true
2052 }
2053 Event::ToggleNear(ViewId::ClockMenu, rect) => {
2054 toggle_clock_menu(self, rect, None, rq, context);
2055 true
2056 }
2057 Event::ToggleNear(ViewId::LibraryMenu, rect) => {
2058 self.toggle_library_menu(rect, None, rq, context);
2059 true
2060 }
2061 Event::Close(ViewId::AddressBar) => {
2062 self.toggle_address_bar(Some(false), true, hub, rq, context);
2063 true
2064 }
2065 Event::Close(ViewId::SearchBar) => {
2066 self.toggle_search_bar(Some(false), true, hub, rq, context);
2067 true
2068 }
2069 Event::Close(ViewId::SortMenu) => {
2070 self.toggle_sort_menu(Rectangle::default(), Some(false), rq, context);
2071 true
2072 }
2073 Event::Close(ViewId::LibraryMenu) => {
2074 self.toggle_library_menu(Rectangle::default(), Some(false), rq, context);
2075 true
2076 }
2077 Event::Close(ViewId::MainMenu) => {
2078 toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
2079 true
2080 }
2081 Event::Close(ViewId::GoToPage) => {
2082 self.toggle_go_to_page(Some(false), hub, rq, context);
2083 true
2084 }
2085 Event::Close(ViewId::RenameDocument) => {
2086 self.toggle_rename_document(Some(false), hub, rq, context);
2087 true
2088 }
2089 Event::Select(EntryId::Sort(sort_method)) => {
2090 let selected_library = context.settings.selected_library;
2091 context.settings.libraries[selected_library].sort_method = sort_method;
2092 self.set_sort_method(sort_method, rq, context);
2093 true
2094 }
2095 Event::Select(EntryId::ReverseOrder) => {
2096 let next_value = !self.reverse_order;
2097 self.set_reverse_order(next_value, rq, context);
2098 true
2099 }
2100 Event::Select(EntryId::LoadLibrary(index)) => {
2101 self.load_library(index, hub, rq, context);
2102 true
2103 }
2104 Event::Select(EntryId::CleanUp) => {
2105 self.clean_up(rq, context);
2106 true
2107 }
2108 Event::FetcherAddDocument(_, ref info) => {
2109 self.add_document(*info.clone(), rq, context);
2110 true
2111 }
2112 Event::Select(EntryId::SetStatus(ref path, status)) => {
2113 self.set_status(path, status, rq, context);
2114 true
2115 }
2116 Event::Select(EntryId::FirstColumn(first_column)) => {
2117 let selected_library = context.settings.selected_library;
2118 context.settings.libraries[selected_library].first_column = first_column;
2119 self.update_first_column(rq, context);
2120 true
2121 }
2122 Event::Select(EntryId::SecondColumn(second_column)) => {
2123 let selected_library = context.settings.selected_library;
2124 context.settings.libraries[selected_library].second_column = second_column;
2125 self.update_second_column(rq, context);
2126 true
2127 }
2128 Event::Select(EntryId::ThumbnailPreviews) => {
2129 let selected_library = context.settings.selected_library;
2130 context.settings.libraries[selected_library].thumbnail_previews =
2131 !context.settings.libraries[selected_library].thumbnail_previews;
2132 self.update_thumbnail_previews(rq, context);
2133 true
2134 }
2135 Event::Submit(ViewId::AddressBarInput, ref addr) => {
2136 self.toggle_keyboard(false, true, None, hub, rq, context);
2137 self.select_directory(Path::new(addr), hub, rq, context);
2138 true
2139 }
2140 Event::Submit(ViewId::HomeSearchInput, ref text) => {
2141 self.query = BookQuery::new(text);
2142 if self.query.is_some() {
2143 self.toggle_keyboard(false, false, None, hub, rq, context);
2144 for i in self.shelf_index + 1..=self.shelf_index + 2 {
2146 rq.add(RenderData::new(
2147 self.child(i).id(),
2148 *self.child(i).rect(),
2149 UpdateMode::Gui,
2150 ));
2151 }
2152 self.refresh_visibles(true, true, rq, context);
2153 } else {
2154 let notif = Notification::new(
2155 None,
2156 "Invalid search query.".to_string(),
2157 false,
2158 hub,
2159 rq,
2160 context,
2161 );
2162 self.children.push(Box::new(notif) as Box<dyn View>);
2163 }
2164 true
2165 }
2166 Event::Submit(ViewId::GoToPageInput, ref text) => {
2167 if text == "(" {
2168 self.go_to_page(0, rq, context);
2169 } else if text == ")" {
2170 self.go_to_page(self.pages_count.saturating_sub(1), rq, context);
2171 } else if text == "_" {
2172 let index = (context.rng.next_u64() % self.pages_count as u64) as usize;
2173 self.go_to_page(index, rq, context);
2174 } else if let Ok(index) = text.parse::<usize>() {
2175 self.go_to_page(index.saturating_sub(1), rq, context);
2176 }
2177 true
2178 }
2179 Event::Submit(ViewId::RenameDocumentInput, ref file_name) => {
2180 if let Some(ref path) = self.target_document.take() {
2181 self.rename(path, file_name, rq, context)
2182 .map_err(|e| error!("Can't rename document: {:#}.", e))
2183 .ok();
2184 }
2185 true
2186 }
2187 Event::NavigationBarResized(_) => {
2188 self.adjust_shelf_top_edge();
2189 self.update_shelf(true, rq, context);
2190 self.update_bottom_bar(rq, context);
2191 for i in self.shelf_index - 2..=self.shelf_index - 1 {
2192 rq.add(RenderData::new(
2193 self.child(i).id(),
2194 *self.child(i).rect(),
2195 UpdateMode::Gui,
2196 ));
2197 }
2198 true
2199 }
2200 Event::Select(EntryId::EmptyTrash) => {
2201 self.empty_trash(hub, rq, context);
2202 true
2203 }
2204 Event::Select(EntryId::Rename(ref path)) => {
2205 self.target_document = Some(path.clone());
2206 self.toggle_rename_document(Some(true), hub, rq, context);
2207 true
2208 }
2209 Event::Select(EntryId::Remove(ref path))
2210 | Event::FetcherRemoveDocument(_, ref path) => {
2211 self.remove(path, rq, context)
2212 .map_err(|e| error!("Can't remove document: {:#}.", e))
2213 .ok();
2214 true
2215 }
2216 Event::Select(EntryId::CopyTo(ref path, index)) => {
2217 self.copy_to(path, index, context)
2218 .map_err(|e| error!("Can't copy document: {:#}.", e))
2219 .ok();
2220 true
2221 }
2222 Event::Select(EntryId::MoveTo(ref path, index)) => {
2223 self.move_to(path, index, rq, context)
2224 .map_err(|e| error!("Can't move document: {:#}.", e))
2225 .ok();
2226 true
2227 }
2228 Event::Select(EntryId::ToggleShowHidden) => {
2229 context.library.show_hidden = !context.library.show_hidden;
2230 self.refresh_visibles(true, false, rq, context);
2231 true
2232 }
2233 Event::SelectDirectory(ref path)
2234 | Event::Select(EntryId::SelectDirectory(ref path)) => {
2235 self.select_directory(path, hub, rq, context);
2236 true
2237 }
2238 Event::ToggleSelectDirectory(ref path)
2239 | Event::Select(EntryId::ToggleSelectDirectory(ref path)) => {
2240 self.toggle_select_directory(path, hub, rq, context);
2241 true
2242 }
2243 Event::Select(EntryId::SearchAuthor(ref author)) => {
2244 let text = format!("'a {}", author);
2245 let query = BookQuery::new(&text);
2246 if query.is_some() {
2247 self.query = query;
2248 self.toggle_search_bar(Some(true), false, hub, rq, context);
2249 self.toggle_keyboard(false, false, None, hub, rq, context);
2250 if let Some(search_bar) =
2251 self.children[self.shelf_index + 2].downcast_mut::<SearchBar>()
2252 {
2253 search_bar.set_text(&text, rq, context);
2254 }
2255 for i in self.shelf_index + 1..=self.shelf_index + 2 {
2257 rq.add(RenderData::new(
2258 self.child(i).id(),
2259 *self.child(i).rect(),
2260 UpdateMode::Gui,
2261 ));
2262 }
2263 self.refresh_visibles(true, true, rq, context);
2264 }
2265 true
2266 }
2267 Event::GoTo(location) => {
2268 self.go_to_page(location, rq, context);
2269 true
2270 }
2271 Event::Chapter(dir) => {
2272 let pages_count = self.pages_count;
2273 match dir {
2274 CycleDir::Previous => self.go_to_page(0, rq, context),
2275 CycleDir::Next => self.go_to_page(pages_count.saturating_sub(1), rq, context),
2276 }
2277 true
2278 }
2279 Event::Page(dir) => {
2280 self.go_to_neighbor(dir, rq, context);
2281 true
2282 }
2283 Event::Device(DeviceEvent::Button {
2284 code: ButtonCode::Backward,
2285 status: ButtonStatus::Pressed,
2286 ..
2287 }) => {
2288 self.go_to_neighbor(CycleDir::Previous, rq, context);
2289 true
2290 }
2291 Event::Device(DeviceEvent::Button {
2292 code: ButtonCode::Forward,
2293 status: ButtonStatus::Pressed,
2294 ..
2295 }) => {
2296 self.go_to_neighbor(CycleDir::Next, rq, context);
2297 true
2298 }
2299 Event::Device(DeviceEvent::NetUp) => {
2300 for fetcher in self.background_fetchers.values_mut() {
2301 if let Some(stdin) = fetcher.process.stdin.as_mut() {
2302 writeln!(stdin, "{}", json!({"type": "network", "status": "up"})).ok();
2303 }
2304 }
2305 true
2306 }
2307 Event::FetcherSearch {
2308 id,
2309 ref path,
2310 ref query,
2311 ref sort_by,
2312 } => {
2313 let path = path.as_ref().unwrap_or(&context.library.home);
2314 let query = query.as_ref().and_then(|text| BookQuery::new(text));
2315 let (sort_method, reverse_order) =
2316 sort_by.unwrap_or((context.library.sort_method, context.library.reverse_order));
2317 let (mut files, _) = context.library.list_by(
2318 path,
2319 query.as_ref(),
2320 sort_method,
2321 reverse_order,
2322 false,
2323 );
2324 for entry in &mut files {
2325 mem::swap(&mut entry.reader, &mut entry.reader_info);
2327 }
2328 if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2329 if let Some(stdin) = fetcher.process.stdin.as_mut() {
2330 writeln!(
2331 stdin,
2332 "{}",
2333 json!({"type": "search",
2334 "results": files})
2335 )
2336 .ok();
2337 }
2338 }
2339 true
2340 }
2341 Event::CheckFetcher(id) => {
2342 if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2343 if let Ok(exit_status) = fetcher.process.wait() {
2344 if !exit_status.success() {
2345 let msg = format!(
2346 "{}: abnormal process termination.",
2347 fetcher.path.display()
2348 );
2349 let notif = Notification::new(None, msg, false, hub, rq, context);
2350 self.children.push(Box::new(notif) as Box<dyn View>);
2351 }
2352 }
2353 }
2354 true
2355 }
2356 Event::ToggleFrontlight => {
2357 if let Some(index) = locate::<TopBar>(self) {
2358 self.child_mut(index)
2359 .downcast_mut::<TopBar>()
2360 .unwrap()
2361 .update_frontlight_icon(rq, context);
2362 }
2363 true
2364 }
2365
2366 Event::AutoFrontlightCoordinates(coordinates) => {
2367 if context
2368 .settings
2369 .auto_frontlight_manual_coordinates
2370 .is_none()
2371 {
2372 context.settings.auto_frontlight_last_coordinates = Some(coordinates);
2373 hub.send(Event::AutoFrontlightConfigChanged).ok();
2374 }
2375 true
2376 }
2377 Event::Reseed => {
2378 self.reseed(rq, context);
2379 true
2380 }
2381 Event::ImportFinished { library_index } => {
2382 if library_index.is_none()
2383 || library_index == Some(context.settings.selected_library)
2384 {
2385 context
2386 .library
2387 .set_sort(self.sort_method, self.reverse_order);
2388 self.refresh_visibles(true, false, rq, context);
2389 }
2390 false
2391 }
2392 _ => false,
2393 }
2394 }
2395
2396 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
2397 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
2398
2399 fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
2400 let dpi = CURRENT_DEVICE.dpi;
2401 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
2402 let (small_thickness, big_thickness) = halves(thickness);
2403 let (small_height, big_height) = (
2404 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
2405 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2406 );
2407
2408 self.children.retain(|child| !child.is::<Menu>());
2409
2410 let top_bar_rect = rect![
2412 rect.min.x,
2413 rect.min.y,
2414 rect.max.x,
2415 rect.min.y + small_height - small_thickness
2416 ];
2417 self.children[0].resize(top_bar_rect, hub, rq, context);
2418
2419 let separator_rect = rect![
2420 rect.min.x,
2421 rect.min.y + small_height - small_thickness,
2422 rect.max.x,
2423 rect.min.y + small_height + big_thickness
2424 ];
2425 self.children[1].resize(separator_rect, hub, rq, context);
2426
2427 let mut shelf_min_y = rect.min.y + small_height + big_thickness;
2428 let mut index = 2;
2429
2430 if context.settings.home.address_bar {
2432 self.children[index].resize(
2433 rect![
2434 rect.min.x,
2435 shelf_min_y,
2436 rect.max.x,
2437 shelf_min_y + small_height - thickness
2438 ],
2439 hub,
2440 rq,
2441 context,
2442 );
2443 shelf_min_y += small_height - thickness;
2444 index += 1;
2445
2446 self.children[index].resize(
2447 rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2448 hub,
2449 rq,
2450 context,
2451 );
2452 shelf_min_y += thickness;
2453 index += 1;
2454 }
2455
2456 if context.settings.home.navigation_bar {
2458 let count = if self.children[self.shelf_index + 2].is::<SearchBar>() {
2459 2
2460 } else {
2461 1
2462 };
2463 let nav_bar = self.children[index]
2464 .as_mut()
2465 .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
2466 .unwrap();
2467 nav_bar.clear();
2468 nav_bar.resize(
2469 rect![
2470 rect.min.x,
2471 shelf_min_y,
2472 rect.max.x,
2473 shelf_min_y + small_height - thickness
2474 ],
2475 hub,
2476 rq,
2477 context,
2478 );
2479 nav_bar.vertical_limit =
2480 rect.max.y - count * small_height - big_height - small_thickness;
2481 nav_bar.set_selected(
2482 self.current_directory.clone(),
2483 &mut RenderQueue::new(),
2484 context,
2485 );
2486 shelf_min_y += nav_bar.rect().height() as i32;
2487 index += 1;
2488
2489 self.children[index].resize(
2490 rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2491 hub,
2492 rq,
2493 context,
2494 );
2495 shelf_min_y += thickness;
2496 }
2497
2498 let bottom_bar_index = rlocate::<BottomBar>(self).unwrap();
2500 index = bottom_bar_index;
2501
2502 let separator_rect = rect![
2503 rect.min.x,
2504 rect.max.y - small_height - small_thickness,
2505 rect.max.x,
2506 rect.max.y - small_height + big_thickness
2507 ];
2508 self.children[index - 1].resize(separator_rect, hub, rq, context);
2509
2510 let bottom_bar_rect = rect![
2511 rect.min.x,
2512 rect.max.y - small_height + big_thickness,
2513 rect.max.x,
2514 rect.max.y
2515 ];
2516 self.children[index].resize(bottom_bar_rect, hub, rq, context);
2517
2518 let mut shelf_max_y = rect.max.y - small_height - small_thickness;
2519
2520 if index - self.shelf_index > 2 {
2521 index -= 2;
2522 if self.children[index].is::<Keyboard>() {
2524 let kb_rect = rect![
2525 rect.min.x,
2526 rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
2527 rect.max.x,
2528 rect.max.y - small_height - small_thickness
2529 ];
2530 self.children[index].resize(kb_rect, hub, rq, context);
2531 let s_max_y = self.children[index].rect().min.y;
2532 self.children[index - 1].resize(
2533 rect![rect.min.x, s_max_y - thickness, rect.max.x, s_max_y],
2534 hub,
2535 rq,
2536 context,
2537 );
2538 index -= 2;
2539 }
2540 if self.children[index].is::<SearchBar>() {
2542 let sp_rect = *self.children[index + 1].rect() - pt!(0, small_height);
2543 self.children[index].resize(
2544 rect![
2545 rect.min.x,
2546 sp_rect.max.y,
2547 rect.max.x,
2548 sp_rect.max.y + small_height - thickness
2549 ],
2550 hub,
2551 rq,
2552 context,
2553 );
2554 self.children[index - 1].resize(sp_rect, hub, rq, context);
2555 shelf_max_y -= small_height;
2556 }
2557 }
2558
2559 let shelf_rect = rect![rect.min.x, shelf_min_y, rect.max.x, shelf_max_y];
2561 self.children[self.shelf_index].resize(shelf_rect, hub, rq, context);
2562
2563 self.update_shelf(true, &mut RenderQueue::new(), context);
2564 self.update_bottom_bar(&mut RenderQueue::new(), context);
2565
2566 for i in bottom_bar_index + 1..self.children.len() {
2568 self.children[i].resize(rect, hub, rq, context);
2569 }
2570
2571 self.rect = rect;
2572 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Full));
2573 }
2574
2575 fn rect(&self) -> &Rectangle {
2576 &self.rect
2577 }
2578
2579 fn rect_mut(&mut self) -> &mut Rectangle {
2580 &mut self.rect
2581 }
2582
2583 fn children(&self) -> &Vec<Box<dyn View>> {
2584 &self.children
2585 }
2586
2587 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
2588 &mut self.children
2589 }
2590
2591 fn id(&self) -> Id {
2592 self.id
2593 }
2594}
2595
2596#[cfg(test)]
2597mod tests {
2598 use super::*;
2599 use crate::context::test_helpers::create_test_context;
2600
2601 #[test]
2602 fn test_toggle_address_bar_with_navigation_bar_maintains_separator_alignment() {
2603 let mut context = create_test_context();
2604 let (tx, _rx) = std::sync::mpsc::channel();
2605 let hub = tx;
2606 let mut rq = RenderQueue::new();
2607
2608 context.settings.home.navigation_bar = false;
2609 context.settings.home.address_bar = false;
2610
2611 let rect = rect![0, 0, 600, 800];
2612 let mut home = Home::new(rect, &mut rq, &mut context).unwrap();
2613
2614 home.toggle_navigation_bar(Some(true), false, &mut rq, &mut context);
2615 assert!(context.settings.home.navigation_bar);
2616
2617 let nav_bar_index =
2618 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2619 let separator_index = home.shelf_index - 1;
2620
2621 let nav_bar_bottom_before = home.children[nav_bar_index].rect().max.y;
2622 let separator_top_before = home.children[separator_index].rect().min.y;
2623 assert_eq!(
2624 nav_bar_bottom_before, separator_top_before,
2625 "Navigation bar and separator should be aligned before toggling address bar"
2626 );
2627
2628 home.toggle_address_bar(Some(true), false, &hub, &mut rq, &mut context);
2629 assert!(context.settings.home.address_bar);
2630
2631 let nav_bar_index =
2632 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2633 let separator_index = home.shelf_index - 1;
2634
2635 let nav_bar_bottom_after_enable = home.children[nav_bar_index].rect().max.y;
2636 let separator_top_after_enable = home.children[separator_index].rect().min.y;
2637 assert_eq!(
2638 nav_bar_bottom_after_enable, separator_top_after_enable,
2639 "Navigation bar and separator should remain aligned after enabling address bar"
2640 );
2641
2642 home.toggle_address_bar(Some(false), false, &hub, &mut rq, &mut context);
2643 assert!(!context.settings.home.address_bar);
2644
2645 let nav_bar_index =
2646 locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2647 let separator_index = home.shelf_index - 1;
2648
2649 let nav_bar_bottom_after_disable = home.children[nav_bar_index].rect().max.y;
2650 let separator_top_after_disable = home.children[separator_index].rect().min.y;
2651 assert_eq!(
2652 nav_bar_bottom_after_disable, separator_top_after_disable,
2653 "Navigation bar and separator should remain aligned after disabling address bar"
2654 );
2655 }
2656}