cadmus_core/view/home/
mod.rs

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