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