Skip to main content

cadmus_core/view/home/
mod.rs

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