Skip to main content

cadmus_core/view/home/
mod.rs

1mod address_bar;
2mod book;
3mod bottom_bar;
4pub mod directories_bar;
5mod directory;
6mod library_label;
7mod shelf;
8
9use self::address_bar::AddressBar;
10use self::bottom_bar::BottomBar;
11use self::shelf::Shelf;
12use super::top_bar::{TopBar, TopBarVariant};
13use crate::color::BLACK;
14use crate::context::Context;
15use crate::device::CURRENT_DEVICE;
16use crate::font::Fonts;
17use crate::framebuffer::{Framebuffer, UpdateMode};
18use crate::geom::{halves, CycleDir, DiagDir, Dir, Rectangle};
19use crate::gesture::GestureEvent;
20use crate::input::{ButtonCode, ButtonStatus, DeviceEvent};
21use crate::library::Library;
22use crate::metadata::{BookQuery, Info, SimpleStatus, SortMethod};
23use crate::settings::{FirstColumn, Hook, SecondColumn};
24use crate::unit::scale_by_dpi;
25use crate::view::common::{locate, locate_by_id, rlocate};
26use crate::view::common::{toggle_battery_menu, toggle_clock_menu, toggle_main_menu};
27use crate::view::filler::Filler;
28use crate::view::keyboard::Keyboard;
29use crate::view::menu::{Menu, MenuKind};
30use crate::view::menu_entry::MenuEntry;
31use crate::view::named_input::NamedInput;
32use crate::view::navigation::providers::directory::DirectoryNavigationProvider;
33use crate::view::navigation::StackNavigationBar;
34use crate::view::notification::Notification;
35use crate::view::search_bar::SearchBar;
36use crate::view::{Bus, Event, Hub, NotificationEvent, RenderData, RenderQueue, ToggleEvent, View};
37use crate::view::{EntryId, EntryKind, Id, ViewId, ID_FEEDER};
38use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
39use anyhow::{format_err, Error};
40use fxhash::FxHashMap;
41use rand_core::Rng;
42use serde_json::{json, Value as JsonValue};
43use std::fs;
44use std::io::Write;
45use std::io::{BufRead, BufReader};
46use std::mem;
47use std::path::{Path, PathBuf};
48use std::process::{Child, Command, Stdio};
49use std::thread;
50use tracing::error;
51
52pub const TRASH_DIRNAME: &str = ".trash";
53
54pub struct Home {
55    id: Id,
56    rect: Rectangle,
57    children: Vec<Box<dyn View>>,
58    current_page: usize,
59    pages_count: usize,
60    shelf_index: usize,
61    focus: Option<ViewId>,
62    query: Option<BookQuery>,
63    sort_method: SortMethod,
64    reverse_order: bool,
65    total_count: usize,
66    current_page_books: Vec<Info>,
67    current_directory: PathBuf,
68    target_document: Option<PathBuf>,
69    background_fetchers: FxHashMap<u32, Fetcher>,
70}
71
72struct Fetcher {
73    path: PathBuf,
74    full_path: PathBuf,
75    process: Child,
76    sort_method: Option<SortMethod>,
77    first_column: Option<FirstColumn>,
78    second_column: Option<SecondColumn>,
79}
80
81impl Home {
82    /// Builds the Home view and loads the initial page of books for the selected library.
83    #[cfg_attr(feature = "tracing", tracing::instrument(skip(rq, 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        match self.spawn_child(
1806            library_path,
1807            &save_path,
1808            &hook.program,
1809            context.settings.wifi,
1810            context.online,
1811            hub,
1812        ) {
1813            Ok(process) => {
1814                let mut sort_method = hook.sort_method;
1815                let mut first_column = hook.first_column;
1816                let mut second_column = hook.second_column;
1817                if let Some(sort_method) = sort_method.replace(self.sort_method) {
1818                    hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1819                }
1820                let selected_library = context.settings.selected_library;
1821                if let Some(first_column) =
1822                    first_column.replace(context.settings.libraries[selected_library].first_column)
1823                {
1824                    hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1825                        .ok();
1826                }
1827                if let Some(second_column) = second_column
1828                    .replace(context.settings.libraries[selected_library].second_column)
1829                {
1830                    hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1831                        .ok();
1832                }
1833                self.background_fetchers.insert(
1834                    process.id(),
1835                    Fetcher {
1836                        path: hook.path.clone(),
1837                        full_path: save_path,
1838                        process,
1839                        sort_method,
1840                        first_column,
1841                        second_column,
1842                    },
1843                );
1844            }
1845            Err(e) => error!("Can't spawn child: {:#}.", e),
1846        }
1847    }
1848
1849    fn spawn_child(
1850        &mut self,
1851        library_path: &Path,
1852        save_path: &Path,
1853        program: &Path,
1854        wifi: bool,
1855        online: bool,
1856        hub: &Hub,
1857    ) -> Result<Child, Error> {
1858        let path = program.canonicalize()?;
1859        let parent = path.parent().unwrap_or_else(|| Path::new(""));
1860        let mut process = Command::new(&path)
1861            .current_dir(parent)
1862            .arg(library_path)
1863            .arg(save_path)
1864            .arg(wifi.to_string())
1865            .arg(online.to_string())
1866            .stdin(Stdio::piped())
1867            .stdout(Stdio::piped())
1868            .spawn()?;
1869        let stdout = process
1870            .stdout
1871            .take()
1872            .ok_or_else(|| format_err!("can't take stdout"))?;
1873        let id = process.id();
1874        let hub2 = hub.clone();
1875        thread::spawn(move || {
1876            let reader = BufReader::new(stdout);
1877            for line_res in reader.lines() {
1878                if let Ok(line) = line_res {
1879                    if let Ok(event) = serde_json::from_str::<JsonValue>(&line) {
1880                        match event.get("type").and_then(JsonValue::as_str) {
1881                            Some("notify") => {
1882                                if let Some(msg) = event.get("message").and_then(JsonValue::as_str)
1883                                {
1884                                    hub2.send(Event::Notification(NotificationEvent::Show(
1885                                        msg.to_string(),
1886                                    )))
1887                                    .ok();
1888                                }
1889                            }
1890                            Some("setWifi") => {
1891                                if let Some(enable) =
1892                                    event.get("enable").and_then(JsonValue::as_bool)
1893                                {
1894                                    hub2.send(Event::SetWifi(enable)).ok();
1895                                }
1896                            }
1897                            Some("addDocument") => {
1898                                if let Some(info) = event
1899                                    .get("info")
1900                                    .map(ToString::to_string)
1901                                    .and_then(|v| serde_json::from_str(&v).ok())
1902                                {
1903                                    hub2.send(Event::FetcherAddDocument(id, Box::new(info)))
1904                                        .ok();
1905                                }
1906                            }
1907                            Some("removeDocument") => {
1908                                if let Some(path) = event.get("path").and_then(JsonValue::as_str) {
1909                                    hub2.send(Event::FetcherRemoveDocument(
1910                                        id,
1911                                        PathBuf::from(path),
1912                                    ))
1913                                    .ok();
1914                                }
1915                            }
1916                            Some("search") => {
1917                                let path = event
1918                                    .get("path")
1919                                    .and_then(JsonValue::as_str)
1920                                    .map(PathBuf::from);
1921                                let query = event
1922                                    .get("query")
1923                                    .and_then(JsonValue::as_str)
1924                                    .map(String::from);
1925                                let sort_by = event
1926                                    .get("sortBy")
1927                                    .map(ToString::to_string)
1928                                    .and_then(|v| serde_json::from_str(&v).ok());
1929                                hub2.send(Event::FetcherSearch {
1930                                    id,
1931                                    path,
1932                                    query,
1933                                    sort_by,
1934                                })
1935                                .ok();
1936                            }
1937                            _ => (),
1938                        }
1939                    }
1940                } else {
1941                    break;
1942                }
1943            }
1944            hub2.send(Event::CheckFetcher(id)).ok();
1945        });
1946        Ok(process)
1947    }
1948
1949    fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
1950        context
1951            .library
1952            .set_sort(self.sort_method, self.reverse_order);
1953        self.refresh_visibles(true, false, &mut RenderQueue::new(), context);
1954
1955        if let Some(top_bar) = self.child_mut(0).downcast_mut::<TopBar>() {
1956            top_bar.reseed(&mut RenderQueue::new(), context);
1957        }
1958
1959        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
1960    }
1961}
1962
1963impl View for Home {
1964    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
1965    fn handle_event(
1966        &mut self,
1967        evt: &Event,
1968        hub: &Hub,
1969        _bus: &mut Bus,
1970        rq: &mut RenderQueue,
1971        context: &mut Context,
1972    ) -> bool {
1973        match *evt {
1974            Event::Gesture(GestureEvent::Swipe {
1975                dir, start, end, ..
1976            }) => {
1977                match dir {
1978                    Dir::South
1979                        if self.children[0].rect().includes(start)
1980                            && self.children[self.shelf_index].rect().includes(end) =>
1981                    {
1982                        if !context.settings.home.navigation_bar {
1983                            self.toggle_navigation_bar(Some(true), true, rq, context);
1984                        } else if !context.settings.home.address_bar {
1985                            self.toggle_address_bar(Some(true), true, hub, rq, context);
1986                        }
1987                    }
1988                    Dir::North
1989                        if self.children[self.shelf_index].rect().includes(start)
1990                            && self.children[0].rect().includes(end) =>
1991                    {
1992                        if context.settings.home.address_bar {
1993                            self.toggle_address_bar(Some(false), true, hub, rq, context);
1994                        } else if context.settings.home.navigation_bar {
1995                            self.toggle_navigation_bar(Some(false), true, rq, context);
1996                        }
1997                    }
1998                    _ => (),
1999                }
2000                true
2001            }
2002            Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
2003                let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
2004                let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
2005                hub.send(Event::Select(EntryId::Rotate(n))).ok();
2006                true
2007            }
2008            Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
2009                match dir {
2010                    Dir::West => self.go_to_page(0, rq, context),
2011                    Dir::East => {
2012                        let pages_count = self.pages_count;
2013                        self.go_to_page(pages_count.saturating_sub(1), rq, context);
2014                    }
2015                    Dir::North => {
2016                        let path = context.library.home.clone();
2017                        self.select_directory(&path, hub, rq, context);
2018                    }
2019                    Dir::South => self.toggle_search_bar(None, true, hub, rq, context),
2020                };
2021                true
2022            }
2023            Event::Gesture(GestureEvent::Corner { dir, .. }) => {
2024                match dir {
2025                    DiagDir::NorthWest | DiagDir::SouthWest => {
2026                        self.go_to_status_change(CycleDir::Previous, rq, context)
2027                    }
2028                    DiagDir::NorthEast | DiagDir::SouthEast => {
2029                        self.go_to_status_change(CycleDir::Next, rq, context)
2030                    }
2031                };
2032                true
2033            }
2034            Event::Focus(v) => {
2035                if self.focus != v {
2036                    self.focus = v;
2037                    if v.is_some() {
2038                        self.toggle_keyboard(true, true, v, hub, rq, context);
2039                    }
2040                }
2041                true
2042            }
2043            Event::Show(ViewId::Keyboard) => {
2044                self.toggle_keyboard(true, true, None, hub, rq, context);
2045                true
2046            }
2047            Event::Toggle(ToggleEvent::View(ViewId::GoToPage)) => {
2048                self.toggle_go_to_page(None, hub, rq, context);
2049                true
2050            }
2051            Event::Toggle(ToggleEvent::View(ViewId::SearchBar)) => {
2052                self.toggle_search_bar(None, true, hub, rq, context);
2053                true
2054            }
2055            Event::ToggleNear(ViewId::TitleMenu, rect) => {
2056                self.toggle_sort_menu(rect, None, rq, context);
2057                true
2058            }
2059            Event::ToggleBookMenu(rect, index) => {
2060                self.toggle_book_menu(index, rect, None, rq, context);
2061                true
2062            }
2063            Event::ToggleNear(ViewId::MainMenu, rect) => {
2064                toggle_main_menu(self, rect, None, rq, context);
2065                true
2066            }
2067            Event::ToggleNear(ViewId::BatteryMenu, rect) => {
2068                toggle_battery_menu(self, rect, None, rq, context);
2069                true
2070            }
2071            Event::ToggleNear(ViewId::ClockMenu, rect) => {
2072                toggle_clock_menu(self, rect, None, rq, context);
2073                true
2074            }
2075            Event::ToggleNear(ViewId::LibraryMenu, rect) => {
2076                self.toggle_library_menu(rect, None, rq, context);
2077                true
2078            }
2079            Event::Close(ViewId::AddressBar) => {
2080                self.toggle_address_bar(Some(false), true, hub, rq, context);
2081                true
2082            }
2083            Event::Close(ViewId::SearchBar) => {
2084                self.toggle_search_bar(Some(false), true, hub, rq, context);
2085                true
2086            }
2087            Event::Close(ViewId::SortMenu) => {
2088                self.toggle_sort_menu(Rectangle::default(), Some(false), rq, context);
2089                true
2090            }
2091            Event::Close(ViewId::LibraryMenu) => {
2092                self.toggle_library_menu(Rectangle::default(), Some(false), rq, context);
2093                true
2094            }
2095            Event::Close(ViewId::MainMenu) => {
2096                toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
2097                true
2098            }
2099            Event::Close(ViewId::GoToPage) => {
2100                self.toggle_go_to_page(Some(false), hub, rq, context);
2101                true
2102            }
2103            Event::Close(ViewId::RenameDocument) => {
2104                self.toggle_rename_document(Some(false), hub, rq, context);
2105                true
2106            }
2107            Event::Select(EntryId::Sort(sort_method)) => {
2108                let selected_library = context.settings.selected_library;
2109                context.settings.libraries[selected_library].sort_method = sort_method;
2110                self.set_sort_method(sort_method, rq, context);
2111                true
2112            }
2113            Event::Select(EntryId::ReverseOrder) => {
2114                let next_value = !self.reverse_order;
2115                self.set_reverse_order(next_value, rq, context);
2116                true
2117            }
2118            Event::Select(EntryId::LoadLibrary(index)) => {
2119                self.load_library(index, hub, rq, context);
2120                true
2121            }
2122            Event::Select(EntryId::Import) => {
2123                let library_index = context.settings.selected_library;
2124                hub.send(Event::ImportLibrary {
2125                    library_index: Some(library_index),
2126                })
2127                .ok();
2128                true
2129            }
2130            Event::Select(EntryId::CleanUp) => {
2131                self.clean_up(rq, context);
2132                true
2133            }
2134            Event::Select(EntryId::Flush) => {
2135                self.flush(context);
2136                true
2137            }
2138            Event::FetcherAddDocument(_, ref info) => {
2139                self.add_document(*info.clone(), rq, context);
2140                true
2141            }
2142            Event::Select(EntryId::SetStatus(ref path, status)) => {
2143                self.set_status(path, status, rq, context);
2144                true
2145            }
2146            Event::Select(EntryId::FirstColumn(first_column)) => {
2147                let selected_library = context.settings.selected_library;
2148                context.settings.libraries[selected_library].first_column = first_column;
2149                self.update_first_column(rq, context);
2150                true
2151            }
2152            Event::Select(EntryId::SecondColumn(second_column)) => {
2153                let selected_library = context.settings.selected_library;
2154                context.settings.libraries[selected_library].second_column = second_column;
2155                self.update_second_column(rq, context);
2156                true
2157            }
2158            Event::Select(EntryId::ThumbnailPreviews) => {
2159                let selected_library = context.settings.selected_library;
2160                context.settings.libraries[selected_library].thumbnail_previews =
2161                    !context.settings.libraries[selected_library].thumbnail_previews;
2162                self.update_thumbnail_previews(rq, context);
2163                true
2164            }
2165            Event::Submit(ViewId::AddressBarInput, ref addr) => {
2166                self.toggle_keyboard(false, true, None, hub, rq, context);
2167                self.select_directory(Path::new(addr), hub, rq, context);
2168                true
2169            }
2170            Event::Submit(ViewId::HomeSearchInput, ref text) => {
2171                self.query = BookQuery::new(text);
2172                if self.query.is_some() {
2173                    self.toggle_keyboard(false, false, None, hub, rq, context);
2174                    // Render the search bar and its separator.
2175                    for i in self.shelf_index + 1..=self.shelf_index + 2 {
2176                        rq.add(RenderData::new(
2177                            self.child(i).id(),
2178                            *self.child(i).rect(),
2179                            UpdateMode::Gui,
2180                        ));
2181                    }
2182                    self.refresh_visibles(true, true, rq, context);
2183                } else {
2184                    let notif = Notification::new(
2185                        None,
2186                        "Invalid search query.".to_string(),
2187                        false,
2188                        hub,
2189                        rq,
2190                        context,
2191                    );
2192                    self.children.push(Box::new(notif) as Box<dyn View>);
2193                }
2194                true
2195            }
2196            Event::Submit(ViewId::GoToPageInput, ref text) => {
2197                if text == "(" {
2198                    self.go_to_page(0, rq, context);
2199                } else if text == ")" {
2200                    self.go_to_page(self.pages_count.saturating_sub(1), rq, context);
2201                } else if text == "_" {
2202                    let index = (context.rng.next_u64() % self.pages_count as u64) as usize;
2203                    self.go_to_page(index, rq, context);
2204                } else if let Ok(index) = text.parse::<usize>() {
2205                    self.go_to_page(index.saturating_sub(1), rq, context);
2206                }
2207                true
2208            }
2209            Event::Submit(ViewId::RenameDocumentInput, ref file_name) => {
2210                if let Some(ref path) = self.target_document.take() {
2211                    self.rename(path, file_name, rq, context)
2212                        .map_err(|e| error!("Can't rename document: {:#}.", e))
2213                        .ok();
2214                }
2215                true
2216            }
2217            Event::NavigationBarResized(_) => {
2218                self.adjust_shelf_top_edge();
2219                self.update_shelf(true, rq, context);
2220                self.update_bottom_bar(rq, context);
2221                for i in self.shelf_index - 2..=self.shelf_index - 1 {
2222                    rq.add(RenderData::new(
2223                        self.child(i).id(),
2224                        *self.child(i).rect(),
2225                        UpdateMode::Gui,
2226                    ));
2227                }
2228                true
2229            }
2230            Event::Select(EntryId::EmptyTrash) => {
2231                self.empty_trash(hub, rq, context);
2232                true
2233            }
2234            Event::Select(EntryId::Rename(ref path)) => {
2235                self.target_document = Some(path.clone());
2236                self.toggle_rename_document(Some(true), hub, rq, context);
2237                true
2238            }
2239            Event::Select(EntryId::Remove(ref path))
2240            | Event::FetcherRemoveDocument(_, ref path) => {
2241                self.remove(path, rq, context)
2242                    .map_err(|e| error!("Can't remove document: {:#}.", e))
2243                    .ok();
2244                true
2245            }
2246            Event::Select(EntryId::CopyTo(ref path, index)) => {
2247                self.copy_to(path, index, context)
2248                    .map_err(|e| error!("Can't copy document: {:#}.", e))
2249                    .ok();
2250                true
2251            }
2252            Event::Select(EntryId::MoveTo(ref path, index)) => {
2253                self.move_to(path, index, rq, context)
2254                    .map_err(|e| error!("Can't move document: {:#}.", e))
2255                    .ok();
2256                true
2257            }
2258            Event::Select(EntryId::ToggleShowHidden) => {
2259                context.library.show_hidden = !context.library.show_hidden;
2260                self.refresh_visibles(true, false, rq, context);
2261                true
2262            }
2263            Event::SelectDirectory(ref path)
2264            | Event::Select(EntryId::SelectDirectory(ref path)) => {
2265                self.select_directory(path, hub, rq, context);
2266                true
2267            }
2268            Event::ToggleSelectDirectory(ref path)
2269            | Event::Select(EntryId::ToggleSelectDirectory(ref path)) => {
2270                self.toggle_select_directory(path, hub, rq, context);
2271                true
2272            }
2273            Event::Select(EntryId::SearchAuthor(ref author)) => {
2274                let text = format!("'a {}", author);
2275                let query = BookQuery::new(&text);
2276                if query.is_some() {
2277                    self.query = query;
2278                    self.toggle_search_bar(Some(true), false, hub, rq, context);
2279                    self.toggle_keyboard(false, false, None, hub, rq, context);
2280                    if let Some(search_bar) =
2281                        self.children[self.shelf_index + 2].downcast_mut::<SearchBar>()
2282                    {
2283                        search_bar.set_text(&text, rq, context);
2284                    }
2285                    // Render the search bar and its separator.
2286                    for i in self.shelf_index + 1..=self.shelf_index + 2 {
2287                        rq.add(RenderData::new(
2288                            self.child(i).id(),
2289                            *self.child(i).rect(),
2290                            UpdateMode::Gui,
2291                        ));
2292                    }
2293                    self.refresh_visibles(true, true, rq, context);
2294                }
2295                true
2296            }
2297            Event::GoTo(location) => {
2298                self.go_to_page(location, rq, context);
2299                true
2300            }
2301            Event::Chapter(dir) => {
2302                let pages_count = self.pages_count;
2303                match dir {
2304                    CycleDir::Previous => self.go_to_page(0, rq, context),
2305                    CycleDir::Next => self.go_to_page(pages_count.saturating_sub(1), rq, context),
2306                }
2307                true
2308            }
2309            Event::Page(dir) => {
2310                self.go_to_neighbor(dir, rq, context);
2311                true
2312            }
2313            Event::Device(DeviceEvent::Button {
2314                code: ButtonCode::Backward,
2315                status: ButtonStatus::Pressed,
2316                ..
2317            }) => {
2318                self.go_to_neighbor(CycleDir::Previous, rq, context);
2319                true
2320            }
2321            Event::Device(DeviceEvent::Button {
2322                code: ButtonCode::Forward,
2323                status: ButtonStatus::Pressed,
2324                ..
2325            }) => {
2326                self.go_to_neighbor(CycleDir::Next, rq, context);
2327                true
2328            }
2329            Event::Device(DeviceEvent::NetUp) => {
2330                for fetcher in self.background_fetchers.values_mut() {
2331                    if let Some(stdin) = fetcher.process.stdin.as_mut() {
2332                        writeln!(stdin, "{}", json!({"type": "network", "status": "up"})).ok();
2333                    }
2334                }
2335                true
2336            }
2337            Event::FetcherSearch {
2338                id,
2339                ref path,
2340                ref query,
2341                ref sort_by,
2342            } => {
2343                let path = path.as_ref().unwrap_or(&context.library.home);
2344                let query = query.as_ref().and_then(|text| BookQuery::new(text));
2345                let (sort_method, reverse_order) =
2346                    sort_by.unwrap_or((context.library.sort_method, context.library.reverse_order));
2347                let (mut files, _) = context.library.list_by(
2348                    path,
2349                    query.as_ref(),
2350                    sort_method,
2351                    reverse_order,
2352                    false,
2353                );
2354                for entry in &mut files {
2355                    // Let the *reader* field pass through.
2356                    mem::swap(&mut entry.reader, &mut entry.reader_info);
2357                }
2358                if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2359                    if let Some(stdin) = fetcher.process.stdin.as_mut() {
2360                        writeln!(
2361                            stdin,
2362                            "{}",
2363                            json!({"type": "search",
2364                                                     "results": files})
2365                        )
2366                        .ok();
2367                    }
2368                }
2369                true
2370            }
2371            Event::CheckFetcher(id) => {
2372                if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2373                    if let Ok(exit_status) = fetcher.process.wait() {
2374                        if !exit_status.success() {
2375                            let msg = format!(
2376                                "{}: abnormal process termination.",
2377                                fetcher.path.display()
2378                            );
2379                            let notif = Notification::new(None, msg, false, hub, rq, context);
2380                            self.children.push(Box::new(notif) as Box<dyn View>);
2381                        }
2382                    }
2383                }
2384                true
2385            }
2386            Event::ToggleFrontlight => {
2387                if let Some(index) = locate::<TopBar>(self) {
2388                    self.child_mut(index)
2389                        .downcast_mut::<TopBar>()
2390                        .unwrap()
2391                        .update_frontlight_icon(rq, context);
2392                }
2393                true
2394            }
2395            Event::Reseed => {
2396                self.reseed(rq, context);
2397                true
2398            }
2399            Event::ImportFinished { library_index } => {
2400                if library_index.is_none()
2401                    || library_index == Some(context.settings.selected_library)
2402                {
2403                    context
2404                        .library
2405                        .set_sort(self.sort_method, self.reverse_order);
2406                    self.refresh_visibles(true, false, rq, context);
2407                }
2408                false
2409            }
2410            _ => false,
2411        }
2412    }
2413
2414    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
2415    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
2416
2417    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
2418        let dpi = CURRENT_DEVICE.dpi;
2419        let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
2420        let (small_thickness, big_thickness) = halves(thickness);
2421        let (small_height, big_height) = (
2422            scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
2423            scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2424        );
2425
2426        self.children.retain(|child| !child.is::<Menu>());
2427
2428        // Top bar.
2429        let top_bar_rect = rect![
2430            rect.min.x,
2431            rect.min.y,
2432            rect.max.x,
2433            rect.min.y + small_height - small_thickness
2434        ];
2435        self.children[0].resize(top_bar_rect, hub, rq, context);
2436
2437        let separator_rect = rect![
2438            rect.min.x,
2439            rect.min.y + small_height - small_thickness,
2440            rect.max.x,
2441            rect.min.y + small_height + big_thickness
2442        ];
2443        self.children[1].resize(separator_rect, hub, rq, context);
2444
2445        let mut shelf_min_y = rect.min.y + small_height + big_thickness;
2446        let mut index = 2;
2447
2448        // Address bar.
2449        if context.settings.home.address_bar {
2450            self.children[index].resize(
2451                rect![
2452                    rect.min.x,
2453                    shelf_min_y,
2454                    rect.max.x,
2455                    shelf_min_y + small_height - thickness
2456                ],
2457                hub,
2458                rq,
2459                context,
2460            );
2461            shelf_min_y += small_height - thickness;
2462            index += 1;
2463
2464            self.children[index].resize(
2465                rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2466                hub,
2467                rq,
2468                context,
2469            );
2470            shelf_min_y += thickness;
2471            index += 1;
2472        }
2473
2474        // Navigation bar.
2475        if context.settings.home.navigation_bar {
2476            let count = if self.children[self.shelf_index + 2].is::<SearchBar>() {
2477                2
2478            } else {
2479                1
2480            };
2481            let nav_bar = self.children[index]
2482                .as_mut()
2483                .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
2484                .unwrap();
2485            nav_bar.clear();
2486            nav_bar.resize(
2487                rect![
2488                    rect.min.x,
2489                    shelf_min_y,
2490                    rect.max.x,
2491                    shelf_min_y + small_height - thickness
2492                ],
2493                hub,
2494                rq,
2495                context,
2496            );
2497            nav_bar.vertical_limit =
2498                rect.max.y - count * small_height - big_height - small_thickness;
2499            nav_bar.set_selected(
2500                self.current_directory.clone(),
2501                &mut RenderQueue::new(),
2502                context,
2503            );
2504            shelf_min_y += nav_bar.rect().height() as i32;
2505            index += 1;
2506
2507            self.children[index].resize(
2508                rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2509                hub,
2510                rq,
2511                context,
2512            );
2513            shelf_min_y += thickness;
2514        }
2515
2516        // Bottom bar.
2517        let bottom_bar_index = rlocate::<BottomBar>(self).unwrap();
2518        index = bottom_bar_index;
2519
2520        let separator_rect = rect![
2521            rect.min.x,
2522            rect.max.y - small_height - small_thickness,
2523            rect.max.x,
2524            rect.max.y - small_height + big_thickness
2525        ];
2526        self.children[index - 1].resize(separator_rect, hub, rq, context);
2527
2528        let bottom_bar_rect = rect![
2529            rect.min.x,
2530            rect.max.y - small_height + big_thickness,
2531            rect.max.x,
2532            rect.max.y
2533        ];
2534        self.children[index].resize(bottom_bar_rect, hub, rq, context);
2535
2536        let mut shelf_max_y = rect.max.y - small_height - small_thickness;
2537
2538        if index - self.shelf_index > 2 {
2539            index -= 2;
2540            // Keyboard.
2541            if self.children[index].is::<Keyboard>() {
2542                let kb_rect = rect![
2543                    rect.min.x,
2544                    rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
2545                    rect.max.x,
2546                    rect.max.y - small_height - small_thickness
2547                ];
2548                self.children[index].resize(kb_rect, hub, rq, context);
2549                let s_max_y = self.children[index].rect().min.y;
2550                self.children[index - 1].resize(
2551                    rect![rect.min.x, s_max_y - thickness, rect.max.x, s_max_y],
2552                    hub,
2553                    rq,
2554                    context,
2555                );
2556                index -= 2;
2557            }
2558            // Search bar.
2559            if self.children[index].is::<SearchBar>() {
2560                let sp_rect = *self.children[index + 1].rect() - pt!(0, small_height);
2561                self.children[index].resize(
2562                    rect![
2563                        rect.min.x,
2564                        sp_rect.max.y,
2565                        rect.max.x,
2566                        sp_rect.max.y + small_height - thickness
2567                    ],
2568                    hub,
2569                    rq,
2570                    context,
2571                );
2572                self.children[index - 1].resize(sp_rect, hub, rq, context);
2573                shelf_max_y -= small_height;
2574            }
2575        }
2576
2577        // Shelf.
2578        let shelf_rect = rect![rect.min.x, shelf_min_y, rect.max.x, shelf_max_y];
2579        self.children[self.shelf_index].resize(shelf_rect, hub, rq, context);
2580
2581        self.update_shelf(true, &mut RenderQueue::new(), context);
2582        self.update_bottom_bar(&mut RenderQueue::new(), context);
2583
2584        // Floating windows.
2585        for i in bottom_bar_index + 1..self.children.len() {
2586            self.children[i].resize(rect, hub, rq, context);
2587        }
2588
2589        self.rect = rect;
2590        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Full));
2591    }
2592
2593    fn rect(&self) -> &Rectangle {
2594        &self.rect
2595    }
2596
2597    fn rect_mut(&mut self) -> &mut Rectangle {
2598        &mut self.rect
2599    }
2600
2601    fn children(&self) -> &Vec<Box<dyn View>> {
2602        &self.children
2603    }
2604
2605    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
2606        &mut self.children
2607    }
2608
2609    fn id(&self) -> Id {
2610        self.id
2611    }
2612}
2613
2614#[cfg(test)]
2615mod tests {
2616    use super::*;
2617    use crate::context::test_helpers::create_test_context;
2618
2619    #[test]
2620    fn test_toggle_address_bar_with_navigation_bar_maintains_separator_alignment() {
2621        let mut context = create_test_context();
2622        let (tx, _rx) = std::sync::mpsc::channel();
2623        let hub = tx;
2624        let mut rq = RenderQueue::new();
2625
2626        context.settings.home.navigation_bar = false;
2627        context.settings.home.address_bar = false;
2628
2629        let rect = rect![0, 0, 600, 800];
2630        let mut home = Home::new(rect, &mut rq, &mut context).unwrap();
2631
2632        home.toggle_navigation_bar(Some(true), false, &mut rq, &mut context);
2633        assert!(context.settings.home.navigation_bar);
2634
2635        let nav_bar_index =
2636            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2637        let separator_index = home.shelf_index - 1;
2638
2639        let nav_bar_bottom_before = home.children[nav_bar_index].rect().max.y;
2640        let separator_top_before = home.children[separator_index].rect().min.y;
2641        assert_eq!(
2642            nav_bar_bottom_before, separator_top_before,
2643            "Navigation bar and separator should be aligned before toggling address bar"
2644        );
2645
2646        home.toggle_address_bar(Some(true), false, &hub, &mut rq, &mut context);
2647        assert!(context.settings.home.address_bar);
2648
2649        let nav_bar_index =
2650            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2651        let separator_index = home.shelf_index - 1;
2652
2653        let nav_bar_bottom_after_enable = home.children[nav_bar_index].rect().max.y;
2654        let separator_top_after_enable = home.children[separator_index].rect().min.y;
2655        assert_eq!(
2656            nav_bar_bottom_after_enable, separator_top_after_enable,
2657            "Navigation bar and separator should remain aligned after enabling address bar"
2658        );
2659
2660        home.toggle_address_bar(Some(false), false, &hub, &mut rq, &mut context);
2661        assert!(!context.settings.home.address_bar);
2662
2663        let nav_bar_index =
2664            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2665        let separator_index = home.shelf_index - 1;
2666
2667        let nav_bar_bottom_after_disable = home.children[nav_bar_index].rect().max.y;
2668        let separator_top_after_disable = home.children[separator_index].rect().min.y;
2669        assert_eq!(
2670            nav_bar_bottom_after_disable, separator_top_after_disable,
2671            "Navigation bar and separator should remain aligned after disabling address bar"
2672        );
2673    }
2674}