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