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, LibraryMode, 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, RenderData, RenderQueue, 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(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;
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;
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 database = if library_settings.mode == LibraryMode::Database {
1425                vec![
1426                    EntryKind::Command("Import".to_string(), EntryId::Import),
1427                    EntryKind::Command("Flush".to_string(), EntryId::Flush),
1428                ]
1429            } else {
1430                Vec::new()
1431            };
1432
1433            let filesystem = if library_settings.mode == LibraryMode::Filesystem {
1434                vec![
1435                    EntryKind::CheckBox(
1436                        "Show Hidden".to_string(),
1437                        EntryId::ToggleShowHidden,
1438                        context.library.show_hidden,
1439                    ),
1440                    EntryKind::Separator,
1441                    EntryKind::Command("Clean Up".to_string(), EntryId::CleanUp),
1442                    EntryKind::Command("Flush".to_string(), EntryId::Flush),
1443                ]
1444            } else {
1445                Vec::new()
1446            };
1447
1448            let mut entries = vec![EntryKind::SubMenu("Library".to_string(), libraries)];
1449
1450            if !database.is_empty() {
1451                entries.push(EntryKind::SubMenu("Database".to_string(), database));
1452            }
1453
1454            if !filesystem.is_empty() {
1455                entries.push(EntryKind::SubMenu("Filesystem".to_string(), filesystem));
1456            }
1457
1458            let hooks: Vec<EntryKind> = context.settings.libraries[selected_library]
1459                .hooks
1460                .iter()
1461                .map(|v| {
1462                    EntryKind::Command(
1463                        v.path.to_string_lossy().into_owned(),
1464                        EntryId::ToggleSelectDirectory(context.library.home.join(&v.path)),
1465                    )
1466                })
1467                .collect();
1468
1469            if !hooks.is_empty() {
1470                entries.push(EntryKind::SubMenu("Toggle Select".to_string(), hooks));
1471            }
1472
1473            entries.push(EntryKind::Separator);
1474
1475            let first_column = library_settings.first_column;
1476            entries.push(EntryKind::SubMenu(
1477                "First Column".to_string(),
1478                vec![
1479                    EntryKind::RadioButton(
1480                        "Title and Author".to_string(),
1481                        EntryId::FirstColumn(FirstColumn::TitleAndAuthor),
1482                        first_column == FirstColumn::TitleAndAuthor,
1483                    ),
1484                    EntryKind::RadioButton(
1485                        "File Name".to_string(),
1486                        EntryId::FirstColumn(FirstColumn::FileName),
1487                        first_column == FirstColumn::FileName,
1488                    ),
1489                ],
1490            ));
1491
1492            let second_column = library_settings.second_column;
1493            entries.push(EntryKind::SubMenu(
1494                "Second Column".to_string(),
1495                vec![
1496                    EntryKind::RadioButton(
1497                        "Progress".to_string(),
1498                        EntryId::SecondColumn(SecondColumn::Progress),
1499                        second_column == SecondColumn::Progress,
1500                    ),
1501                    EntryKind::RadioButton(
1502                        "Year".to_string(),
1503                        EntryId::SecondColumn(SecondColumn::Year),
1504                        second_column == SecondColumn::Year,
1505                    ),
1506                ],
1507            ));
1508
1509            entries.push(EntryKind::CheckBox(
1510                "Thumbnail Previews".to_string(),
1511                EntryId::ThumbnailPreviews,
1512                library_settings.thumbnail_previews,
1513            ));
1514
1515            let trash_path = context.library.home.join(TRASH_DIRNAME);
1516            if let Ok(trash) = Library::new(trash_path, LibraryMode::Database)
1517                .map_err(|e| error!("Can't inspect trash: {:#?}.", e))
1518            {
1519                if trash.is_empty() == Some(false) {
1520                    entries.push(EntryKind::Separator);
1521                    entries.push(EntryKind::Command(
1522                        "Empty Trash".to_string(),
1523                        EntryId::EmptyTrash,
1524                    ));
1525                }
1526            }
1527
1528            let library_menu = Menu::new(
1529                rect,
1530                ViewId::LibraryMenu,
1531                MenuKind::DropDown,
1532                entries,
1533                context,
1534            );
1535            rq.add(RenderData::new(
1536                library_menu.id(),
1537                *library_menu.rect(),
1538                UpdateMode::Gui,
1539            ));
1540            self.children.push(Box::new(library_menu) as Box<dyn View>);
1541        }
1542    }
1543
1544    fn add_document(&mut self, info: Info, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1545        context.library.add_document(info);
1546        self.sort(false, hub, rq, context);
1547        self.refresh_visibles(true, false, hub, rq, context);
1548    }
1549
1550    fn set_status(
1551        &mut self,
1552        path: &Path,
1553        status: SimpleStatus,
1554        hub: &Hub,
1555        rq: &mut RenderQueue,
1556        context: &mut Context,
1557    ) {
1558        context.library.set_status(path, status);
1559
1560        // Is the current sort method affected by this change?
1561        if self.sort_method.is_status_related() {
1562            self.sort(false, hub, rq, context);
1563        }
1564
1565        self.refresh_visibles(true, false, hub, rq, context);
1566    }
1567
1568    fn empty_trash(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1569        let trash_path = context.library.home.join(TRASH_DIRNAME);
1570
1571        let trash = Library::new(trash_path, LibraryMode::Database)
1572            .map_err(|e| error!("Can't load trash: {:#}.", e));
1573        if trash.is_err() {
1574            return;
1575        }
1576
1577        let mut trash = trash.unwrap();
1578
1579        let (files, _) = trash.list(&trash.home, None, false);
1580        if files.is_empty() {
1581            return;
1582        }
1583
1584        let mut count = 0;
1585        for info in files {
1586            match trash.remove(&info.file.path) {
1587                Err(e) => error!("Can't erase {}: {:#}.", info.file.path.display(), e),
1588                Ok(()) => count += 1,
1589            }
1590        }
1591        trash.flush();
1592        let message = format!(
1593            "Removed {} book{}.",
1594            count,
1595            if count != 1 { "s" } else { "" }
1596        );
1597        let notif = Notification::new(None, message, false, hub, rq, context);
1598        self.children.push(Box::new(notif) as Box<dyn View>);
1599    }
1600
1601    fn rename(
1602        &mut self,
1603        path: &Path,
1604        file_name: &str,
1605        hub: &Hub,
1606        rq: &mut RenderQueue,
1607        context: &mut Context,
1608    ) -> Result<(), Error> {
1609        context.library.rename(path, file_name)?;
1610        self.refresh_visibles(true, false, hub, rq, context);
1611        Ok(())
1612    }
1613
1614    fn remove(
1615        &mut self,
1616        path: &Path,
1617        hub: &Hub,
1618        rq: &mut RenderQueue,
1619        context: &mut Context,
1620    ) -> Result<(), Error> {
1621        let full_path = context.library.home.join(path);
1622        if full_path.exists() {
1623            let trash_path = context.library.home.join(TRASH_DIRNAME);
1624            if !trash_path.is_dir() {
1625                fs::create_dir(&trash_path)?;
1626            }
1627            let mut trash = Library::new(trash_path, LibraryMode::Database)?;
1628            context.library.move_to(path, &mut trash)?;
1629            let (mut files, _) = trash.list(&trash.home, None, false);
1630            let mut size = files.iter().map(|info| info.file.size).sum::<u64>();
1631            if size > context.settings.home.max_trash_size {
1632                sort(&mut files, SortMethod::Added, true);
1633                while size > context.settings.home.max_trash_size {
1634                    let info = files.pop().unwrap();
1635                    if let Err(e) = trash.remove(&info.file.path) {
1636                        error!("Can't erase {}: {:#}", info.file.path.display(), e);
1637                        break;
1638                    }
1639                    size -= info.file.size;
1640                }
1641            }
1642            trash.flush();
1643        } else {
1644            context.library.remove(path)?;
1645        }
1646        self.refresh_visibles(true, false, hub, rq, context);
1647        Ok(())
1648    }
1649
1650    fn copy_to(&mut self, path: &Path, index: usize, context: &mut Context) -> Result<(), Error> {
1651        let library_settings = &context.settings.libraries[index];
1652        let mut library = Library::new(&library_settings.path, library_settings.mode)?;
1653        context.library.copy_to(path, &mut library)?;
1654        library.flush();
1655        Ok(())
1656    }
1657
1658    fn move_to(
1659        &mut self,
1660        path: &Path,
1661        index: usize,
1662        hub: &Hub,
1663        rq: &mut RenderQueue,
1664        context: &mut Context,
1665    ) -> Result<(), Error> {
1666        let library_settings = &context.settings.libraries[index];
1667        let mut library = Library::new(&library_settings.path, library_settings.mode)?;
1668        context.library.move_to(path, &mut library)?;
1669        library.flush();
1670        self.refresh_visibles(true, false, hub, rq, context);
1671        Ok(())
1672    }
1673
1674    fn set_reverse_order(
1675        &mut self,
1676        value: bool,
1677        hub: &Hub,
1678        rq: &mut RenderQueue,
1679        context: &mut Context,
1680    ) {
1681        self.reverse_order = value;
1682        self.current_page = 0;
1683        self.sort(true, hub, rq, context);
1684    }
1685
1686    fn set_sort_method(
1687        &mut self,
1688        sort_method: SortMethod,
1689        hub: &Hub,
1690        rq: &mut RenderQueue,
1691        context: &mut Context,
1692    ) {
1693        self.sort_method = sort_method;
1694        self.reverse_order = sort_method.reverse_order();
1695
1696        if let Some(index) = locate_by_id(self, ViewId::SortMenu) {
1697            self.child_mut(index)
1698                .children_mut()
1699                .last_mut()
1700                .unwrap()
1701                .downcast_mut::<MenuEntry>()
1702                .unwrap()
1703                .update(sort_method.reverse_order(), rq);
1704        }
1705
1706        self.current_page = 0;
1707        self.sort(true, hub, rq, context);
1708    }
1709
1710    fn sort(&mut self, update: bool, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1711        context.library.sort(self.sort_method, self.reverse_order);
1712        sort(
1713            &mut self.visible_books,
1714            self.sort_method,
1715            self.reverse_order,
1716        );
1717
1718        if update {
1719            self.update_shelf(false, hub, rq, context);
1720            let search_visible = rlocate::<SearchBar>(self).is_some();
1721            self.update_top_bar(search_visible, rq);
1722            self.update_bottom_bar(rq, context);
1723        }
1724    }
1725
1726    fn load_library(
1727        &mut self,
1728        index: usize,
1729        hub: &Hub,
1730        rq: &mut RenderQueue,
1731        context: &mut Context,
1732    ) {
1733        if index == context.settings.selected_library {
1734            return;
1735        }
1736
1737        let library_settings = context.settings.libraries[index].clone();
1738        let library = Library::new(&library_settings.path, library_settings.mode)
1739            .map_err(|e| error!("Can't load library: {:#}.", e));
1740
1741        if library.is_err() {
1742            return;
1743        }
1744
1745        let library = library.unwrap();
1746
1747        let old_path = mem::take(&mut self.current_directory);
1748        self.terminate_fetchers(&old_path, false, hub, context);
1749
1750        let mut update_top_bar = false;
1751
1752        if self.query.is_some() {
1753            self.toggle_search_bar(Some(false), false, hub, rq, context);
1754            update_top_bar = true;
1755        }
1756
1757        context.library.flush();
1758
1759        context.library = library;
1760        context.settings.selected_library = index;
1761
1762        if self.sort_method != library_settings.sort_method {
1763            self.sort_method = library_settings.sort_method;
1764            self.reverse_order = library_settings.sort_method.reverse_order();
1765            update_top_bar = true;
1766        }
1767
1768        context.library.sort(self.sort_method, self.reverse_order);
1769
1770        if update_top_bar {
1771            let search_visible = rlocate::<SearchBar>(self).is_some();
1772            self.update_top_bar(search_visible, rq);
1773        }
1774
1775        if let Some(shelf) = self.children[self.shelf_index]
1776            .as_mut()
1777            .downcast_mut::<Shelf>()
1778        {
1779            shelf.set_first_column(library_settings.first_column);
1780            shelf.set_second_column(library_settings.second_column);
1781            shelf.set_thumbnail_previews(library_settings.thumbnail_previews);
1782        }
1783
1784        let home = context.library.home.clone();
1785        self.select_directory(&home, hub, rq, context);
1786    }
1787
1788    fn import(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1789        context.library.import(&context.settings.import);
1790        context.library.sort(self.sort_method, self.reverse_order);
1791        self.refresh_visibles(true, false, hub, rq, context);
1792    }
1793
1794    fn clean_up(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1795        context.library.clean_up();
1796        self.refresh_visibles(true, false, hub, rq, context);
1797    }
1798
1799    fn flush(&mut self, context: &mut Context) {
1800        context.library.flush();
1801    }
1802
1803    fn terminate_fetchers(&mut self, path: &Path, update: bool, hub: &Hub, context: &mut Context) {
1804        self.background_fetchers.retain(|id, fetcher| {
1805            if fetcher.full_path == path {
1806                unsafe { libc::kill(*id as libc::pid_t, libc::SIGTERM) };
1807                fetcher.process.wait().ok();
1808                if update {
1809                    if let Some(sort_method) = fetcher.sort_method {
1810                        hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1811                    }
1812                    if let Some(first_column) = fetcher.first_column {
1813                        hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1814                            .ok();
1815                    }
1816                    if let Some(second_column) = fetcher.second_column {
1817                        hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1818                            .ok();
1819                    }
1820                } else {
1821                    let selected_library = context.settings.selected_library;
1822                    if let Some(sort_method) = fetcher.sort_method {
1823                        context.settings.libraries[selected_library].sort_method = sort_method;
1824                    }
1825                    if let Some(first_column) = fetcher.first_column {
1826                        context.settings.libraries[selected_library].first_column = first_column;
1827                    }
1828                    if let Some(second_column) = fetcher.second_column {
1829                        context.settings.libraries[selected_library].second_column = second_column;
1830                    }
1831                }
1832                false
1833            } else {
1834                true
1835            }
1836        });
1837    }
1838
1839    fn insert_fetcher(&mut self, hook: &Hook, hub: &Hub, context: &Context) {
1840        let library_path = &context.library.home;
1841        let save_path = context.library.home.join(&hook.path);
1842        match self.spawn_child(
1843            library_path,
1844            &save_path,
1845            &hook.program,
1846            context.settings.wifi,
1847            context.online,
1848            hub,
1849        ) {
1850            Ok(process) => {
1851                let mut sort_method = hook.sort_method;
1852                let mut first_column = hook.first_column;
1853                let mut second_column = hook.second_column;
1854                if let Some(sort_method) = sort_method.replace(self.sort_method) {
1855                    hub.send(Event::Select(EntryId::Sort(sort_method))).ok();
1856                }
1857                let selected_library = context.settings.selected_library;
1858                if let Some(first_column) =
1859                    first_column.replace(context.settings.libraries[selected_library].first_column)
1860                {
1861                    hub.send(Event::Select(EntryId::FirstColumn(first_column)))
1862                        .ok();
1863                }
1864                if let Some(second_column) = second_column
1865                    .replace(context.settings.libraries[selected_library].second_column)
1866                {
1867                    hub.send(Event::Select(EntryId::SecondColumn(second_column)))
1868                        .ok();
1869                }
1870                self.background_fetchers.insert(
1871                    process.id(),
1872                    Fetcher {
1873                        path: hook.path.clone(),
1874                        full_path: save_path,
1875                        process,
1876                        sort_method,
1877                        first_column,
1878                        second_column,
1879                    },
1880                );
1881            }
1882            Err(e) => error!("Can't spawn child: {:#}.", e),
1883        }
1884    }
1885
1886    fn spawn_child(
1887        &mut self,
1888        library_path: &Path,
1889        save_path: &Path,
1890        program: &Path,
1891        wifi: bool,
1892        online: bool,
1893        hub: &Hub,
1894    ) -> Result<Child, Error> {
1895        let path = program.canonicalize()?;
1896        let parent = path.parent().unwrap_or_else(|| Path::new(""));
1897        let mut process = Command::new(&path)
1898            .current_dir(parent)
1899            .arg(library_path)
1900            .arg(save_path)
1901            .arg(wifi.to_string())
1902            .arg(online.to_string())
1903            .stdin(Stdio::piped())
1904            .stdout(Stdio::piped())
1905            .spawn()?;
1906        let stdout = process
1907            .stdout
1908            .take()
1909            .ok_or_else(|| format_err!("can't take stdout"))?;
1910        let id = process.id();
1911        let hub2 = hub.clone();
1912        thread::spawn(move || {
1913            let reader = BufReader::new(stdout);
1914            for line_res in reader.lines() {
1915                if let Ok(line) = line_res {
1916                    if let Ok(event) = serde_json::from_str::<JsonValue>(&line) {
1917                        match event.get("type").and_then(JsonValue::as_str) {
1918                            Some("notify") => {
1919                                if let Some(msg) = event.get("message").and_then(JsonValue::as_str)
1920                                {
1921                                    hub2.send(Event::Notify(msg.to_string())).ok();
1922                                }
1923                            }
1924                            Some("setWifi") => {
1925                                if let Some(enable) =
1926                                    event.get("enable").and_then(JsonValue::as_bool)
1927                                {
1928                                    hub2.send(Event::SetWifi(enable)).ok();
1929                                }
1930                            }
1931                            Some("addDocument") => {
1932                                if let Some(info) = event
1933                                    .get("info")
1934                                    .map(ToString::to_string)
1935                                    .and_then(|v| serde_json::from_str(&v).ok())
1936                                {
1937                                    hub2.send(Event::FetcherAddDocument(id, Box::new(info)))
1938                                        .ok();
1939                                }
1940                            }
1941                            Some("removeDocument") => {
1942                                if let Some(path) = event.get("path").and_then(JsonValue::as_str) {
1943                                    hub2.send(Event::FetcherRemoveDocument(
1944                                        id,
1945                                        PathBuf::from(path),
1946                                    ))
1947                                    .ok();
1948                                }
1949                            }
1950                            Some("search") => {
1951                                let path = event
1952                                    .get("path")
1953                                    .and_then(JsonValue::as_str)
1954                                    .map(PathBuf::from);
1955                                let query = event
1956                                    .get("query")
1957                                    .and_then(JsonValue::as_str)
1958                                    .map(String::from);
1959                                let sort_by = event
1960                                    .get("sortBy")
1961                                    .map(ToString::to_string)
1962                                    .and_then(|v| serde_json::from_str(&v).ok());
1963                                hub2.send(Event::FetcherSearch {
1964                                    id,
1965                                    path,
1966                                    query,
1967                                    sort_by,
1968                                })
1969                                .ok();
1970                            }
1971                            _ => (),
1972                        }
1973                    }
1974                } else {
1975                    break;
1976                }
1977            }
1978            hub2.send(Event::CheckFetcher(id)).ok();
1979        });
1980        Ok(process)
1981    }
1982
1983    fn reseed(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
1984        context.library.sort(self.sort_method, self.reverse_order);
1985        self.refresh_visibles(true, false, hub, &mut RenderQueue::new(), context);
1986
1987        if let Some(top_bar) = self.child_mut(0).downcast_mut::<TopBar>() {
1988            top_bar.reseed(&mut RenderQueue::new(), context);
1989        }
1990
1991        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
1992    }
1993}
1994
1995impl View for Home {
1996    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
1997    fn handle_event(
1998        &mut self,
1999        evt: &Event,
2000        hub: &Hub,
2001        _bus: &mut Bus,
2002        rq: &mut RenderQueue,
2003        context: &mut Context,
2004    ) -> bool {
2005        match *evt {
2006            Event::Gesture(GestureEvent::Swipe {
2007                dir, start, end, ..
2008            }) => {
2009                match dir {
2010                    Dir::South
2011                        if self.children[0].rect().includes(start)
2012                            && self.children[self.shelf_index].rect().includes(end) =>
2013                    {
2014                        if !context.settings.home.navigation_bar {
2015                            self.toggle_navigation_bar(Some(true), true, hub, rq, context);
2016                        } else if !context.settings.home.address_bar {
2017                            self.toggle_address_bar(Some(true), true, hub, rq, context);
2018                        }
2019                    }
2020                    Dir::North
2021                        if self.children[self.shelf_index].rect().includes(start)
2022                            && self.children[0].rect().includes(end) =>
2023                    {
2024                        if context.settings.home.address_bar {
2025                            self.toggle_address_bar(Some(false), true, hub, rq, context);
2026                        } else if context.settings.home.navigation_bar {
2027                            self.toggle_navigation_bar(Some(false), true, hub, rq, context);
2028                        }
2029                    }
2030                    _ => (),
2031                }
2032                true
2033            }
2034            Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
2035                let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
2036                let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
2037                hub.send(Event::Select(EntryId::Rotate(n))).ok();
2038                true
2039            }
2040            Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
2041                match dir {
2042                    Dir::West => self.go_to_page(0, hub, rq, context),
2043                    Dir::East => {
2044                        let pages_count = self.pages_count;
2045                        self.go_to_page(pages_count.saturating_sub(1), hub, rq, context);
2046                    }
2047                    Dir::North => {
2048                        let path = context.library.home.clone();
2049                        self.select_directory(&path, hub, rq, context);
2050                    }
2051                    Dir::South => self.toggle_search_bar(None, true, hub, rq, context),
2052                };
2053                true
2054            }
2055            Event::Gesture(GestureEvent::Corner { dir, .. }) => {
2056                match dir {
2057                    DiagDir::NorthWest | DiagDir::SouthWest => {
2058                        self.go_to_status_change(CycleDir::Previous, hub, rq, context)
2059                    }
2060                    DiagDir::NorthEast | DiagDir::SouthEast => {
2061                        self.go_to_status_change(CycleDir::Next, hub, rq, context)
2062                    }
2063                };
2064                true
2065            }
2066            Event::Focus(v) => {
2067                if self.focus != v {
2068                    self.focus = v;
2069                    if v.is_some() {
2070                        self.toggle_keyboard(true, true, v, hub, rq, context);
2071                    }
2072                }
2073                true
2074            }
2075            Event::Show(ViewId::Keyboard) => {
2076                self.toggle_keyboard(true, true, None, hub, rq, context);
2077                true
2078            }
2079            Event::Toggle(ViewId::GoToPage) => {
2080                self.toggle_go_to_page(None, hub, rq, context);
2081                true
2082            }
2083            Event::Toggle(ViewId::SearchBar) => {
2084                self.toggle_search_bar(None, true, hub, rq, context);
2085                true
2086            }
2087            Event::ToggleNear(ViewId::TitleMenu, rect) => {
2088                self.toggle_sort_menu(rect, None, rq, context);
2089                true
2090            }
2091            Event::ToggleBookMenu(rect, index) => {
2092                self.toggle_book_menu(index, rect, None, rq, context);
2093                true
2094            }
2095            Event::ToggleNear(ViewId::MainMenu, rect) => {
2096                toggle_main_menu(self, rect, None, rq, context);
2097                true
2098            }
2099            Event::ToggleNear(ViewId::BatteryMenu, rect) => {
2100                toggle_battery_menu(self, rect, None, rq, context);
2101                true
2102            }
2103            Event::ToggleNear(ViewId::ClockMenu, rect) => {
2104                toggle_clock_menu(self, rect, None, rq, context);
2105                true
2106            }
2107            Event::ToggleNear(ViewId::LibraryMenu, rect) => {
2108                self.toggle_library_menu(rect, None, rq, context);
2109                true
2110            }
2111            Event::Close(ViewId::AddressBar) => {
2112                self.toggle_address_bar(Some(false), true, hub, rq, context);
2113                true
2114            }
2115            Event::Close(ViewId::SearchBar) => {
2116                self.toggle_search_bar(Some(false), true, hub, rq, context);
2117                true
2118            }
2119            Event::Close(ViewId::SortMenu) => {
2120                self.toggle_sort_menu(Rectangle::default(), Some(false), rq, context);
2121                true
2122            }
2123            Event::Close(ViewId::LibraryMenu) => {
2124                self.toggle_library_menu(Rectangle::default(), Some(false), rq, context);
2125                true
2126            }
2127            Event::Close(ViewId::MainMenu) => {
2128                toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
2129                true
2130            }
2131            Event::Close(ViewId::GoToPage) => {
2132                self.toggle_go_to_page(Some(false), hub, rq, context);
2133                true
2134            }
2135            Event::Close(ViewId::RenameDocument) => {
2136                self.toggle_rename_document(Some(false), hub, rq, context);
2137                true
2138            }
2139            Event::Select(EntryId::Sort(sort_method)) => {
2140                let selected_library = context.settings.selected_library;
2141                context.settings.libraries[selected_library].sort_method = sort_method;
2142                self.set_sort_method(sort_method, hub, rq, context);
2143                true
2144            }
2145            Event::Select(EntryId::ReverseOrder) => {
2146                let next_value = !self.reverse_order;
2147                self.set_reverse_order(next_value, hub, rq, context);
2148                true
2149            }
2150            Event::Select(EntryId::LoadLibrary(index)) => {
2151                self.load_library(index, hub, rq, context);
2152                true
2153            }
2154            Event::Select(EntryId::Import) => {
2155                self.import(hub, rq, context);
2156                true
2157            }
2158            Event::Select(EntryId::CleanUp) => {
2159                self.clean_up(hub, rq, context);
2160                true
2161            }
2162            Event::Select(EntryId::Flush) => {
2163                self.flush(context);
2164                true
2165            }
2166            Event::FetcherAddDocument(_, ref info) => {
2167                self.add_document(*info.clone(), hub, rq, context);
2168                true
2169            }
2170            Event::Select(EntryId::SetStatus(ref path, status)) => {
2171                self.set_status(path, status, hub, rq, context);
2172                true
2173            }
2174            Event::Select(EntryId::FirstColumn(first_column)) => {
2175                let selected_library = context.settings.selected_library;
2176                context.settings.libraries[selected_library].first_column = first_column;
2177                self.update_first_column(hub, rq, context);
2178                true
2179            }
2180            Event::Select(EntryId::SecondColumn(second_column)) => {
2181                let selected_library = context.settings.selected_library;
2182                context.settings.libraries[selected_library].second_column = second_column;
2183                self.update_second_column(hub, rq, context);
2184                true
2185            }
2186            Event::Select(EntryId::ThumbnailPreviews) => {
2187                let selected_library = context.settings.selected_library;
2188                context.settings.libraries[selected_library].thumbnail_previews =
2189                    !context.settings.libraries[selected_library].thumbnail_previews;
2190                self.update_thumbnail_previews(hub, rq, context);
2191                true
2192            }
2193            Event::Submit(ViewId::AddressBarInput, ref addr) => {
2194                self.toggle_keyboard(false, true, None, hub, rq, context);
2195                self.select_directory(Path::new(addr), hub, rq, context);
2196                true
2197            }
2198            Event::Submit(ViewId::HomeSearchInput, ref text) => {
2199                self.query = BookQuery::new(text);
2200                if self.query.is_some() {
2201                    self.toggle_keyboard(false, false, None, hub, rq, context);
2202                    // Render the search bar and its separator.
2203                    for i in self.shelf_index + 1..=self.shelf_index + 2 {
2204                        rq.add(RenderData::new(
2205                            self.child(i).id(),
2206                            *self.child(i).rect(),
2207                            UpdateMode::Gui,
2208                        ));
2209                    }
2210                    self.refresh_visibles(true, true, hub, rq, context);
2211                } else {
2212                    let notif = Notification::new(
2213                        None,
2214                        "Invalid search query.".to_string(),
2215                        false,
2216                        hub,
2217                        rq,
2218                        context,
2219                    );
2220                    self.children.push(Box::new(notif) as Box<dyn View>);
2221                }
2222                true
2223            }
2224            Event::Submit(ViewId::GoToPageInput, ref text) => {
2225                if text == "(" {
2226                    self.go_to_page(0, hub, rq, context);
2227                } else if text == ")" {
2228                    self.go_to_page(self.pages_count.saturating_sub(1), hub, rq, context);
2229                } else if text == "_" {
2230                    let index = (context.rng.next_u64() % self.pages_count as u64) as usize;
2231                    self.go_to_page(index, hub, rq, context);
2232                } else if let Ok(index) = text.parse::<usize>() {
2233                    self.go_to_page(index.saturating_sub(1), hub, rq, context);
2234                }
2235                true
2236            }
2237            Event::Submit(ViewId::RenameDocumentInput, ref file_name) => {
2238                if let Some(ref path) = self.target_document.take() {
2239                    self.rename(path, file_name, hub, rq, context)
2240                        .map_err(|e| error!("Can't rename document: {:#}.", e))
2241                        .ok();
2242                }
2243                true
2244            }
2245            Event::NavigationBarResized(_) => {
2246                self.adjust_shelf_top_edge();
2247                self.update_shelf(true, hub, rq, context);
2248                self.update_bottom_bar(rq, context);
2249                for i in self.shelf_index - 2..=self.shelf_index - 1 {
2250                    rq.add(RenderData::new(
2251                        self.child(i).id(),
2252                        *self.child(i).rect(),
2253                        UpdateMode::Gui,
2254                    ));
2255                }
2256                true
2257            }
2258            Event::Select(EntryId::EmptyTrash) => {
2259                self.empty_trash(hub, rq, context);
2260                true
2261            }
2262            Event::Select(EntryId::Rename(ref path)) => {
2263                self.target_document = Some(path.clone());
2264                self.toggle_rename_document(Some(true), hub, rq, context);
2265                true
2266            }
2267            Event::Select(EntryId::Remove(ref path))
2268            | Event::FetcherRemoveDocument(_, ref path) => {
2269                self.remove(path, hub, rq, context)
2270                    .map_err(|e| error!("Can't remove document: {:#}.", e))
2271                    .ok();
2272                true
2273            }
2274            Event::Select(EntryId::CopyTo(ref path, index)) => {
2275                self.copy_to(path, index, context)
2276                    .map_err(|e| error!("Can't copy document: {:#}.", e))
2277                    .ok();
2278                true
2279            }
2280            Event::Select(EntryId::MoveTo(ref path, index)) => {
2281                self.move_to(path, index, hub, rq, context)
2282                    .map_err(|e| error!("Can't move document: {:#}.", e))
2283                    .ok();
2284                true
2285            }
2286            Event::Select(EntryId::ToggleShowHidden) => {
2287                context.library.show_hidden = !context.library.show_hidden;
2288                self.refresh_visibles(true, false, hub, rq, context);
2289                true
2290            }
2291            Event::SelectDirectory(ref path)
2292            | Event::Select(EntryId::SelectDirectory(ref path)) => {
2293                self.select_directory(path, hub, rq, context);
2294                true
2295            }
2296            Event::ToggleSelectDirectory(ref path)
2297            | Event::Select(EntryId::ToggleSelectDirectory(ref path)) => {
2298                self.toggle_select_directory(path, hub, rq, context);
2299                true
2300            }
2301            Event::Select(EntryId::SearchAuthor(ref author)) => {
2302                let text = format!("'a {}", author);
2303                let query = BookQuery::new(&text);
2304                if query.is_some() {
2305                    self.query = query;
2306                    self.toggle_search_bar(Some(true), false, hub, rq, context);
2307                    self.toggle_keyboard(false, false, None, hub, rq, context);
2308                    if let Some(search_bar) =
2309                        self.children[self.shelf_index + 2].downcast_mut::<SearchBar>()
2310                    {
2311                        search_bar.set_text(&text, rq, context);
2312                    }
2313                    // Render the search bar and its separator.
2314                    for i in self.shelf_index + 1..=self.shelf_index + 2 {
2315                        rq.add(RenderData::new(
2316                            self.child(i).id(),
2317                            *self.child(i).rect(),
2318                            UpdateMode::Gui,
2319                        ));
2320                    }
2321                    self.refresh_visibles(true, true, hub, rq, context);
2322                }
2323                true
2324            }
2325            Event::GoTo(location) => {
2326                self.go_to_page(location as usize, hub, rq, context);
2327                true
2328            }
2329            Event::Chapter(dir) => {
2330                let pages_count = self.pages_count;
2331                match dir {
2332                    CycleDir::Previous => self.go_to_page(0, hub, rq, context),
2333                    CycleDir::Next => {
2334                        self.go_to_page(pages_count.saturating_sub(1), hub, rq, context)
2335                    }
2336                }
2337                true
2338            }
2339            Event::Page(dir) => {
2340                self.go_to_neighbor(dir, hub, rq, context);
2341                true
2342            }
2343            Event::Device(DeviceEvent::Button {
2344                code: ButtonCode::Backward,
2345                status: ButtonStatus::Pressed,
2346                ..
2347            }) => {
2348                self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
2349                true
2350            }
2351            Event::Device(DeviceEvent::Button {
2352                code: ButtonCode::Forward,
2353                status: ButtonStatus::Pressed,
2354                ..
2355            }) => {
2356                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
2357                true
2358            }
2359            Event::Device(DeviceEvent::NetUp) => {
2360                for fetcher in self.background_fetchers.values_mut() {
2361                    if let Some(stdin) = fetcher.process.stdin.as_mut() {
2362                        writeln!(stdin, "{}", json!({"type": "network", "status": "up"})).ok();
2363                    }
2364                }
2365                true
2366            }
2367            Event::FetcherSearch {
2368                id,
2369                ref path,
2370                ref query,
2371                ref sort_by,
2372            } => {
2373                let path = path.as_ref().unwrap_or(&context.library.home);
2374                let query = query.as_ref().and_then(|text| BookQuery::new(text));
2375                let (mut files, _) = context.library.list(path, query.as_ref(), false);
2376                if let Some((sort_method, reverse_order)) = *sort_by {
2377                    sort(&mut files, sort_method, reverse_order);
2378                }
2379                for entry in &mut files {
2380                    // Let the *reader* field pass through.
2381                    mem::swap(&mut entry.reader, &mut entry.reader_info);
2382                }
2383                if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2384                    if let Some(stdin) = fetcher.process.stdin.as_mut() {
2385                        writeln!(
2386                            stdin,
2387                            "{}",
2388                            json!({"type": "search",
2389                                                     "results": files})
2390                        )
2391                        .ok();
2392                    }
2393                }
2394                true
2395            }
2396            Event::CheckFetcher(id) => {
2397                if let Some(fetcher) = self.background_fetchers.get_mut(&id) {
2398                    if let Ok(exit_status) = fetcher.process.wait() {
2399                        if !exit_status.success() {
2400                            let msg = format!(
2401                                "{}: abnormal process termination.",
2402                                fetcher.path.display()
2403                            );
2404                            let notif = Notification::new(None, msg, false, hub, rq, context);
2405                            self.children.push(Box::new(notif) as Box<dyn View>);
2406                        }
2407                    }
2408                }
2409                true
2410            }
2411            Event::ToggleFrontlight => {
2412                if let Some(index) = locate::<TopBar>(self) {
2413                    self.child_mut(index)
2414                        .downcast_mut::<TopBar>()
2415                        .unwrap()
2416                        .update_frontlight_icon(rq, context);
2417                }
2418                true
2419            }
2420            Event::Reseed => {
2421                self.reseed(hub, rq, context);
2422                true
2423            }
2424            _ => false,
2425        }
2426    }
2427
2428    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
2429    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
2430
2431    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
2432        let dpi = CURRENT_DEVICE.dpi;
2433        let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
2434        let (small_thickness, big_thickness) = halves(thickness);
2435        let (small_height, big_height) = (
2436            scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
2437            scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2438        );
2439
2440        self.children.retain(|child| !child.is::<Menu>());
2441
2442        // Top bar.
2443        let top_bar_rect = rect![
2444            rect.min.x,
2445            rect.min.y,
2446            rect.max.x,
2447            rect.min.y + small_height - small_thickness
2448        ];
2449        self.children[0].resize(top_bar_rect, hub, rq, context);
2450
2451        let separator_rect = rect![
2452            rect.min.x,
2453            rect.min.y + small_height - small_thickness,
2454            rect.max.x,
2455            rect.min.y + small_height + big_thickness
2456        ];
2457        self.children[1].resize(separator_rect, hub, rq, context);
2458
2459        let mut shelf_min_y = rect.min.y + small_height + big_thickness;
2460        let mut index = 2;
2461
2462        // Address bar.
2463        if context.settings.home.address_bar {
2464            self.children[index].resize(
2465                rect![
2466                    rect.min.x,
2467                    shelf_min_y,
2468                    rect.max.x,
2469                    shelf_min_y + small_height - thickness
2470                ],
2471                hub,
2472                rq,
2473                context,
2474            );
2475            shelf_min_y += small_height - thickness;
2476            index += 1;
2477
2478            self.children[index].resize(
2479                rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2480                hub,
2481                rq,
2482                context,
2483            );
2484            shelf_min_y += thickness;
2485            index += 1;
2486        }
2487
2488        // Navigation bar.
2489        if context.settings.home.navigation_bar {
2490            let count = if self.children[self.shelf_index + 2].is::<SearchBar>() {
2491                2
2492            } else {
2493                1
2494            };
2495            let nav_bar = self.children[index]
2496                .as_mut()
2497                .downcast_mut::<StackNavigationBar<DirectoryNavigationProvider>>()
2498                .unwrap();
2499            nav_bar.clear();
2500            nav_bar.resize(
2501                rect![
2502                    rect.min.x,
2503                    shelf_min_y,
2504                    rect.max.x,
2505                    shelf_min_y + small_height - thickness
2506                ],
2507                hub,
2508                rq,
2509                context,
2510            );
2511            nav_bar.vertical_limit =
2512                rect.max.y - count * small_height - big_height - small_thickness;
2513            nav_bar.set_selected(
2514                self.current_directory.clone(),
2515                &mut RenderQueue::new(),
2516                context,
2517            );
2518            shelf_min_y += nav_bar.rect().height() as i32;
2519            index += 1;
2520
2521            self.children[index].resize(
2522                rect![rect.min.x, shelf_min_y, rect.max.x, shelf_min_y + thickness],
2523                hub,
2524                rq,
2525                context,
2526            );
2527            shelf_min_y += thickness;
2528        }
2529
2530        // Bottom bar.
2531        let bottom_bar_index = rlocate::<BottomBar>(self).unwrap();
2532        index = bottom_bar_index;
2533
2534        let separator_rect = rect![
2535            rect.min.x,
2536            rect.max.y - small_height - small_thickness,
2537            rect.max.x,
2538            rect.max.y - small_height + big_thickness
2539        ];
2540        self.children[index - 1].resize(separator_rect, hub, rq, context);
2541
2542        let bottom_bar_rect = rect![
2543            rect.min.x,
2544            rect.max.y - small_height + big_thickness,
2545            rect.max.x,
2546            rect.max.y
2547        ];
2548        self.children[index].resize(bottom_bar_rect, hub, rq, context);
2549
2550        let mut shelf_max_y = rect.max.y - small_height - small_thickness;
2551
2552        if index - self.shelf_index > 2 {
2553            index -= 2;
2554            // Keyboard.
2555            if self.children[index].is::<Keyboard>() {
2556                let kb_rect = rect![
2557                    rect.min.x,
2558                    rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
2559                    rect.max.x,
2560                    rect.max.y - small_height - small_thickness
2561                ];
2562                self.children[index].resize(kb_rect, hub, rq, context);
2563                let s_max_y = self.children[index].rect().min.y;
2564                self.children[index - 1].resize(
2565                    rect![rect.min.x, s_max_y - thickness, rect.max.x, s_max_y],
2566                    hub,
2567                    rq,
2568                    context,
2569                );
2570                index -= 2;
2571            }
2572            // Search bar.
2573            if self.children[index].is::<SearchBar>() {
2574                let sp_rect = *self.children[index + 1].rect() - pt!(0, small_height);
2575                self.children[index].resize(
2576                    rect![
2577                        rect.min.x,
2578                        sp_rect.max.y,
2579                        rect.max.x,
2580                        sp_rect.max.y + small_height - thickness
2581                    ],
2582                    hub,
2583                    rq,
2584                    context,
2585                );
2586                self.children[index - 1].resize(sp_rect, hub, rq, context);
2587                shelf_max_y -= small_height;
2588            }
2589        }
2590
2591        // Shelf.
2592        let shelf_rect = rect![rect.min.x, shelf_min_y, rect.max.x, shelf_max_y];
2593        self.children[self.shelf_index].resize(shelf_rect, hub, rq, context);
2594
2595        self.update_shelf(true, hub, &mut RenderQueue::new(), context);
2596        self.update_bottom_bar(&mut RenderQueue::new(), context);
2597
2598        // Floating windows.
2599        for i in bottom_bar_index + 1..self.children.len() {
2600            self.children[i].resize(rect, hub, rq, context);
2601        }
2602
2603        self.rect = rect;
2604        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Full));
2605    }
2606
2607    fn rect(&self) -> &Rectangle {
2608        &self.rect
2609    }
2610
2611    fn rect_mut(&mut self) -> &mut Rectangle {
2612        &mut self.rect
2613    }
2614
2615    fn children(&self) -> &Vec<Box<dyn View>> {
2616        &self.children
2617    }
2618
2619    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
2620        &mut self.children
2621    }
2622
2623    fn id(&self) -> Id {
2624        self.id
2625    }
2626}
2627
2628#[cfg(test)]
2629mod tests {
2630    use super::*;
2631    use crate::battery::{Battery, FakeBattery};
2632    use crate::framebuffer::Pixmap;
2633    use crate::frontlight::{Frontlight, LightLevels};
2634    use crate::lightsensor::LightSensor;
2635    use std::env;
2636    use std::path::Path;
2637
2638    fn create_test_context() -> Context {
2639        let fb = Box::new(Pixmap::new(600, 800, 1)) as Box<dyn Framebuffer>;
2640        let battery = Box::new(FakeBattery::new()) as Box<dyn Battery>;
2641        let frontlight = Box::new(LightLevels::default()) as Box<dyn Frontlight>;
2642        let lightsensor = Box::new(0u16) as Box<dyn LightSensor>;
2643
2644        let fonts = crate::font::Fonts::load_from(
2645            Path::new(
2646                &env::var("TEST_ROOT_DIR").expect("TEST_ROOT_DIR must be set for this test."),
2647            )
2648            .to_path_buf(),
2649        )
2650        .expect(
2651            "Failed to load fonts. Tests require font files to be present. \
2652             Run tests from the project root directory.",
2653        );
2654
2655        Context::new(
2656            fb,
2657            None,
2658            crate::library::Library::new(Path::new("/tmp"), crate::settings::LibraryMode::Database)
2659                .unwrap(),
2660            crate::settings::Settings::default(),
2661            fonts,
2662            battery,
2663            frontlight,
2664            lightsensor,
2665        )
2666    }
2667
2668    #[test]
2669    fn test_toggle_address_bar_with_navigation_bar_maintains_separator_alignment() {
2670        let mut context = create_test_context();
2671        let (tx, _rx) = std::sync::mpsc::channel();
2672        let hub = tx;
2673        let mut rq = RenderQueue::new();
2674
2675        context.settings.home.navigation_bar = false;
2676        context.settings.home.address_bar = false;
2677
2678        let rect = rect![0, 0, 600, 800];
2679        let mut home = Home::new(rect, &hub, &mut rq, &mut context).unwrap();
2680
2681        home.toggle_navigation_bar(Some(true), false, &hub, &mut rq, &mut context);
2682        assert!(context.settings.home.navigation_bar);
2683
2684        let nav_bar_index =
2685            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2686        let separator_index = home.shelf_index - 1;
2687
2688        let nav_bar_bottom_before = home.children[nav_bar_index].rect().max.y;
2689        let separator_top_before = home.children[separator_index].rect().min.y;
2690        assert_eq!(
2691            nav_bar_bottom_before, separator_top_before,
2692            "Navigation bar and separator should be aligned before toggling address bar"
2693        );
2694
2695        home.toggle_address_bar(Some(true), false, &hub, &mut rq, &mut context);
2696        assert!(context.settings.home.address_bar);
2697
2698        let nav_bar_index =
2699            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2700        let separator_index = home.shelf_index - 1;
2701
2702        let nav_bar_bottom_after_enable = home.children[nav_bar_index].rect().max.y;
2703        let separator_top_after_enable = home.children[separator_index].rect().min.y;
2704        assert_eq!(
2705            nav_bar_bottom_after_enable, separator_top_after_enable,
2706            "Navigation bar and separator should remain aligned after enabling address bar"
2707        );
2708
2709        home.toggle_address_bar(Some(false), false, &hub, &mut rq, &mut context);
2710        assert!(!context.settings.home.address_bar);
2711
2712        let nav_bar_index =
2713            locate::<StackNavigationBar<DirectoryNavigationProvider>>(&home).unwrap();
2714        let separator_index = home.shelf_index - 1;
2715
2716        let nav_bar_bottom_after_disable = home.children[nav_bar_index].rect().max.y;
2717        let separator_top_after_disable = home.children[separator_index].rect().min.y;
2718        assert_eq!(
2719            nav_bar_bottom_after_disable, separator_top_after_disable,
2720            "Navigation bar and separator should remain aligned after disabling address bar"
2721        );
2722    }
2723}