Skip to main content

cadmus_core/view/home/
mod.rs

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