cadmus_core/view/reader/
mod.rs

1mod bottom_bar;
2mod chapter_label;
3mod margin_cropper;
4mod results_bar;
5mod results_label;
6mod tool_bar;
7
8use self::bottom_bar::BottomBar;
9use self::margin_cropper::{MarginCropper, BUTTON_DIAMETER};
10use self::results_bar::ResultsBar;
11use self::tool_bar::ToolBar;
12use super::top_bar::{TopBar, TopBarVariant};
13use crate::color::{BLACK, WHITE};
14use crate::context::Context;
15use crate::device::CURRENT_DEVICE;
16use crate::document::epub::EpubDocumentStatic;
17use crate::document::html::HtmlDocument;
18use crate::document::{
19    annotations_as_html, bookmarks_as_html, toc_as_html, SimpleTocEntry, TocEntry, TocLocation,
20};
21use crate::document::{
22    open, BoundedText, Document, Location, Neighbors, TextLocation, BYTES_PER_PAGE,
23};
24use crate::font::family_names;
25use crate::font::Fonts;
26use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
27use crate::frontlight::LightLevels;
28use crate::geom::{halves, Axis, CycleDir, DiagDir, Dir, LinearDir, Region};
29use crate::geom::{BorderSpec, Boundary, CornerSpec, Point, Rectangle, Vec2};
30use crate::gesture::GestureEvent;
31use crate::helpers::AsciiExtension;
32use crate::input::{ButtonCode, ButtonStatus, DeviceEvent, FingerStatus};
33use crate::metadata::{make_query, CroppingMargins, Margin};
34use crate::metadata::{
35    Annotation, FileInfo, Info, PageScheme, ReaderInfo, ScrollMode, TextAlign, ZoomMode,
36};
37use crate::metadata::{DEFAULT_CONTRAST_EXPONENT, DEFAULT_CONTRAST_GRAY};
38use crate::settings::{
39    guess_frontlight, BottomRightGestureAction, EastStripAction, FinishedAction,
40    SouthEastCornerAction, SouthStripAction, WestStripAction,
41};
42use crate::settings::{
43    DEFAULT_FONT_FAMILY, DEFAULT_LINE_HEIGHT, DEFAULT_MARGIN_WIDTH, DEFAULT_TEXT_ALIGN,
44};
45use crate::settings::{HYPHEN_PENALTY, STRETCH_TOLERANCE};
46use crate::unit::{mm_to_px, scale_by_dpi};
47use crate::view::common::{locate, locate_by_id, rlocate};
48use crate::view::common::{toggle_battery_menu, toggle_clock_menu, toggle_main_menu};
49use crate::view::filler::Filler;
50use crate::view::keyboard::Keyboard;
51use crate::view::menu::{Menu, MenuKind};
52use crate::view::menu_entry::MenuEntry;
53use crate::view::named_input::NamedInput;
54use crate::view::notification::Notification;
55use crate::view::search_bar::SearchBar;
56use crate::view::{AppCmd, Bus, Event, Hub, RenderData, RenderQueue, ToggleEvent, View};
57use crate::view::{EntryId, EntryKind, Id, SliderId, ViewId, ID_FEEDER};
58use crate::view::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
59use chrono::Local;
60use fxhash::{FxHashMap, FxHashSet};
61use rand_core::Rng;
62use regex::Regex;
63use septem::prelude::*;
64use septem::{Digit, Roman};
65use std::collections::{BTreeMap, VecDeque};
66use std::fs::OpenOptions;
67use std::io::prelude::*;
68use std::path::PathBuf;
69use std::sync::atomic::AtomicBool;
70use std::sync::atomic::Ordering as AtomicOrdering;
71use std::sync::{Arc, Mutex};
72use std::thread;
73use tracing::{debug, error, info, warn};
74
75const HISTORY_SIZE: usize = 32;
76const RECT_DIST_JITTER: f32 = 24.0;
77const ANNOTATION_DRIFT: u8 = 0x44;
78const HIGHLIGHT_DRIFT: u8 = 0x22;
79const MEM_SCHEME: &str = "mem:";
80
81pub struct Reader {
82    id: Id,
83    rect: Rectangle,
84    children: Vec<Box<dyn View>>,
85    doc: Arc<Mutex<Box<dyn Document>>>,
86    cache: BTreeMap<usize, Resource>,         // Cached page pixmaps.
87    chunks: Vec<RenderChunk>,                 // Chunks of pages being rendered.
88    text: FxHashMap<usize, Vec<BoundedText>>, // Text of the current chunks.
89    annotations: FxHashMap<usize, Vec<Annotation>>, // Annotations for the current chunks.
90    noninverted_regions: FxHashMap<usize, Vec<Boundary>>,
91    focus: Option<ViewId>,
92    search: Option<Search>,
93    search_direction: LinearDir,
94    held_buttons: FxHashSet<ButtonCode>,
95    selection: Option<Selection>,
96    target_annotation: Option<[TextLocation; 2]>,
97    history: VecDeque<usize>,
98    state: State,
99    info: Info,
100    current_page: usize,
101    pages_count: usize,
102    view_port: ViewPort,
103    contrast: Contrast,
104    synthetic: bool,
105    page_turns: usize,
106    reflowable: bool,
107    ephemeral: bool,
108    finished: bool,
109}
110
111struct ViewPort {
112    zoom_mode: ZoomMode,
113    scroll_mode: ScrollMode,
114    page_offset: Point, // Offset relative to the top left corner of a resource's frame.
115    margin_width: i32,
116}
117
118impl Default for ViewPort {
119    fn default() -> Self {
120        ViewPort {
121            zoom_mode: ZoomMode::FitToPage,
122            scroll_mode: ScrollMode::Screen,
123            page_offset: pt!(0, 0),
124            margin_width: 0,
125        }
126    }
127}
128
129#[derive(Debug, Copy, Clone, Eq, PartialEq)]
130enum State {
131    Idle,
132    Selection(i32),
133    AdjustSelection,
134}
135
136struct Selection {
137    start: TextLocation,
138    end: TextLocation,
139    anchor: TextLocation,
140}
141
142struct Resource {
143    pixmap: Pixmap,
144    frame: Rectangle, // The pixmap's rectangle minus the cropping margins.
145    scale: f32,
146}
147
148#[derive(Debug, Clone)]
149struct RenderChunk {
150    location: usize,
151    frame: Rectangle, // A subrectangle of the corresponding resource's frame.
152    position: Point,
153    scale: f32,
154}
155
156struct Search {
157    query: String,
158    highlights: BTreeMap<usize, Vec<Vec<Boundary>>>,
159    running: Arc<AtomicBool>,
160    current_page: usize,
161    results_count: usize,
162}
163
164impl Default for Search {
165    fn default() -> Self {
166        Search {
167            query: String::new(),
168            highlights: BTreeMap::new(),
169            running: Arc::new(AtomicBool::new(true)),
170            current_page: 0,
171            results_count: 0,
172        }
173    }
174}
175
176struct Contrast {
177    exponent: f32,
178    gray: f32,
179}
180
181impl Default for Contrast {
182    fn default() -> Contrast {
183        Contrast {
184            exponent: DEFAULT_CONTRAST_EXPONENT,
185            gray: DEFAULT_CONTRAST_GRAY,
186        }
187    }
188}
189
190fn scaling_factor(
191    rect: &Rectangle,
192    cropping_margin: &Margin,
193    screen_margin_width: i32,
194    dims: (f32, f32),
195    zoom_mode: ZoomMode,
196) -> f32 {
197    if let ZoomMode::Custom(sf) = zoom_mode {
198        return sf;
199    }
200
201    let (page_width, page_height) = dims;
202    let surface_width = (rect.width() as i32 - 2 * screen_margin_width) as f32;
203    let frame_width = (1.0 - (cropping_margin.left + cropping_margin.right)) * page_width;
204    let width_ratio = surface_width / frame_width;
205    match zoom_mode {
206        ZoomMode::FitToPage => {
207            let surface_height = (rect.height() as i32 - 2 * screen_margin_width) as f32;
208            let frame_height = (1.0 - (cropping_margin.top + cropping_margin.bottom)) * page_height;
209            let height_ratio = surface_height / frame_height;
210            width_ratio.min(height_ratio)
211        }
212        ZoomMode::FitToWidth => width_ratio,
213        ZoomMode::Custom(_) => unreachable!(),
214    }
215}
216
217fn build_pixmap(rect: &Rectangle, doc: &mut dyn Document, location: usize) -> (Pixmap, usize) {
218    let scale = scaling_factor(
219        rect,
220        &Margin::default(),
221        0,
222        doc.dims(location).unwrap(),
223        ZoomMode::FitToPage,
224    );
225    doc.pixmap(
226        Location::Exact(location),
227        scale,
228        CURRENT_DEVICE.color_samples(),
229    )
230    .unwrap()
231}
232
233fn find_cut(
234    frame: &Rectangle,
235    y_pos: i32,
236    scale: f32,
237    dir: LinearDir,
238    lines: &[BoundedText],
239) -> Option<i32> {
240    let y_pos_u = y_pos as f32 / scale;
241    let frame_u = frame.to_boundary() / scale;
242    let mut rect_a: Option<Boundary> = None;
243    let max_line_height = frame_u.height() / 10.0;
244
245    for line in lines {
246        if frame_u.contains(&line.rect)
247            && line.rect.height() <= max_line_height
248            && y_pos_u >= line.rect.min.y
249            && y_pos_u < line.rect.max.y
250        {
251            rect_a = Some(line.rect);
252            break;
253        }
254    }
255
256    rect_a.map(|ra| {
257        if dir == LinearDir::Backward {
258            (scale * ra.min.y).floor() as i32
259        } else {
260            (scale * ra.max.y).ceil() as i32
261        }
262    })
263}
264
265fn word_separator(lang: &str) -> &'static str {
266    let l = lang.to_ascii_lowercase();
267    match l.as_str() {
268        // https://en.wikipedia.org/wiki/Scriptio_continua
269        // Japanese, Chinese, Burmese, Lao, Khmer, Thai, Bengali, Javanese, Sundanese.
270        "ja" | "zh" | "my" | "lo" | "km" | "th" | "bn" | "jv" | "su" => "",
271        _ => " ",
272    }
273}
274
275impl Reader {
276    pub fn new(
277        rect: Rectangle,
278        mut info: Info,
279        hub: &Hub,
280        context: &mut Context,
281    ) -> Option<Reader> {
282        let id = ID_FEEDER.next();
283        let settings = &context.settings;
284        let path = if !info.file.absolute_path.as_os_str().is_empty() {
285            info.file.absolute_path.clone()
286        } else {
287            context.library.home.join(&info.file.path)
288        };
289
290        debug!(
291            resolved_path = %path.display(),
292            file_exists = path.exists(),
293            "Opening document"
294        );
295
296        let doc = open(&path);
297        if doc.is_none() {
298            warn!(
299                resolved_path = %path.display(),
300                file_exists = path.exists(),
301                "Failed to open document: open() returned None"
302            );
303        }
304        doc.and_then(|mut doc| {
305            let (width, height) = context.display.dims;
306            let font_size = info
307                .reader
308                .as_ref()
309                .and_then(|r| r.font_size)
310                .unwrap_or(settings.reader.font_size);
311
312            doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
313
314            let margin_width = info
315                .reader
316                .as_ref()
317                .and_then(|r| r.margin_width)
318                .unwrap_or(settings.reader.margin_width);
319
320            if margin_width != DEFAULT_MARGIN_WIDTH {
321                doc.set_margin_width(margin_width);
322            }
323
324            let font_family = info
325                .reader
326                .as_ref()
327                .and_then(|r| r.font_family.as_ref())
328                .unwrap_or(&settings.reader.font_family);
329
330            if font_family != DEFAULT_FONT_FAMILY {
331                doc.set_font_family(font_family, &settings.reader.font_path);
332            }
333
334            let line_height = info
335                .reader
336                .as_ref()
337                .and_then(|r| r.line_height)
338                .unwrap_or(settings.reader.line_height);
339
340            if (line_height - DEFAULT_LINE_HEIGHT).abs() > f32::EPSILON {
341                doc.set_line_height(line_height);
342            }
343
344            let text_align = info
345                .reader
346                .as_ref()
347                .and_then(|r| r.text_align)
348                .unwrap_or(settings.reader.text_align);
349
350            if text_align != DEFAULT_TEXT_ALIGN {
351                doc.set_text_align(text_align);
352            }
353
354            let hyphen_penalty = settings.reader.paragraph_breaker.hyphen_penalty;
355
356            if hyphen_penalty != HYPHEN_PENALTY {
357                doc.set_hyphen_penalty(hyphen_penalty);
358            }
359
360            let stretch_tolerance = settings.reader.paragraph_breaker.stretch_tolerance;
361
362            if stretch_tolerance != STRETCH_TOLERANCE {
363                doc.set_stretch_tolerance(stretch_tolerance);
364            }
365
366            if settings.reader.ignore_document_css {
367                doc.set_ignore_document_css(true);
368            }
369
370            let first_location = doc.resolve_location(Location::Exact(0));
371            if first_location.is_none() {
372                warn!(
373                    resolved_path = %path.display(),
374                    "Document opened but resolve_location(Exact(0)) returned None"
375                );
376            }
377            let first_location = first_location?;
378
379            let mut view_port = ViewPort::default();
380            let mut contrast = Contrast::default();
381            let pages_count = doc.pages_count();
382            let current_page;
383
384            // TODO: use get_or_insert_with?
385            if let Some(ref mut r) = info.reader {
386                r.opened = Local::now().naive_local();
387
388                if r.finished {
389                    r.finished = false;
390                    r.current_page = first_location;
391                    r.page_offset = None;
392                }
393
394                current_page = doc
395                    .resolve_location(Location::Exact(r.current_page))
396                    .unwrap_or(first_location);
397
398                if let Some(zoom_mode) = r.zoom_mode {
399                    view_port.zoom_mode = zoom_mode;
400                }
401
402                if let Some(scroll_mode) = r.scroll_mode {
403                    view_port.scroll_mode = scroll_mode;
404                } else {
405                    view_port.scroll_mode = if settings.reader.continuous_fit_to_width {
406                        ScrollMode::Screen
407                    } else {
408                        ScrollMode::Page
409                    };
410                }
411
412                if let Some(page_offset) = r.page_offset {
413                    view_port.page_offset = page_offset;
414                }
415
416                if !doc.is_reflowable() {
417                    view_port.margin_width = mm_to_px(
418                        r.screen_margin_width.unwrap_or(0) as f32,
419                        CURRENT_DEVICE.dpi,
420                    ) as i32;
421                }
422
423                if let Some(exponent) = r.contrast_exponent {
424                    contrast.exponent = exponent;
425                }
426
427                if let Some(gray) = r.contrast_gray {
428                    contrast.gray = gray;
429                }
430            } else {
431                current_page = first_location;
432
433                info.reader = Some(ReaderInfo {
434                    current_page,
435                    pages_count,
436                    ..Default::default()
437                });
438            }
439
440            let synthetic = doc.has_synthetic_page_numbers();
441            let reflowable = doc.is_reflowable();
442
443            if info.toc.is_none() {
444                if let Some(toc) = doc.toc() {
445                    let simple_toc: Vec<SimpleTocEntry> =
446                        toc.iter().map(SimpleTocEntry::from).collect();
447                    context
448                        .library
449                        .sync_toc(&info.file.path, simple_toc.clone());
450                    info.toc = Some(simple_toc);
451                }
452            }
453
454            info!("{}", info.file.path.display());
455
456            hub.send(Event::Update(UpdateMode::Partial)).ok();
457
458            Some(Reader {
459                id,
460                rect,
461                children: Vec::new(),
462                doc: Arc::new(Mutex::new(doc)),
463                cache: BTreeMap::new(),
464                chunks: Vec::new(),
465                text: FxHashMap::default(),
466                annotations: FxHashMap::default(),
467                noninverted_regions: FxHashMap::default(),
468                focus: None,
469                search: None,
470                search_direction: LinearDir::Forward,
471                held_buttons: FxHashSet::default(),
472                selection: None,
473                target_annotation: None,
474                history: VecDeque::new(),
475                state: State::Idle,
476                info,
477                current_page,
478                pages_count,
479                view_port,
480                synthetic,
481                page_turns: 0,
482                contrast,
483                ephemeral: false,
484                reflowable,
485                finished: false,
486            })
487        })
488    }
489
490    pub fn from_html(
491        rect: Rectangle,
492        html: &str,
493        link_uri: Option<&str>,
494        hub: &Hub,
495        context: &mut Context,
496    ) -> Reader {
497        let id = ID_FEEDER.next();
498
499        let mut info = Info {
500            file: FileInfo {
501                path: PathBuf::from(MEM_SCHEME),
502                kind: "html".to_string(),
503                size: html.len() as u64,
504                ..Default::default()
505            },
506            ..Default::default()
507        };
508
509        let mut doc = HtmlDocument::new_from_memory(html);
510        let (width, height) = context.display.dims;
511        let font_size = context.settings.reader.font_size;
512        doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
513        let pages_count = doc.pages_count();
514        info.title = doc.title().unwrap_or_default();
515
516        let mut current_page = 0;
517        if let Some(link_uri) = link_uri {
518            let mut loc = Location::Exact(0);
519            while let Some((links, offset)) = doc.links(loc) {
520                if links.iter().any(|link| link.text == link_uri) {
521                    current_page = offset;
522                    break;
523                }
524                loc = Location::Next(offset);
525            }
526        }
527
528        hub.send(Event::Update(UpdateMode::Partial)).ok();
529
530        Reader {
531            id,
532            rect,
533            children: Vec::new(),
534            doc: Arc::new(Mutex::new(Box::new(doc))),
535            cache: BTreeMap::new(),
536            chunks: Vec::new(),
537            text: FxHashMap::default(),
538            annotations: FxHashMap::default(),
539            noninverted_regions: FxHashMap::default(),
540            focus: None,
541            search: None,
542            search_direction: LinearDir::Forward,
543            held_buttons: FxHashSet::default(),
544            selection: None,
545            target_annotation: None,
546            history: VecDeque::new(),
547            state: State::Idle,
548            info,
549            current_page,
550            pages_count,
551            view_port: ViewPort::default(),
552            synthetic: true,
553            page_turns: 0,
554            contrast: Contrast::default(),
555            ephemeral: true,
556            reflowable: true,
557            finished: false,
558        }
559    }
560
561    #[cfg_attr(feature = "otel", tracing::instrument(skip_all))]
562    pub fn from_embedded_epub(
563        rect: Rectangle,
564        epub_bytes: &'static [u8],
565        hub: &Hub,
566        context: &mut Context,
567    ) -> Option<Reader> {
568        let id = ID_FEEDER.next();
569
570        let mut doc = EpubDocumentStatic::new_from_static(epub_bytes).ok()?;
571
572        let info = Info {
573            file: FileInfo {
574                path: PathBuf::from("mem:documentation.epub"),
575                kind: "epub".to_string(),
576                size: epub_bytes.len() as u64,
577                ..Default::default()
578            },
579            title: doc.title().unwrap_or_default(),
580            ..Default::default()
581        };
582
583        let (width, height) = context.display.dims;
584        doc.layout(width, height, 7.0, CURRENT_DEVICE.dpi);
585        doc.set_margin_width(mm_to_px(0.0, CURRENT_DEVICE.dpi) as i32);
586        let pages_count = doc.pages_count();
587
588        hub.send(Event::Update(UpdateMode::Partial)).ok();
589
590        Some(Reader {
591            id,
592            rect,
593            children: Vec::new(),
594            doc: Arc::new(Mutex::new(Box::new(doc))),
595            cache: BTreeMap::new(),
596            chunks: Vec::new(),
597            text: FxHashMap::default(),
598            annotations: FxHashMap::default(),
599            noninverted_regions: FxHashMap::default(),
600            focus: None,
601            search: None,
602            search_direction: LinearDir::Forward,
603            held_buttons: FxHashSet::default(),
604            selection: None,
605            target_annotation: None,
606            history: VecDeque::new(),
607            state: State::Idle,
608            info,
609            current_page: 0,
610            pages_count,
611            view_port: ViewPort::default(),
612            synthetic: true,
613            page_turns: 0,
614            contrast: Contrast::default(),
615            ephemeral: true,
616            reflowable: true,
617            finished: false,
618        })
619    }
620
621    fn load_pixmap(&mut self, location: usize) {
622        if self.cache.contains_key(&location) {
623            return;
624        }
625
626        let mut doc = self.doc.lock().unwrap();
627        let cropping_margin = self
628            .info
629            .reader
630            .as_ref()
631            .and_then(|r| r.cropping_margins.as_ref().map(|c| c.margin(location)))
632            .cloned()
633            .unwrap_or_default();
634        let dims = doc.dims(location).unwrap_or((3.0, 4.0));
635        let screen_margin_width = self.view_port.margin_width;
636        let scale = scaling_factor(
637            &self.rect,
638            &cropping_margin,
639            screen_margin_width,
640            dims,
641            self.view_port.zoom_mode,
642        );
643        if let Some((pixmap, _)) = doc.pixmap(
644            Location::Exact(location),
645            scale,
646            CURRENT_DEVICE.color_samples(),
647        ) {
648            let frame = rect![
649                (cropping_margin.left * pixmap.width as f32).ceil() as i32,
650                (cropping_margin.top * pixmap.height as f32).ceil() as i32,
651                ((1.0 - cropping_margin.right) * pixmap.width as f32).floor() as i32,
652                ((1.0 - cropping_margin.bottom) * pixmap.height as f32).floor() as i32
653            ];
654            self.cache.insert(
655                location,
656                Resource {
657                    pixmap,
658                    frame,
659                    scale,
660                },
661            );
662        } else {
663            let width = (dims.0 as f32 * scale).max(1.0) as u32;
664            let height = (dims.1 as f32 * scale).max(1.0) as u32;
665            let pixmap = Pixmap::empty(width, height, CURRENT_DEVICE.color_samples());
666            let frame = pixmap.rect();
667            self.cache.insert(
668                location,
669                Resource {
670                    pixmap,
671                    frame,
672                    scale,
673                },
674            );
675        }
676    }
677
678    fn load_text(&mut self, location: usize) {
679        if self.text.contains_key(&location) {
680            return;
681        }
682
683        let mut doc = self.doc.lock().unwrap();
684        let loc = Location::Exact(location);
685        let words = doc.words(loc).map(|(words, _)| words).unwrap_or_default();
686        self.text.insert(location, words);
687    }
688
689    fn go_to_page(
690        &mut self,
691        location: usize,
692        record: bool,
693        hub: &Hub,
694        rq: &mut RenderQueue,
695        context: &Context,
696    ) {
697        let loc = {
698            let mut doc = self.doc.lock().unwrap();
699            doc.resolve_location(Location::Exact(location))
700        };
701
702        if let Some(location) = loc {
703            if record {
704                self.history.push_back(self.current_page);
705                if self.history.len() > HISTORY_SIZE {
706                    self.history.pop_front();
707                }
708            }
709
710            if let Some(ref mut s) = self.search {
711                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
712            }
713
714            self.current_page = location;
715            self.view_port.page_offset = pt!(0);
716            self.selection = None;
717            self.state = State::Idle;
718            self.update(None, hub, rq, context);
719            self.update_bottom_bar(rq);
720
721            if self.search.is_some() {
722                self.update_results_bar(rq);
723            }
724        }
725    }
726
727    fn go_to_chapter(&mut self, dir: CycleDir, hub: &Hub, rq: &mut RenderQueue, context: &Context) {
728        let current_page = self.current_page;
729        let loc = {
730            let mut doc = self.doc.lock().unwrap();
731            if let Some(toc) = self.toc().or_else(|| doc.toc()) {
732                let chap_offset = if dir == CycleDir::Previous {
733                    doc.chapter(current_page, &toc)
734                        .and_then(|(chap, _)| doc.resolve_location(chap.location.clone()))
735                        .and_then(|chap_offset| {
736                            if chap_offset < current_page {
737                                Some(chap_offset)
738                            } else {
739                                None
740                            }
741                        })
742                } else {
743                    None
744                };
745                chap_offset.or_else(|| {
746                    doc.chapter_relative(current_page, dir, &toc)
747                        .and_then(|rel_chap| doc.resolve_location(rel_chap.location.clone()))
748                })
749            } else {
750                None
751            }
752        };
753        if let Some(location) = loc {
754            self.go_to_page(location, true, hub, rq, context);
755        }
756    }
757
758    fn text_location_range(&self) -> Option<[TextLocation; 2]> {
759        let mut min_loc = None;
760        let mut max_loc = None;
761        for chunk in &self.chunks {
762            for word in &self.text[&chunk.location] {
763                let rect = (word.rect * chunk.scale).to_rect();
764                if rect.overlaps(&chunk.frame) {
765                    if let Some(ref mut min) = min_loc {
766                        if word.location < *min {
767                            *min = word.location;
768                        }
769                    } else {
770                        min_loc = Some(word.location);
771                    }
772                    if let Some(ref mut max) = max_loc {
773                        if word.location > *max {
774                            *max = word.location;
775                        }
776                    } else {
777                        max_loc = Some(word.location);
778                    }
779                }
780            }
781        }
782
783        min_loc.and_then(|min| max_loc.map(|max| [min, max]))
784    }
785
786    fn go_to_bookmark(
787        &mut self,
788        dir: CycleDir,
789        hub: &Hub,
790        rq: &mut RenderQueue,
791        context: &Context,
792    ) {
793        let loc_bkm = self.info.reader.as_ref().and_then(|r| match dir {
794            CycleDir::Next => r.bookmarks.range(self.current_page + 1..).next().cloned(),
795            CycleDir::Previous => r.bookmarks.range(..self.current_page).next_back().cloned(),
796        });
797
798        if let Some(location) = loc_bkm {
799            self.go_to_page(location, true, hub, rq, context);
800        }
801    }
802
803    fn go_to_annotation(
804        &mut self,
805        dir: CycleDir,
806        hub: &Hub,
807        rq: &mut RenderQueue,
808        context: &Context,
809    ) {
810        let loc_annot = self.info.reader.as_ref().and_then(|r| match dir {
811            CycleDir::Next => self.text_location_range().and_then(|[_, max]| {
812                r.annotations
813                    .iter()
814                    .filter(|annot| annot.selection[0] > max)
815                    .map(|annot| annot.selection[0])
816                    .min()
817                    .map(|tl| tl.location())
818            }),
819            CycleDir::Previous => self.text_location_range().and_then(|[min, _]| {
820                r.annotations
821                    .iter()
822                    .filter(|annot| annot.selection[1] < min)
823                    .map(|annot| annot.selection[1])
824                    .max()
825                    .map(|tl| tl.location())
826            }),
827        });
828
829        if let Some(location) = loc_annot {
830            self.go_to_page(location, true, hub, rq, context);
831        }
832    }
833
834    fn go_to_last_page(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &Context) {
835        if let Some(location) = self.history.pop_back() {
836            self.go_to_page(location, false, hub, rq, context);
837        }
838    }
839
840    fn vertical_scroll(
841        &mut self,
842        delta_y: i32,
843        hub: &Hub,
844        rq: &mut RenderQueue,
845        context: &mut Context,
846    ) {
847        if delta_y == 0 || self.view_port.zoom_mode == ZoomMode::FitToPage || self.cache.is_empty()
848        {
849            return;
850        }
851
852        let mut next_top_offset = self.view_port.page_offset.y + delta_y;
853        let mut location = self.current_page;
854
855        match self.view_port.scroll_mode {
856            ScrollMode::Screen => {
857                let max_top_offset = self.cache[&location].frame.height().saturating_sub(1) as i32;
858
859                if next_top_offset < 0 {
860                    let mut doc = self.doc.lock().unwrap();
861                    if let Some(previous_location) =
862                        doc.resolve_location(Location::Previous(location))
863                    {
864                        if !self.cache.contains_key(&previous_location) {
865                            return;
866                        }
867                        location = previous_location;
868                        let frame = self.cache[&location].frame;
869                        next_top_offset = (frame.height() as i32 + next_top_offset).max(0);
870                    } else {
871                        next_top_offset = 0;
872                    }
873                } else if next_top_offset > max_top_offset {
874                    let mut doc = self.doc.lock().unwrap();
875                    if let Some(next_location) = doc.resolve_location(Location::Next(location)) {
876                        if !self.cache.contains_key(&next_location) {
877                            return;
878                        }
879                        location = next_location;
880                        let frame = self.cache[&location].frame;
881                        let mto = frame.height().saturating_sub(1) as i32;
882                        next_top_offset = (next_top_offset - max_top_offset - 1).min(mto);
883                    } else {
884                        next_top_offset = max_top_offset;
885                    }
886                }
887
888                {
889                    let Resource { frame, scale, .. } = *self.cache.get(&location).unwrap();
890                    let mut doc = self.doc.lock().unwrap();
891                    if let Some((lines, _)) = doc.lines(Location::Exact(location)) {
892                        if let Some(mut y_pos) = find_cut(
893                            &frame,
894                            frame.min.y + next_top_offset,
895                            scale,
896                            LinearDir::Forward,
897                            &lines,
898                        ) {
899                            y_pos = y_pos.clamp(frame.min.y, frame.max.y - 1);
900                            next_top_offset = y_pos - frame.min.y;
901                        }
902                    }
903                }
904            }
905            ScrollMode::Page => {
906                let frame_height = self.cache[&location].frame.height() as i32;
907                let available_height = self.rect.height() as i32 - 2 * self.view_port.margin_width;
908                if frame_height > available_height {
909                    next_top_offset = next_top_offset.max(0).min(frame_height - available_height);
910                } else {
911                    next_top_offset = self.view_port.page_offset.y;
912                }
913            }
914        }
915
916        let location_changed = location != self.current_page;
917        if !location_changed && next_top_offset == self.view_port.page_offset.y {
918            return;
919        }
920
921        self.view_port.page_offset.y = next_top_offset;
922        self.current_page = location;
923        self.update(None, hub, rq, context);
924
925        if location_changed {
926            if let Some(ref mut s) = self.search {
927                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
928            }
929            self.update_bottom_bar(rq);
930            if self.search.is_some() {
931                self.update_results_bar(rq);
932            }
933        }
934    }
935
936    fn directional_scroll(
937        &mut self,
938        delta: Point,
939        hub: &Hub,
940        rq: &mut RenderQueue,
941        context: &mut Context,
942    ) {
943        if delta == pt!(0) || self.cache.is_empty() {
944            return;
945        }
946
947        let Resource { frame, .. } = self.cache[&self.current_page];
948        let next_page_offset = self.view_port.page_offset + delta;
949        let vpw = self.rect.width() as i32 - 2 * self.view_port.margin_width;
950        let vph = self.rect.height() as i32 - 2 * self.view_port.margin_width;
951        let vprect = rect![pt!(0), pt!(vpw, vph)] + next_page_offset + frame.min;
952
953        if vprect.overlaps(&frame) {
954            self.view_port.page_offset = next_page_offset;
955            self.update(None, hub, rq, context);
956        }
957    }
958
959    fn go_to_neighbor(
960        &mut self,
961        dir: CycleDir,
962        hub: &Hub,
963        rq: &mut RenderQueue,
964        context: &mut Context,
965    ) {
966        if self.chunks.is_empty() {
967            return;
968        }
969
970        let current_page = self.current_page;
971        let page_offset = self.view_port.page_offset;
972
973        let loc = {
974            let neighloc = match dir {
975                CycleDir::Previous => match self.view_port.zoom_mode {
976                    ZoomMode::FitToPage => Location::Previous(current_page),
977                    ZoomMode::FitToWidth => match self.view_port.scroll_mode {
978                        ScrollMode::Screen => {
979                            let first_chunk = self.chunks.first().cloned().unwrap();
980                            let mut location = first_chunk.location;
981                            let available_height =
982                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
983                            let mut height = 0;
984
985                            loop {
986                                self.load_pixmap(location);
987                                self.load_text(location);
988                                let Resource { mut frame, .. } = self.cache[&location];
989                                if location == first_chunk.location {
990                                    frame.max.y = first_chunk.frame.min.y;
991                                }
992                                height += frame.height() as i32;
993                                if height >= available_height {
994                                    break;
995                                }
996                                let mut doc = self.doc.lock().unwrap();
997                                if let Some(previous_location) =
998                                    doc.resolve_location(Location::Previous(location))
999                                {
1000                                    location = previous_location;
1001                                } else {
1002                                    break;
1003                                }
1004                            }
1005
1006                            let mut next_top_offset = (height - available_height).max(0);
1007                            if height > available_height {
1008                                let Resource { frame, scale, .. } = self.cache[&location];
1009                                let mut doc = self.doc.lock().unwrap();
1010                                if let Some((lines, _)) = doc.lines(Location::Exact(location)) {
1011                                    if let Some(mut y_pos) = find_cut(
1012                                        &frame,
1013                                        frame.min.y + next_top_offset,
1014                                        scale,
1015                                        LinearDir::Forward,
1016                                        &lines,
1017                                    ) {
1018                                        y_pos = y_pos.clamp(frame.min.y, frame.max.y - 1);
1019                                        next_top_offset = y_pos - frame.min.y;
1020                                    }
1021                                }
1022                            }
1023
1024                            self.view_port.page_offset.y = next_top_offset;
1025                            Location::Exact(location)
1026                        }
1027                        ScrollMode::Page => {
1028                            let available_height =
1029                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
1030                            if self.view_port.page_offset.y > 0 {
1031                                self.view_port.page_offset.y =
1032                                    (self.view_port.page_offset.y - available_height).max(0);
1033                                Location::Exact(current_page)
1034                            } else {
1035                                let previous_location = self
1036                                    .doc
1037                                    .lock()
1038                                    .unwrap()
1039                                    .resolve_location(Location::Previous(current_page));
1040                                if let Some(location) = previous_location {
1041                                    self.load_pixmap(location);
1042                                    let frame = self.cache[&location].frame;
1043                                    self.view_port.page_offset.y =
1044                                        (frame.height() as i32 - available_height).max(0);
1045                                }
1046                                Location::Previous(current_page)
1047                            }
1048                        }
1049                    },
1050                    ZoomMode::Custom(_) => {
1051                        self.view_port.page_offset = pt!(0);
1052                        Location::Previous(current_page)
1053                    }
1054                },
1055                CycleDir::Next => match self.view_port.zoom_mode {
1056                    ZoomMode::FitToPage => Location::Next(current_page),
1057                    ZoomMode::FitToWidth => match self.view_port.scroll_mode {
1058                        ScrollMode::Screen => {
1059                            let &RenderChunk {
1060                                location, frame, ..
1061                            } = self.chunks.last().unwrap();
1062                            self.load_pixmap(location);
1063                            self.load_text(location);
1064                            let pixmap_frame = self.cache[&location].frame;
1065                            let next_top_offset = frame.max.y - pixmap_frame.min.y;
1066                            if next_top_offset == pixmap_frame.height() as i32 {
1067                                self.view_port.page_offset.y = 0;
1068                                Location::Next(location)
1069                            } else {
1070                                self.view_port.page_offset.y = next_top_offset;
1071                                Location::Exact(location)
1072                            }
1073                        }
1074                        ScrollMode::Page => {
1075                            let available_height =
1076                                self.rect.height() as i32 - 2 * self.view_port.margin_width;
1077                            let frame_height = self.cache[&current_page].frame.height() as i32;
1078                            let next_top_offset = self.view_port.page_offset.y + available_height;
1079                            if frame_height < available_height || next_top_offset == frame_height {
1080                                self.view_port.page_offset.y = 0;
1081                                Location::Next(current_page)
1082                            } else {
1083                                self.view_port.page_offset.y =
1084                                    next_top_offset.min(frame_height - available_height);
1085                                Location::Exact(current_page)
1086                            }
1087                        }
1088                    },
1089                    ZoomMode::Custom(_) => {
1090                        self.view_port.page_offset = pt!(0);
1091                        Location::Next(current_page)
1092                    }
1093                },
1094            };
1095            let mut doc = self.doc.lock().unwrap();
1096            doc.resolve_location(neighloc)
1097        };
1098        match loc {
1099            Some(location)
1100                if location != current_page || self.view_port.page_offset != page_offset =>
1101            {
1102                if let Some(ref mut s) = self.search {
1103                    s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
1104                }
1105
1106                self.current_page = location;
1107                self.selection = None;
1108                self.state = State::Idle;
1109                self.update(None, hub, rq, context);
1110                self.update_bottom_bar(rq);
1111
1112                if self.search.is_some() {
1113                    self.update_results_bar(rq);
1114                }
1115            }
1116            _ => match dir {
1117                CycleDir::Next => {
1118                    self.finished = true;
1119                    let action = if self.ephemeral {
1120                        FinishedAction::Notify
1121                    } else {
1122                        context
1123                            .settings
1124                            .libraries
1125                            .get(context.settings.selected_library)
1126                            .and_then(|lib| lib.finished)
1127                            .unwrap_or(context.settings.reader.finished)
1128                    };
1129                    match action {
1130                        FinishedAction::Notify => {
1131                            let notif = Notification::new(
1132                                None,
1133                                "No next page.".to_string(),
1134                                false,
1135                                hub,
1136                                rq,
1137                                context,
1138                            );
1139                            self.children.push(Box::new(notif) as Box<dyn View>);
1140                        }
1141                        FinishedAction::Close => {
1142                            self.quit(context);
1143                            hub.send(Event::Back).ok();
1144                        }
1145                        FinishedAction::GoToNext => {
1146                            let current_path = self.info.file.path.clone();
1147                            let books: Vec<crate::metadata::Info> =
1148                                context.library.books.values().cloned().collect();
1149                            let next = books
1150                                .iter()
1151                                .position(|b| b.file.path == current_path)
1152                                .and_then(|i| books.get(i + 1))
1153                                .cloned();
1154
1155                            self.quit(context);
1156
1157                            match next {
1158                                Some(next_info) => {
1159                                    hub.send(Event::Open(Box::new(next_info))).ok();
1160                                }
1161                                None => {
1162                                    hub.send(Event::Back).ok();
1163                                }
1164                            }
1165                        }
1166                    }
1167                }
1168                CycleDir::Previous => {
1169                    let notif = Notification::new(
1170                        None,
1171                        "No previous page.".to_string(),
1172                        false,
1173                        hub,
1174                        rq,
1175                        context,
1176                    );
1177                    self.children.push(Box::new(notif) as Box<dyn View>);
1178                }
1179            },
1180        }
1181    }
1182
1183    fn go_to_results_page(
1184        &mut self,
1185        index: usize,
1186        hub: &Hub,
1187        rq: &mut RenderQueue,
1188        context: &Context,
1189    ) {
1190        let mut loc = None;
1191        if let Some(ref mut s) = self.search {
1192            if index < s.highlights.len() {
1193                s.current_page = index;
1194                loc = Some(*s.highlights.keys().nth(index).unwrap());
1195            }
1196        }
1197        if let Some(location) = loc {
1198            self.current_page = location;
1199            self.view_port.page_offset = pt!(0, 0);
1200            self.selection = None;
1201            self.state = State::Idle;
1202            self.update_results_bar(rq);
1203            self.update_bottom_bar(rq);
1204            self.update(None, hub, rq, context);
1205        }
1206    }
1207
1208    fn go_to_results_neighbor(
1209        &mut self,
1210        dir: CycleDir,
1211        hub: &Hub,
1212        rq: &mut RenderQueue,
1213        context: &Context,
1214    ) {
1215        let loc = self.search.as_ref().and_then(|s| match dir {
1216            CycleDir::Next => s
1217                .highlights
1218                .range(self.current_page + 1..)
1219                .next()
1220                .map(|e| *e.0),
1221            CycleDir::Previous => s
1222                .highlights
1223                .range(..self.current_page)
1224                .next_back()
1225                .map(|e| *e.0),
1226        });
1227        if let Some(location) = loc {
1228            if let Some(ref mut s) = self.search {
1229                s.current_page = s.highlights.range(..=location).count().saturating_sub(1);
1230            }
1231            self.view_port.page_offset = pt!(0, 0);
1232            self.current_page = location;
1233            self.update_results_bar(rq);
1234            self.update_bottom_bar(rq);
1235            self.update(None, hub, rq, context);
1236        }
1237    }
1238
1239    fn update_bottom_bar(&mut self, rq: &mut RenderQueue) {
1240        if let Some(index) = locate::<BottomBar>(self) {
1241            let current_page = self.current_page;
1242            let mut doc = self.doc.lock().unwrap();
1243            let rtoc = self.toc().or_else(|| doc.toc());
1244            let chapter = rtoc.as_ref().and_then(|toc| doc.chapter(current_page, toc));
1245            let title = chapter.map(|(c, _)| c.title.clone()).unwrap_or_default();
1246            let progress = chapter.map(|(_, p)| p).unwrap_or_default();
1247            let bottom_bar = self.children[index]
1248                .as_mut()
1249                .downcast_mut::<BottomBar>()
1250                .unwrap();
1251            let neighbors = Neighbors {
1252                previous_page: doc.resolve_location(Location::Previous(current_page)),
1253                next_page: doc.resolve_location(Location::Next(current_page)),
1254            };
1255            bottom_bar.update_chapter_label(title, progress, rq);
1256            bottom_bar.update_page_label(self.current_page, self.pages_count, rq);
1257            bottom_bar.update_icons(&neighbors, rq);
1258        }
1259    }
1260
1261    fn update_tool_bar(&mut self, rq: &mut RenderQueue, context: &mut Context) {
1262        if let Some(index) = locate::<ToolBar>(self) {
1263            let tool_bar = self.children[index]
1264                .as_mut()
1265                .downcast_mut::<ToolBar>()
1266                .unwrap();
1267            let settings = &context.settings;
1268            if self.reflowable {
1269                let font_family = self
1270                    .info
1271                    .reader
1272                    .as_ref()
1273                    .and_then(|r| r.font_family.clone())
1274                    .unwrap_or_else(|| settings.reader.font_family.clone());
1275                tool_bar.update_font_family(font_family, rq);
1276                let font_size = self
1277                    .info
1278                    .reader
1279                    .as_ref()
1280                    .and_then(|r| r.font_size)
1281                    .unwrap_or(settings.reader.font_size);
1282                tool_bar.update_font_size_slider(font_size, rq);
1283                let text_align = self
1284                    .info
1285                    .reader
1286                    .as_ref()
1287                    .and_then(|r| r.text_align)
1288                    .unwrap_or(settings.reader.text_align);
1289                tool_bar.update_text_align_icon(text_align, rq);
1290                let line_height = self
1291                    .info
1292                    .reader
1293                    .as_ref()
1294                    .and_then(|r| r.line_height)
1295                    .unwrap_or(settings.reader.line_height);
1296                tool_bar.update_line_height(line_height, rq);
1297            } else {
1298                tool_bar.update_contrast_exponent_slider(self.contrast.exponent, rq);
1299                tool_bar.update_contrast_gray_slider(self.contrast.gray, rq);
1300            }
1301            let reflowable = self.reflowable;
1302            let margin_width = self
1303                .info
1304                .reader
1305                .as_ref()
1306                .and_then(|r| {
1307                    if reflowable {
1308                        r.margin_width
1309                    } else {
1310                        r.screen_margin_width
1311                    }
1312                })
1313                .unwrap_or_else(|| {
1314                    if reflowable {
1315                        settings.reader.margin_width
1316                    } else {
1317                        0
1318                    }
1319                });
1320            tool_bar.update_margin_width(margin_width, rq);
1321        }
1322    }
1323
1324    fn update_results_bar(&mut self, rq: &mut RenderQueue) {
1325        if self.search.is_none() {
1326            return;
1327        }
1328        let (count, current_page, pages_count) = {
1329            let s = self.search.as_ref().unwrap();
1330            (s.results_count, s.current_page, s.highlights.len())
1331        };
1332        if let Some(index) = locate::<ResultsBar>(self) {
1333            let results_bar = self.child_mut(index).downcast_mut::<ResultsBar>().unwrap();
1334            results_bar.update_results_label(count, rq);
1335            results_bar.update_page_label(current_page, pages_count, rq);
1336            results_bar.update_icons(current_page, pages_count, rq);
1337        }
1338    }
1339
1340    #[inline]
1341    fn update_noninverted_regions(&mut self, inverted: bool) {
1342        self.noninverted_regions.clear();
1343        if inverted {
1344            for chunk in &self.chunks {
1345                if let Some((images, _)) = self
1346                    .doc
1347                    .lock()
1348                    .unwrap()
1349                    .images(Location::Exact(chunk.location))
1350                {
1351                    self.noninverted_regions.insert(chunk.location, images);
1352                }
1353            }
1354        }
1355    }
1356
1357    #[inline]
1358    fn update_annotations(&mut self) {
1359        self.annotations.clear();
1360        if let Some(annotations) = self
1361            .info
1362            .reader
1363            .as_ref()
1364            .map(|r| &r.annotations)
1365            .filter(|a| !a.is_empty())
1366        {
1367            for chunk in &self.chunks {
1368                let words = &self.text[&chunk.location];
1369                if words.is_empty() {
1370                    continue;
1371                }
1372                for annot in annotations {
1373                    let [start, end] = annot.selection;
1374                    if (start >= words[0].location && start <= words[words.len() - 1].location)
1375                        || (end >= words[0].location && end <= words[words.len() - 1].location)
1376                    {
1377                        self.annotations
1378                            .entry(chunk.location)
1379                            .or_insert_with(Vec::new)
1380                            .push(annot.clone());
1381                    }
1382                }
1383            }
1384        }
1385    }
1386
1387    fn update(
1388        &mut self,
1389        update_mode: Option<UpdateMode>,
1390        hub: &Hub,
1391        rq: &mut RenderQueue,
1392        context: &Context,
1393    ) {
1394        self.page_turns += 1;
1395        let update_mode = update_mode.unwrap_or_else(|| {
1396            let pair = context
1397                .settings
1398                .reader
1399                .refresh_rate
1400                .by_kind
1401                .get(&self.info.file.kind)
1402                .unwrap_or_else(|| &context.settings.reader.refresh_rate.global);
1403            let refresh_rate = if context.fb.inverted() {
1404                pair.inverted
1405            } else {
1406                pair.regular
1407            };
1408            if refresh_rate == 0 || self.page_turns % (refresh_rate as usize) != 0 {
1409                UpdateMode::Partial
1410            } else {
1411                UpdateMode::Full
1412            }
1413        });
1414
1415        self.chunks.clear();
1416        let mut location = self.current_page;
1417        let smw = self.view_port.margin_width;
1418
1419        match self.view_port.zoom_mode {
1420            ZoomMode::FitToPage => {
1421                self.load_pixmap(location);
1422                self.load_text(location);
1423                let Resource { frame, scale, .. } = self.cache[&location];
1424                let dx = smw + ((self.rect.width() - frame.width()) as i32 - 2 * smw) / 2;
1425                let dy = smw + ((self.rect.height() - frame.height()) as i32 - 2 * smw) / 2;
1426                self.chunks.push(RenderChunk {
1427                    frame,
1428                    location,
1429                    position: pt!(dx, dy),
1430                    scale,
1431                });
1432            }
1433            ZoomMode::FitToWidth => match self.view_port.scroll_mode {
1434                ScrollMode::Screen => {
1435                    let available_height = self.rect.height() as i32 - 2 * smw;
1436                    let mut height = 0;
1437                    while height < available_height {
1438                        self.load_pixmap(location);
1439                        self.load_text(location);
1440                        let Resource {
1441                            mut frame, scale, ..
1442                        } = self.cache[&location];
1443                        if location == self.current_page {
1444                            frame.min.y += self.view_port.page_offset.y;
1445                        }
1446                        let position = pt!(smw, smw + height);
1447                        self.chunks.push(RenderChunk {
1448                            frame,
1449                            location,
1450                            position,
1451                            scale,
1452                        });
1453                        height += frame.height() as i32;
1454                        if let Ok(mut doc) = self.doc.lock() {
1455                            if let Some(next_location) =
1456                                doc.resolve_location(Location::Next(location))
1457                            {
1458                                location = next_location;
1459                            } else {
1460                                break;
1461                            }
1462                        }
1463                    }
1464                    if height > available_height {
1465                        if let Some(last_chunk) = self.chunks.last_mut() {
1466                            last_chunk.frame.max.y -= height - available_height;
1467                            let mut doc = self.doc.lock().unwrap();
1468                            if let Some((lines, _)) =
1469                                doc.lines(Location::Exact(last_chunk.location))
1470                            {
1471                                let pixmap_frame = self.cache[&last_chunk.location].frame;
1472                                if let Some(mut y_pos) = find_cut(
1473                                    &pixmap_frame,
1474                                    last_chunk.frame.max.y,
1475                                    last_chunk.scale,
1476                                    LinearDir::Backward,
1477                                    &lines,
1478                                ) {
1479                                    y_pos = y_pos.clamp(pixmap_frame.min.y, pixmap_frame.max.y - 1);
1480                                    last_chunk.frame.max.y = y_pos;
1481                                }
1482                            }
1483                        }
1484                        let actual_height: i32 =
1485                            self.chunks.iter().map(|c| c.frame.height() as i32).sum();
1486                        let dy = (available_height - actual_height) / 2;
1487                        for chunk in &mut self.chunks {
1488                            chunk.position.y += dy;
1489                        }
1490                    }
1491                }
1492                ScrollMode::Page => {
1493                    self.load_pixmap(location);
1494                    self.load_text(location);
1495                    let available_height = self.rect.height() as i32 - 2 * smw;
1496                    let Resource {
1497                        mut frame, scale, ..
1498                    } = self.cache[&location];
1499                    frame.min.y += self.view_port.page_offset.y;
1500                    frame.max.y = (frame.min.y + available_height).min(frame.max.y);
1501                    let position = pt!(smw, smw + (available_height - frame.height() as i32) / 2);
1502                    self.chunks.push(RenderChunk {
1503                        frame,
1504                        location,
1505                        position,
1506                        scale,
1507                    });
1508                }
1509            },
1510            ZoomMode::Custom(_) => {
1511                self.load_pixmap(location);
1512                self.load_text(location);
1513                let Resource { frame, scale, .. } = self.cache[&location];
1514                let vpw = self.rect.width() as i32 - 2 * smw;
1515                let vph = self.rect.height() as i32 - 2 * smw;
1516                let vpr = rect![pt!(0), pt!(vpw, vph)] + self.view_port.page_offset + frame.min;
1517                if let Some(rect) = frame.intersection(&vpr) {
1518                    let position = pt!(smw) + rect.min - vpr.min;
1519                    self.chunks.push(RenderChunk {
1520                        frame: rect,
1521                        location,
1522                        position,
1523                        scale,
1524                    });
1525                }
1526            }
1527        }
1528
1529        rq.add(RenderData::new(self.id, self.rect, update_mode));
1530        let first_location = self.chunks.first().map(|c| c.location).unwrap();
1531        let last_location = self.chunks.last().map(|c| c.location).unwrap();
1532
1533        while self.cache.len() > 3 {
1534            let left_count = self.cache.range(..first_location).count();
1535            let right_count = self.cache.range(last_location + 1..).count();
1536            let extremum = if left_count >= right_count {
1537                self.cache.keys().next().cloned().unwrap()
1538            } else {
1539                self.cache.keys().next_back().cloned().unwrap()
1540            };
1541            self.cache.remove(&extremum);
1542        }
1543
1544        self.update_annotations();
1545        self.update_noninverted_regions(context.fb.inverted());
1546
1547        if self.view_port.zoom_mode == ZoomMode::FitToPage
1548            || self.view_port.zoom_mode == ZoomMode::FitToWidth
1549        {
1550            let doc2 = self.doc.clone();
1551            let hub2 = hub.clone();
1552            thread::spawn(move || {
1553                let mut doc = doc2.lock().unwrap();
1554                if let Some(next_location) = doc.resolve_location(Location::Next(last_location)) {
1555                    hub2.send(Event::LoadPixmap(next_location)).ok();
1556                }
1557            });
1558            let doc3 = self.doc.clone();
1559            let hub3 = hub.clone();
1560            thread::spawn(move || {
1561                let mut doc = doc3.lock().unwrap();
1562                if let Some(previous_location) =
1563                    doc.resolve_location(Location::Previous(first_location))
1564                {
1565                    hub3.send(Event::LoadPixmap(previous_location)).ok();
1566                }
1567            });
1568        }
1569    }
1570
1571    fn search(&mut self, text: &str, query: Regex, hub: &Hub, rq: &mut RenderQueue) {
1572        let s = Search {
1573            query: text.to_string(),
1574            ..Default::default()
1575        };
1576
1577        let hub2 = hub.clone();
1578        let doc2 = Arc::clone(&self.doc);
1579        let running = Arc::clone(&s.running);
1580        let current_page = self.current_page;
1581        let search_direction = self.search_direction;
1582        let ws = word_separator(&self.info.language);
1583
1584        thread::spawn(move || {
1585            let mut loc = Location::Exact(current_page);
1586            let mut started = false;
1587
1588            loop {
1589                if !running.load(AtomicOrdering::Relaxed) {
1590                    break;
1591                }
1592
1593                let mut doc = doc2.lock().unwrap();
1594                let mut text = String::new();
1595                let mut rects = BTreeMap::new();
1596
1597                if let Some(location) = doc.resolve_location(loc) {
1598                    if location == current_page && started {
1599                        break;
1600                    }
1601                    if let Some((ref words, _)) = doc.words(Location::Exact(location)) {
1602                        for word in words {
1603                            if !running.load(AtomicOrdering::Relaxed) {
1604                                break;
1605                            }
1606                            if text.ends_with('\u{00AD}') {
1607                                text.pop();
1608                            } else if !text.ends_with('-') && !text.is_empty() {
1609                                text.push_str(ws);
1610                            }
1611                            rects.insert(text.len(), word.rect);
1612                            text += &word.text;
1613                        }
1614                        for m in query.find_iter(&text) {
1615                            if let Some((first, _)) = rects.range(..=m.start()).next_back() {
1616                                let mut match_rects = Vec::new();
1617                                for (_, rect) in rects.range(*first..m.end()) {
1618                                    match_rects.push(*rect);
1619                                }
1620                                hub2.send(Event::SearchResult(location, match_rects)).ok();
1621                            }
1622                        }
1623                    }
1624                    loc = match search_direction {
1625                        LinearDir::Forward => Location::Next(location),
1626                        LinearDir::Backward => Location::Previous(location),
1627                    };
1628                } else {
1629                    loc = match search_direction {
1630                        LinearDir::Forward => Location::Exact(0),
1631                        LinearDir::Backward => Location::Exact(doc.pages_count() - 1),
1632                    };
1633                }
1634
1635                started = true;
1636            }
1637
1638            running.store(false, AtomicOrdering::Relaxed);
1639            hub2.send(Event::EndOfSearch).ok();
1640        });
1641
1642        if self.search.is_some() {
1643            self.render_results(rq);
1644        }
1645
1646        self.search = Some(s);
1647    }
1648
1649    fn toggle_keyboard(
1650        &mut self,
1651        enable: bool,
1652        id: Option<ViewId>,
1653        hub: &Hub,
1654        rq: &mut RenderQueue,
1655        context: &mut Context,
1656    ) {
1657        if let Some(index) = locate::<Keyboard>(self) {
1658            if enable {
1659                return;
1660            }
1661
1662            let mut rect = *self.child(index).rect();
1663            rect.absorb(self.child(index - 1).rect());
1664
1665            if index == 1 {
1666                rect.absorb(self.child(index + 1).rect());
1667                self.children.drain(index - 1..=index + 1);
1668                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1669            } else {
1670                self.children.drain(index - 1..=index);
1671
1672                let start_index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1673                let y_min = self.child(start_index).rect().min.y;
1674                let delta_y = rect.height() as i32;
1675
1676                for i in start_index..index - 1 {
1677                    let shifted_rect = *self.child(i).rect() + pt!(0, delta_y);
1678                    self.child_mut(i).resize(shifted_rect, hub, rq, context);
1679                    rq.add(RenderData::new(
1680                        self.child(i).id(),
1681                        shifted_rect,
1682                        UpdateMode::Gui,
1683                    ));
1684                }
1685
1686                let rect = rect![self.rect.min.x, y_min, self.rect.max.x, y_min + delta_y];
1687                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1688            }
1689
1690            context.kb_rect = Rectangle::default();
1691            hub.send(Event::Focus(None)).ok();
1692        } else {
1693            if !enable {
1694                return;
1695            }
1696
1697            let dpi = CURRENT_DEVICE.dpi;
1698            let (small_height, big_height) = (
1699                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
1700                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
1701            );
1702            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1703            let (small_thickness, big_thickness) = halves(thickness);
1704
1705            let mut kb_rect = rect![
1706                self.rect.min.x,
1707                self.rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
1708                self.rect.max.x,
1709                self.rect.max.y - small_height - small_thickness
1710            ];
1711
1712            let number = matches!(
1713                id,
1714                Some(ViewId::GoToPageInput)
1715                    | Some(ViewId::GoToResultsPageInput)
1716                    | Some(ViewId::NamePageInput)
1717            );
1718
1719            let index = rlocate::<Filler>(self).unwrap_or(0);
1720
1721            if index == 0 {
1722                let separator = Filler::new(
1723                    rect![
1724                        self.rect.min.x,
1725                        kb_rect.max.y,
1726                        self.rect.max.x,
1727                        kb_rect.max.y + thickness
1728                    ],
1729                    BLACK,
1730                );
1731                self.children
1732                    .insert(index, Box::new(separator) as Box<dyn View>);
1733            }
1734
1735            let keyboard = Keyboard::new(&mut kb_rect, number, context);
1736            self.children
1737                .insert(index, Box::new(keyboard) as Box<dyn View>);
1738
1739            let separator = Filler::new(
1740                rect![
1741                    self.rect.min.x,
1742                    kb_rect.min.y - thickness,
1743                    self.rect.max.x,
1744                    kb_rect.min.y
1745                ],
1746                BLACK,
1747            );
1748            self.children
1749                .insert(index, Box::new(separator) as Box<dyn View>);
1750
1751            if index == 0 {
1752                for i in index..index + 3 {
1753                    rq.add(RenderData::new(
1754                        self.child(i).id(),
1755                        *self.child(i).rect(),
1756                        UpdateMode::Gui,
1757                    ));
1758                }
1759            } else {
1760                for i in index..index + 2 {
1761                    rq.add(RenderData::new(
1762                        self.child(i).id(),
1763                        *self.child(i).rect(),
1764                        UpdateMode::Gui,
1765                    ));
1766                }
1767
1768                let delta_y = kb_rect.height() as i32 + thickness;
1769                let start_index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1770
1771                for i in start_index..index {
1772                    let shifted_rect = *self.child(i).rect() + pt!(0, -delta_y);
1773                    self.child_mut(i).resize(shifted_rect, hub, rq, context);
1774                    rq.add(RenderData::new(
1775                        self.child(i).id(),
1776                        shifted_rect,
1777                        UpdateMode::Gui,
1778                    ));
1779                }
1780            }
1781        }
1782    }
1783
1784    fn toggle_tool_bar(&mut self, enable: bool, rq: &mut RenderQueue, context: &mut Context) {
1785        if let Some(index) = locate::<ToolBar>(self) {
1786            if enable {
1787                return;
1788            }
1789
1790            let mut rect = *self.child(index).rect();
1791            rect.absorb(self.child(index - 1).rect());
1792            self.children.drain(index - 1..=index);
1793            rq.add(RenderData::expose(rect, UpdateMode::Gui));
1794        } else {
1795            if !enable {
1796                return;
1797            }
1798
1799            let dpi = CURRENT_DEVICE.dpi;
1800            let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
1801            let tb_height = 2 * big_height;
1802
1803            let sp_rect = *self.child(2).rect() - pt!(0, tb_height as i32);
1804
1805            let tool_bar = ToolBar::new(
1806                rect![
1807                    self.rect.min.x,
1808                    sp_rect.max.y,
1809                    self.rect.max.x,
1810                    sp_rect.max.y + tb_height as i32
1811                ],
1812                self.reflowable,
1813                self.info.reader.as_ref(),
1814                &context.settings.reader,
1815            );
1816            self.children.insert(2, Box::new(tool_bar) as Box<dyn View>);
1817
1818            let separator = Filler::new(sp_rect, BLACK);
1819            self.children
1820                .insert(2, Box::new(separator) as Box<dyn View>);
1821        }
1822    }
1823
1824    fn toggle_results_bar(&mut self, enable: bool, rq: &mut RenderQueue, _context: &mut Context) {
1825        if let Some(index) = locate::<ResultsBar>(self) {
1826            if enable {
1827                return;
1828            }
1829
1830            let mut rect = *self.child(index).rect();
1831            rect.absorb(self.child(index - 1).rect());
1832            self.children.drain(index - 1..=index);
1833            rq.add(RenderData::expose(rect, UpdateMode::Gui));
1834        } else {
1835            if !enable {
1836                return;
1837            }
1838
1839            let dpi = CURRENT_DEVICE.dpi;
1840            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1841            let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
1842            let index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1843
1844            let sp_rect = *self.child(index).rect() - pt!(0, small_height);
1845            let y_min = sp_rect.max.y;
1846            let mut rect = rect![
1847                self.rect.min.x,
1848                y_min,
1849                self.rect.max.x,
1850                y_min + small_height - thickness
1851            ];
1852
1853            if let Some(ref s) = self.search {
1854                let results_bar = ResultsBar::new(
1855                    rect,
1856                    s.current_page,
1857                    s.highlights.len(),
1858                    s.results_count,
1859                    !s.running.load(AtomicOrdering::Relaxed),
1860                );
1861                self.children
1862                    .insert(index, Box::new(results_bar) as Box<dyn View>);
1863                let separator = Filler::new(sp_rect, BLACK);
1864                self.children
1865                    .insert(index, Box::new(separator) as Box<dyn View>);
1866                rect.absorb(&sp_rect);
1867                rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
1868            }
1869        }
1870    }
1871
1872    fn toggle_search_bar(
1873        &mut self,
1874        enable: bool,
1875        hub: &Hub,
1876        rq: &mut RenderQueue,
1877        context: &mut Context,
1878    ) {
1879        if let Some(index) = locate::<SearchBar>(self) {
1880            if enable {
1881                return;
1882            }
1883
1884            if let Some(ViewId::ReaderSearchInput) = self.focus {
1885                self.toggle_keyboard(false, None, hub, rq, context);
1886            }
1887
1888            if self.child(0).is::<TopBar>() {
1889                self.toggle_bars(Some(false), hub, rq, context);
1890            } else {
1891                let mut rect = *self.child(index).rect();
1892                rect.absorb(self.child(index - 1).rect());
1893                rect.absorb(self.child(index + 1).rect());
1894                self.children.drain(index - 1..=index + 1);
1895                rq.add(RenderData::expose(rect, UpdateMode::Gui));
1896            }
1897        } else {
1898            if !enable {
1899                return;
1900            }
1901
1902            self.toggle_tool_bar(false, rq, context);
1903
1904            let dpi = CURRENT_DEVICE.dpi;
1905            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
1906            let (small_thickness, big_thickness) = halves(thickness);
1907            let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32;
1908            let index = locate::<TopBar>(self).map(|index| index + 2).unwrap_or(0);
1909
1910            if index == 0 {
1911                let sp_rect = rect![
1912                    self.rect.min.x,
1913                    self.rect.max.y - small_height - small_thickness,
1914                    self.rect.max.x,
1915                    self.rect.max.y - small_height + big_thickness
1916                ];
1917                let separator = Filler::new(sp_rect, BLACK);
1918                self.children
1919                    .insert(index, Box::new(separator) as Box<dyn View>);
1920            }
1921
1922            let sp_rect = rect![
1923                self.rect.min.x,
1924                self.rect.max.y - 2 * small_height - small_thickness,
1925                self.rect.max.x,
1926                self.rect.max.y - 2 * small_height + big_thickness
1927            ];
1928            let y_min = sp_rect.max.y;
1929            let rect = rect![
1930                self.rect.min.x,
1931                y_min,
1932                self.rect.max.x,
1933                y_min + small_height - thickness
1934            ];
1935            let search_bar = SearchBar::new(rect, ViewId::ReaderSearchInput, "", "", context);
1936            self.children
1937                .insert(index, Box::new(search_bar) as Box<dyn View>);
1938
1939            let separator = Filler::new(sp_rect, BLACK);
1940            self.children
1941                .insert(index, Box::new(separator) as Box<dyn View>);
1942
1943            rq.add(RenderData::new(
1944                self.child(index).id(),
1945                *self.child(index).rect(),
1946                UpdateMode::Gui,
1947            ));
1948            rq.add(RenderData::new(
1949                self.child(index + 1).id(),
1950                *self.child(index + 1).rect(),
1951                UpdateMode::Gui,
1952            ));
1953
1954            if index == 0 {
1955                rq.add(RenderData::new(
1956                    self.child(index + 2).id(),
1957                    *self.child(index + 2).rect(),
1958                    UpdateMode::Gui,
1959                ));
1960            }
1961
1962            self.toggle_keyboard(true, Some(ViewId::ReaderSearchInput), hub, rq, context);
1963            hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok();
1964        }
1965    }
1966
1967    fn toggle_bars(
1968        &mut self,
1969        enable: Option<bool>,
1970        hub: &Hub,
1971        rq: &mut RenderQueue,
1972        context: &mut Context,
1973    ) {
1974        if let Some(top_index) = locate::<TopBar>(self) {
1975            if let Some(true) = enable {
1976                return;
1977            }
1978
1979            if let Some(bottom_index) = locate::<BottomBar>(self) {
1980                let mut top_rect = *self.child(top_index).rect();
1981                top_rect.absorb(self.child(top_index + 1).rect());
1982                let mut bottom_rect = *self.child(bottom_index).rect();
1983                for i in top_index + 2..bottom_index {
1984                    bottom_rect.absorb(self.child(i).rect());
1985                }
1986
1987                self.children.drain(top_index..=bottom_index);
1988
1989                rq.add(RenderData::expose(top_rect, UpdateMode::Gui));
1990                rq.add(RenderData::expose(bottom_rect, UpdateMode::Gui));
1991                hub.send(Event::Focus(None)).ok();
1992            }
1993        } else {
1994            if let Some(false) = enable {
1995                return;
1996            }
1997
1998            let dpi = CURRENT_DEVICE.dpi;
1999            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
2000            let (small_thickness, big_thickness) = halves(thickness);
2001            let (small_height, big_height) = (
2002                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
2003                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
2004            );
2005
2006            let mut doc = self.doc.lock().unwrap();
2007            let mut index = 0;
2008
2009            let top_bar = TopBar::new(
2010                rect![
2011                    self.rect.min.x,
2012                    self.rect.min.y,
2013                    self.rect.max.x,
2014                    self.rect.min.y + small_height - small_thickness
2015                ],
2016                TopBarVariant::Back,
2017                self.info.title(),
2018                context,
2019            );
2020
2021            self.children
2022                .insert(index, Box::new(top_bar) as Box<dyn View>);
2023            index += 1;
2024
2025            let separator = Filler::new(
2026                rect![
2027                    self.rect.min.x,
2028                    self.rect.min.y + small_height - small_thickness,
2029                    self.rect.max.x,
2030                    self.rect.min.y + small_height + big_thickness
2031                ],
2032                BLACK,
2033            );
2034            self.children
2035                .insert(index, Box::new(separator) as Box<dyn View>);
2036            index += 1;
2037
2038            if let Some(ref s) = self.search {
2039                if let Some(sindex) = rlocate::<SearchBar>(self) {
2040                    index = sindex + 2;
2041                } else {
2042                    let separator = Filler::new(
2043                        rect![
2044                            self.rect.min.x,
2045                            self.rect.max.y - 3 * small_height - small_thickness,
2046                            self.rect.max.x,
2047                            self.rect.max.y - 3 * small_height + big_thickness
2048                        ],
2049                        BLACK,
2050                    );
2051                    self.children
2052                        .insert(index, Box::new(separator) as Box<dyn View>);
2053                    index += 1;
2054
2055                    let results_bar = ResultsBar::new(
2056                        rect![
2057                            self.rect.min.x,
2058                            self.rect.max.y - 3 * small_height + big_thickness,
2059                            self.rect.max.x,
2060                            self.rect.max.y - 2 * small_height - small_thickness
2061                        ],
2062                        s.current_page,
2063                        s.highlights.len(),
2064                        s.results_count,
2065                        !s.running.load(AtomicOrdering::Relaxed),
2066                    );
2067                    self.children
2068                        .insert(index, Box::new(results_bar) as Box<dyn View>);
2069                    index += 1;
2070
2071                    let separator = Filler::new(
2072                        rect![
2073                            self.rect.min.x,
2074                            self.rect.max.y - 2 * small_height - small_thickness,
2075                            self.rect.max.x,
2076                            self.rect.max.y - 2 * small_height + big_thickness
2077                        ],
2078                        BLACK,
2079                    );
2080                    self.children
2081                        .insert(index, Box::new(separator) as Box<dyn View>);
2082                    index += 1;
2083
2084                    let search_bar = SearchBar::new(
2085                        rect![
2086                            self.rect.min.x,
2087                            self.rect.max.y - 2 * small_height + big_thickness,
2088                            self.rect.max.x,
2089                            self.rect.max.y - small_height - small_thickness
2090                        ],
2091                        ViewId::ReaderSearchInput,
2092                        "",
2093                        &s.query,
2094                        context,
2095                    );
2096                    self.children
2097                        .insert(index, Box::new(search_bar) as Box<dyn View>);
2098                    index += 1;
2099                }
2100            } else {
2101                let tb_height = 2 * big_height;
2102                let separator = Filler::new(
2103                    rect![
2104                        self.rect.min.x,
2105                        self.rect.max.y - (small_height + tb_height) as i32 - small_thickness,
2106                        self.rect.max.x,
2107                        self.rect.max.y - (small_height + tb_height) as i32 + big_thickness
2108                    ],
2109                    BLACK,
2110                );
2111                self.children
2112                    .insert(index, Box::new(separator) as Box<dyn View>);
2113                index += 1;
2114
2115                let tool_bar = ToolBar::new(
2116                    rect![
2117                        self.rect.min.x,
2118                        self.rect.max.y - (small_height + tb_height) as i32 + big_thickness,
2119                        self.rect.max.x,
2120                        self.rect.max.y - small_height - small_thickness
2121                    ],
2122                    self.reflowable,
2123                    self.info.reader.as_ref(),
2124                    &context.settings.reader,
2125                );
2126                self.children
2127                    .insert(index, Box::new(tool_bar) as Box<dyn View>);
2128                index += 1;
2129            }
2130
2131            let separator = Filler::new(
2132                rect![
2133                    self.rect.min.x,
2134                    self.rect.max.y - small_height - small_thickness,
2135                    self.rect.max.x,
2136                    self.rect.max.y - small_height + big_thickness
2137                ],
2138                BLACK,
2139            );
2140            self.children
2141                .insert(index, Box::new(separator) as Box<dyn View>);
2142            index += 1;
2143
2144            let neighbors = Neighbors {
2145                previous_page: doc.resolve_location(Location::Previous(self.current_page)),
2146                next_page: doc.resolve_location(Location::Next(self.current_page)),
2147            };
2148
2149            let bottom_bar = BottomBar::new(
2150                rect![
2151                    self.rect.min.x,
2152                    self.rect.max.y - small_height + big_thickness,
2153                    self.rect.max.x,
2154                    self.rect.max.y
2155                ],
2156                doc.as_mut(),
2157                self.toc(),
2158                self.current_page,
2159                self.pages_count,
2160                &neighbors,
2161                self.synthetic,
2162            );
2163            self.children
2164                .insert(index, Box::new(bottom_bar) as Box<dyn View>);
2165
2166            for i in 0..=index {
2167                rq.add(RenderData::new(
2168                    self.child(i).id(),
2169                    *self.child(i).rect(),
2170                    UpdateMode::Gui,
2171                ));
2172            }
2173        }
2174    }
2175
2176    fn toggle_margin_cropper(
2177        &mut self,
2178        enable: bool,
2179        hub: &Hub,
2180        rq: &mut RenderQueue,
2181        context: &mut Context,
2182    ) {
2183        if let Some(index) = locate::<MarginCropper>(self) {
2184            if enable {
2185                return;
2186            }
2187
2188            rq.add(RenderData::expose(
2189                *self.child(index).rect(),
2190                UpdateMode::Gui,
2191            ));
2192            self.children.remove(index);
2193        } else {
2194            if !enable {
2195                return;
2196            }
2197
2198            self.toggle_bars(Some(false), hub, rq, context);
2199
2200            let dpi = CURRENT_DEVICE.dpi;
2201            let padding = scale_by_dpi(BUTTON_DIAMETER / 2.0, dpi) as i32;
2202            let pixmap_rect = rect![self.rect.min + pt!(padding), self.rect.max - pt!(padding)];
2203
2204            let margin = self
2205                .info
2206                .reader
2207                .as_ref()
2208                .and_then(|r| {
2209                    r.cropping_margins
2210                        .as_ref()
2211                        .map(|c| c.margin(self.current_page))
2212                })
2213                .cloned()
2214                .unwrap_or_default();
2215
2216            let mut doc = self.doc.lock().unwrap();
2217            let (pixmap, _) = build_pixmap(&pixmap_rect, doc.as_mut(), self.current_page);
2218
2219            let margin_cropper = MarginCropper::new(self.rect, pixmap, &margin, context);
2220            rq.add(RenderData::new(
2221                margin_cropper.id(),
2222                *margin_cropper.rect(),
2223                UpdateMode::Gui,
2224            ));
2225            self.children
2226                .push(Box::new(margin_cropper) as Box<dyn View>);
2227        }
2228    }
2229
2230    fn toggle_edit_note(
2231        &mut self,
2232        text: Option<String>,
2233        enable: Option<bool>,
2234        hub: &Hub,
2235        rq: &mut RenderQueue,
2236        context: &mut Context,
2237    ) {
2238        if let Some(index) = locate_by_id(self, ViewId::EditNote) {
2239            if let Some(true) = enable {
2240                return;
2241            }
2242
2243            rq.add(RenderData::expose(
2244                *self.child(index).rect(),
2245                UpdateMode::Gui,
2246            ));
2247            self.children.remove(index);
2248
2249            if self
2250                .focus
2251                .map(|focus_id| focus_id == ViewId::EditNoteInput)
2252                .unwrap_or(false)
2253            {
2254                self.toggle_keyboard(false, None, hub, rq, context);
2255            }
2256        } else {
2257            if let Some(false) = enable {
2258                return;
2259            }
2260
2261            let mut edit_note = NamedInput::new(
2262                "Note".to_string(),
2263                ViewId::EditNote,
2264                ViewId::EditNoteInput,
2265                32,
2266                context,
2267            );
2268            if let Some(text) = text.as_ref() {
2269                edit_note.set_text(text, &mut RenderQueue::new(), context);
2270            }
2271
2272            rq.add(RenderData::new(
2273                edit_note.id(),
2274                *edit_note.rect(),
2275                UpdateMode::Gui,
2276            ));
2277            hub.send(Event::Focus(Some(ViewId::EditNoteInput))).ok();
2278
2279            self.children.push(Box::new(edit_note) as Box<dyn View>);
2280        }
2281    }
2282
2283    fn toggle_name_page(
2284        &mut self,
2285        enable: Option<bool>,
2286        hub: &Hub,
2287        rq: &mut RenderQueue,
2288        context: &mut Context,
2289    ) {
2290        if let Some(index) = locate_by_id(self, ViewId::NamePage) {
2291            if let Some(true) = enable {
2292                return;
2293            }
2294
2295            rq.add(RenderData::expose(
2296                *self.child(index).rect(),
2297                UpdateMode::Gui,
2298            ));
2299            self.children.remove(index);
2300
2301            if self
2302                .focus
2303                .map(|focus_id| focus_id == ViewId::NamePageInput)
2304                .unwrap_or(false)
2305            {
2306                self.toggle_keyboard(false, None, hub, rq, context);
2307            }
2308        } else {
2309            if let Some(false) = enable {
2310                return;
2311            }
2312
2313            let name_page = NamedInput::new(
2314                "Name page".to_string(),
2315                ViewId::NamePage,
2316                ViewId::NamePageInput,
2317                4,
2318                context,
2319            );
2320            rq.add(RenderData::new(
2321                name_page.id(),
2322                *name_page.rect(),
2323                UpdateMode::Gui,
2324            ));
2325            hub.send(Event::Focus(Some(ViewId::NamePageInput))).ok();
2326
2327            self.children.push(Box::new(name_page) as Box<dyn View>);
2328        }
2329    }
2330
2331    fn toggle_go_to_page(
2332        &mut self,
2333        enable: Option<bool>,
2334        id: ViewId,
2335        hub: &Hub,
2336        rq: &mut RenderQueue,
2337        context: &mut Context,
2338    ) {
2339        let (text, input_id) = if id == ViewId::GoToPage {
2340            ("Go to page", ViewId::GoToPageInput)
2341        } else {
2342            ("Go to results page", ViewId::GoToResultsPageInput)
2343        };
2344
2345        if let Some(index) = locate_by_id(self, id) {
2346            if let Some(true) = enable {
2347                return;
2348            }
2349
2350            rq.add(RenderData::expose(
2351                *self.child(index).rect(),
2352                UpdateMode::Gui,
2353            ));
2354            self.children.remove(index);
2355
2356            if self
2357                .focus
2358                .map(|focus_id| focus_id == input_id)
2359                .unwrap_or(false)
2360            {
2361                self.toggle_keyboard(false, None, hub, rq, context);
2362            }
2363        } else {
2364            if let Some(false) = enable {
2365                return;
2366            }
2367
2368            let go_to_page = NamedInput::new(text.to_string(), id, input_id, 4, context);
2369            rq.add(RenderData::new(
2370                go_to_page.id(),
2371                *go_to_page.rect(),
2372                UpdateMode::Gui,
2373            ));
2374            hub.send(Event::Focus(Some(input_id))).ok();
2375
2376            self.children.push(Box::new(go_to_page) as Box<dyn View>);
2377        }
2378    }
2379
2380    pub fn toggle_annotation_menu(
2381        &mut self,
2382        annot: &Annotation,
2383        rect: Rectangle,
2384        enable: Option<bool>,
2385        rq: &mut RenderQueue,
2386        context: &mut Context,
2387    ) {
2388        if let Some(index) = locate_by_id(self, ViewId::AnnotationMenu) {
2389            if let Some(true) = enable {
2390                return;
2391            }
2392
2393            rq.add(RenderData::expose(
2394                *self.child(index).rect(),
2395                UpdateMode::Gui,
2396            ));
2397            self.children.remove(index);
2398        } else {
2399            if let Some(false) = enable {
2400                return;
2401            }
2402
2403            let sel = annot.selection;
2404            let mut entries = Vec::new();
2405
2406            if annot.note.is_empty() {
2407                entries.push(EntryKind::Command(
2408                    "Remove Highlight".to_string(),
2409                    EntryId::RemoveAnnotation(sel),
2410                ));
2411                entries.push(EntryKind::Separator);
2412                entries.push(EntryKind::Command(
2413                    "Add Note".to_string(),
2414                    EntryId::EditAnnotationNote(sel),
2415                ));
2416            } else {
2417                entries.push(EntryKind::Command(
2418                    "Remove Annotation".to_string(),
2419                    EntryId::RemoveAnnotation(sel),
2420                ));
2421                entries.push(EntryKind::Separator);
2422                entries.push(EntryKind::Command(
2423                    "Edit Note".to_string(),
2424                    EntryId::EditAnnotationNote(sel),
2425                ));
2426                entries.push(EntryKind::Command(
2427                    "Remove Note".to_string(),
2428                    EntryId::RemoveAnnotationNote(sel),
2429                ));
2430            }
2431
2432            let selection_menu = Menu::new(
2433                rect,
2434                ViewId::AnnotationMenu,
2435                MenuKind::Contextual,
2436                entries,
2437                context,
2438            );
2439            rq.add(RenderData::new(
2440                selection_menu.id(),
2441                *selection_menu.rect(),
2442                UpdateMode::Gui,
2443            ));
2444            self.children
2445                .push(Box::new(selection_menu) as Box<dyn View>);
2446        }
2447    }
2448
2449    pub fn toggle_selection_menu(
2450        &mut self,
2451        rect: Rectangle,
2452        enable: Option<bool>,
2453        rq: &mut RenderQueue,
2454        context: &mut Context,
2455    ) {
2456        if let Some(index) = locate_by_id(self, ViewId::SelectionMenu) {
2457            if let Some(true) = enable {
2458                return;
2459            }
2460
2461            rq.add(RenderData::expose(
2462                *self.child(index).rect(),
2463                UpdateMode::Gui,
2464            ));
2465            self.children.remove(index);
2466        } else {
2467            if let Some(false) = enable {
2468                return;
2469            }
2470            let mut entries = vec![
2471                EntryKind::Command("Highlight".to_string(), EntryId::HighlightSelection),
2472                EntryKind::Command("Add Note".to_string(), EntryId::AnnotateSelection),
2473            ];
2474
2475            entries.push(EntryKind::Separator);
2476            entries.push(EntryKind::Command(
2477                "Define".to_string(),
2478                EntryId::DefineSelection,
2479            ));
2480            entries.push(EntryKind::Command(
2481                "Search".to_string(),
2482                EntryId::SearchForSelection,
2483            ));
2484
2485            if self
2486                .info
2487                .reader
2488                .as_ref()
2489                .map_or(false, |r| !r.page_names.is_empty())
2490            {
2491                entries.push(EntryKind::Command(
2492                    "Go To".to_string(),
2493                    EntryId::GoToSelectedPageName,
2494                ));
2495            }
2496
2497            entries.push(EntryKind::Separator);
2498            entries.push(EntryKind::Command(
2499                "Adjust Selection".to_string(),
2500                EntryId::AdjustSelection,
2501            ));
2502
2503            let selection_menu = Menu::new(
2504                rect,
2505                ViewId::SelectionMenu,
2506                MenuKind::Contextual,
2507                entries,
2508                context,
2509            );
2510            rq.add(RenderData::new(
2511                selection_menu.id(),
2512                *selection_menu.rect(),
2513                UpdateMode::Gui,
2514            ));
2515            self.children
2516                .push(Box::new(selection_menu) as Box<dyn View>);
2517        }
2518    }
2519
2520    pub fn toggle_title_menu(
2521        &mut self,
2522        rect: Rectangle,
2523        enable: Option<bool>,
2524        rq: &mut RenderQueue,
2525        context: &mut Context,
2526    ) {
2527        if let Some(index) = locate_by_id(self, ViewId::TitleMenu) {
2528            if let Some(true) = enable {
2529                return;
2530            }
2531
2532            rq.add(RenderData::expose(
2533                *self.child(index).rect(),
2534                UpdateMode::Gui,
2535            ));
2536            self.children.remove(index);
2537        } else {
2538            if let Some(false) = enable {
2539                return;
2540            }
2541
2542            let zoom_mode = self.view_port.zoom_mode;
2543            let scroll_mode = self.view_port.scroll_mode;
2544            let sf = if let ZoomMode::Custom(sf) = zoom_mode {
2545                sf
2546            } else {
2547                1.0
2548            };
2549
2550            let mut entries = if self.reflowable {
2551                vec![EntryKind::SubMenu(
2552                    "Zoom Mode".to_string(),
2553                    vec![
2554                        EntryKind::RadioButton(
2555                            "Fit to Page".to_string(),
2556                            EntryId::SetZoomMode(ZoomMode::FitToPage),
2557                            zoom_mode == ZoomMode::FitToPage,
2558                        ),
2559                        EntryKind::RadioButton(
2560                            format!("Custom ({:.1}%)", 100.0 * sf),
2561                            EntryId::SetZoomMode(ZoomMode::Custom(sf)),
2562                            zoom_mode == ZoomMode::Custom(sf),
2563                        ),
2564                    ],
2565                )]
2566            } else {
2567                vec![EntryKind::SubMenu(
2568                    "Zoom Mode".to_string(),
2569                    vec![
2570                        EntryKind::RadioButton(
2571                            "Fit to Page".to_string(),
2572                            EntryId::SetZoomMode(ZoomMode::FitToPage),
2573                            zoom_mode == ZoomMode::FitToPage,
2574                        ),
2575                        EntryKind::RadioButton(
2576                            "Fit to Width".to_string(),
2577                            EntryId::SetZoomMode(ZoomMode::FitToWidth),
2578                            zoom_mode == ZoomMode::FitToWidth,
2579                        ),
2580                        EntryKind::RadioButton(
2581                            format!("Custom ({:.1}%)", 100.0 * sf),
2582                            EntryId::SetZoomMode(ZoomMode::Custom(sf)),
2583                            zoom_mode == ZoomMode::Custom(sf),
2584                        ),
2585                    ],
2586                )]
2587            };
2588
2589            entries.push(EntryKind::SubMenu(
2590                "Scroll Mode".to_string(),
2591                vec![
2592                    EntryKind::RadioButton(
2593                        "Screen".to_string(),
2594                        EntryId::SetScrollMode(ScrollMode::Screen),
2595                        scroll_mode == ScrollMode::Screen,
2596                    ),
2597                    EntryKind::RadioButton(
2598                        "Page".to_string(),
2599                        EntryId::SetScrollMode(ScrollMode::Page),
2600                        scroll_mode == ScrollMode::Page,
2601                    ),
2602                ],
2603            ));
2604
2605            if self.ephemeral {
2606                entries.push(EntryKind::Command("Save".to_string(), EntryId::Save));
2607            }
2608
2609            if self
2610                .info
2611                .reader
2612                .as_ref()
2613                .map_or(false, |r| !r.annotations.is_empty())
2614            {
2615                entries.push(EntryKind::Command(
2616                    "Annotations".to_string(),
2617                    EntryId::Annotations,
2618                ));
2619            }
2620
2621            if self
2622                .info
2623                .reader
2624                .as_ref()
2625                .map_or(false, |r| !r.bookmarks.is_empty())
2626            {
2627                entries.push(EntryKind::Command(
2628                    "Bookmarks".to_string(),
2629                    EntryId::Bookmarks,
2630                ));
2631            }
2632
2633            if !entries.is_empty() {
2634                entries.push(EntryKind::Separator);
2635            }
2636
2637            entries.push(EntryKind::CheckBox(
2638                "Apply Dithering".to_string(),
2639                EntryId::ToggleDithered,
2640                context.fb.dithered(),
2641            ));
2642
2643            let mut title_menu = Menu::new(
2644                rect,
2645                ViewId::TitleMenu,
2646                MenuKind::DropDown,
2647                entries,
2648                context,
2649            );
2650            title_menu
2651                .child_mut(1)
2652                .downcast_mut::<MenuEntry>()
2653                .unwrap()
2654                .set_disabled(zoom_mode != ZoomMode::FitToWidth, rq);
2655
2656            rq.add(RenderData::new(
2657                title_menu.id(),
2658                *title_menu.rect(),
2659                UpdateMode::Gui,
2660            ));
2661            self.children.push(Box::new(title_menu) as Box<dyn View>);
2662        }
2663    }
2664
2665    fn toggle_font_family_menu(
2666        &mut self,
2667        rect: Rectangle,
2668        enable: Option<bool>,
2669        rq: &mut RenderQueue,
2670        context: &mut Context,
2671    ) {
2672        if let Some(index) = locate_by_id(self, ViewId::FontFamilyMenu) {
2673            if let Some(true) = enable {
2674                return;
2675            }
2676
2677            rq.add(RenderData::expose(
2678                *self.child(index).rect(),
2679                UpdateMode::Gui,
2680            ));
2681            self.children.remove(index);
2682        } else {
2683            if let Some(false) = enable {
2684                return;
2685            }
2686
2687            let mut families = family_names(&context.settings.reader.font_path)
2688                .map_err(|e| error!("Can't get family names: {:#}.", e))
2689                .unwrap_or_default();
2690            let current_family = self
2691                .info
2692                .reader
2693                .as_ref()
2694                .and_then(|r| r.font_family.clone())
2695                .unwrap_or_else(|| context.settings.reader.font_family.clone());
2696            families.insert(DEFAULT_FONT_FAMILY.to_string());
2697            let entries = families
2698                .iter()
2699                .map(|f| {
2700                    EntryKind::RadioButton(
2701                        f.clone(),
2702                        EntryId::SetFontFamily(f.clone()),
2703                        *f == current_family,
2704                    )
2705                })
2706                .collect();
2707            let font_family_menu = Menu::new(
2708                rect,
2709                ViewId::FontFamilyMenu,
2710                MenuKind::DropDown,
2711                entries,
2712                context,
2713            );
2714            rq.add(RenderData::new(
2715                font_family_menu.id(),
2716                *font_family_menu.rect(),
2717                UpdateMode::Gui,
2718            ));
2719            self.children
2720                .push(Box::new(font_family_menu) as Box<dyn View>);
2721        }
2722    }
2723
2724    fn toggle_font_size_menu(
2725        &mut self,
2726        rect: Rectangle,
2727        enable: Option<bool>,
2728        rq: &mut RenderQueue,
2729        context: &mut Context,
2730    ) {
2731        if let Some(index) = locate_by_id(self, ViewId::FontSizeMenu) {
2732            if let Some(true) = enable {
2733                return;
2734            }
2735
2736            rq.add(RenderData::expose(
2737                *self.child(index).rect(),
2738                UpdateMode::Gui,
2739            ));
2740            self.children.remove(index);
2741        } else {
2742            if let Some(false) = enable {
2743                return;
2744            }
2745
2746            let font_size = self
2747                .info
2748                .reader
2749                .as_ref()
2750                .and_then(|r| r.font_size)
2751                .unwrap_or(context.settings.reader.font_size);
2752            let min_font_size = context.settings.reader.font_size / 2.0;
2753            let max_font_size = 3.0 * context.settings.reader.font_size / 2.0;
2754            let entries = (0..=20)
2755                .filter_map(|v| {
2756                    let fs = font_size - 1.0 + v as f32 / 10.0;
2757                    if fs >= min_font_size && fs <= max_font_size {
2758                        Some(EntryKind::RadioButton(
2759                            format!("{:.1}", fs),
2760                            EntryId::SetFontSize(v),
2761                            (fs - font_size).abs() < 0.05,
2762                        ))
2763                    } else {
2764                        None
2765                    }
2766                })
2767                .collect();
2768            let font_size_menu = Menu::new(
2769                rect,
2770                ViewId::FontSizeMenu,
2771                MenuKind::Contextual,
2772                entries,
2773                context,
2774            );
2775            rq.add(RenderData::new(
2776                font_size_menu.id(),
2777                *font_size_menu.rect(),
2778                UpdateMode::Gui,
2779            ));
2780            self.children
2781                .push(Box::new(font_size_menu) as Box<dyn View>);
2782        }
2783    }
2784
2785    fn toggle_text_align_menu(
2786        &mut self,
2787        rect: Rectangle,
2788        enable: Option<bool>,
2789        rq: &mut RenderQueue,
2790        context: &mut Context,
2791    ) {
2792        if let Some(index) = locate_by_id(self, ViewId::TextAlignMenu) {
2793            if let Some(true) = enable {
2794                return;
2795            }
2796
2797            rq.add(RenderData::expose(
2798                *self.child(index).rect(),
2799                UpdateMode::Gui,
2800            ));
2801            self.children.remove(index);
2802        } else {
2803            if let Some(false) = enable {
2804                return;
2805            }
2806
2807            let text_align = self
2808                .info
2809                .reader
2810                .as_ref()
2811                .and_then(|r| r.text_align)
2812                .unwrap_or(context.settings.reader.text_align);
2813            let choices = [
2814                TextAlign::Justify,
2815                TextAlign::Left,
2816                TextAlign::Right,
2817                TextAlign::Center,
2818            ];
2819            let entries = choices
2820                .iter()
2821                .map(|v| {
2822                    EntryKind::RadioButton(
2823                        v.to_string(),
2824                        EntryId::SetTextAlign(*v),
2825                        text_align == *v,
2826                    )
2827                })
2828                .collect();
2829            let text_align_menu = Menu::new(
2830                rect,
2831                ViewId::TextAlignMenu,
2832                MenuKind::Contextual,
2833                entries,
2834                context,
2835            );
2836            rq.add(RenderData::new(
2837                text_align_menu.id(),
2838                *text_align_menu.rect(),
2839                UpdateMode::Gui,
2840            ));
2841            self.children
2842                .push(Box::new(text_align_menu) as Box<dyn View>);
2843        }
2844    }
2845
2846    fn toggle_line_height_menu(
2847        &mut self,
2848        rect: Rectangle,
2849        enable: Option<bool>,
2850        rq: &mut RenderQueue,
2851        context: &mut Context,
2852    ) {
2853        if let Some(index) = locate_by_id(self, ViewId::LineHeightMenu) {
2854            if let Some(true) = enable {
2855                return;
2856            }
2857
2858            rq.add(RenderData::expose(
2859                *self.child(index).rect(),
2860                UpdateMode::Gui,
2861            ));
2862            self.children.remove(index);
2863        } else {
2864            if let Some(false) = enable {
2865                return;
2866            }
2867
2868            let line_height = self
2869                .info
2870                .reader
2871                .as_ref()
2872                .and_then(|r| r.line_height)
2873                .unwrap_or(context.settings.reader.line_height);
2874            let entries = (0..=10)
2875                .map(|x| {
2876                    let lh = 1.0 + x as f32 / 10.0;
2877                    EntryKind::RadioButton(
2878                        format!("{:.1}", lh),
2879                        EntryId::SetLineHeight(x),
2880                        (lh - line_height).abs() < 0.05,
2881                    )
2882                })
2883                .collect();
2884            let line_height_menu = Menu::new(
2885                rect,
2886                ViewId::LineHeightMenu,
2887                MenuKind::DropDown,
2888                entries,
2889                context,
2890            );
2891            rq.add(RenderData::new(
2892                line_height_menu.id(),
2893                *line_height_menu.rect(),
2894                UpdateMode::Gui,
2895            ));
2896            self.children
2897                .push(Box::new(line_height_menu) as Box<dyn View>);
2898        }
2899    }
2900
2901    fn toggle_contrast_exponent_menu(
2902        &mut self,
2903        rect: Rectangle,
2904        enable: Option<bool>,
2905        rq: &mut RenderQueue,
2906        context: &mut Context,
2907    ) {
2908        if let Some(index) = locate_by_id(self, ViewId::ContrastExponentMenu) {
2909            if let Some(true) = enable {
2910                return;
2911            }
2912
2913            rq.add(RenderData::expose(
2914                *self.child(index).rect(),
2915                UpdateMode::Gui,
2916            ));
2917            self.children.remove(index);
2918        } else {
2919            if let Some(false) = enable {
2920                return;
2921            }
2922
2923            let entries = (0..=8)
2924                .map(|x| {
2925                    let e = 1.0 + x as f32 / 2.0;
2926                    EntryKind::RadioButton(
2927                        format!("{:.1}", e),
2928                        EntryId::SetContrastExponent(x),
2929                        (e - self.contrast.exponent).abs() < f32::EPSILON,
2930                    )
2931                })
2932                .collect();
2933            let contrast_exponent_menu = Menu::new(
2934                rect,
2935                ViewId::ContrastExponentMenu,
2936                MenuKind::DropDown,
2937                entries,
2938                context,
2939            );
2940            rq.add(RenderData::new(
2941                contrast_exponent_menu.id(),
2942                *contrast_exponent_menu.rect(),
2943                UpdateMode::Gui,
2944            ));
2945            self.children
2946                .push(Box::new(contrast_exponent_menu) as Box<dyn View>);
2947        }
2948    }
2949
2950    fn toggle_contrast_gray_menu(
2951        &mut self,
2952        rect: Rectangle,
2953        enable: Option<bool>,
2954        rq: &mut RenderQueue,
2955        context: &mut Context,
2956    ) {
2957        if let Some(index) = locate_by_id(self, ViewId::ContrastGrayMenu) {
2958            if let Some(true) = enable {
2959                return;
2960            }
2961
2962            rq.add(RenderData::expose(
2963                *self.child(index).rect(),
2964                UpdateMode::Gui,
2965            ));
2966            self.children.remove(index);
2967        } else {
2968            if let Some(false) = enable {
2969                return;
2970            }
2971
2972            let entries = (1..=6)
2973                .map(|x| {
2974                    let g = ((1 << 8) - (1 << (8 - x))) as f32;
2975                    EntryKind::RadioButton(
2976                        format!("{:.1}", g),
2977                        EntryId::SetContrastGray(x),
2978                        (g - self.contrast.gray).abs() < f32::EPSILON,
2979                    )
2980                })
2981                .collect();
2982            let contrast_gray_menu = Menu::new(
2983                rect,
2984                ViewId::ContrastGrayMenu,
2985                MenuKind::DropDown,
2986                entries,
2987                context,
2988            );
2989            rq.add(RenderData::new(
2990                contrast_gray_menu.id(),
2991                *contrast_gray_menu.rect(),
2992                UpdateMode::Gui,
2993            ));
2994            self.children
2995                .push(Box::new(contrast_gray_menu) as Box<dyn View>);
2996        }
2997    }
2998
2999    fn toggle_margin_width_menu(
3000        &mut self,
3001        rect: Rectangle,
3002        enable: Option<bool>,
3003        rq: &mut RenderQueue,
3004        context: &mut Context,
3005    ) {
3006        if let Some(index) = locate_by_id(self, ViewId::MarginWidthMenu) {
3007            if let Some(true) = enable {
3008                return;
3009            }
3010
3011            rq.add(RenderData::expose(
3012                *self.child(index).rect(),
3013                UpdateMode::Gui,
3014            ));
3015            self.children.remove(index);
3016        } else {
3017            if let Some(false) = enable {
3018                return;
3019            }
3020
3021            let reflowable = self.reflowable;
3022            let margin_width = self
3023                .info
3024                .reader
3025                .as_ref()
3026                .and_then(|r| {
3027                    if reflowable {
3028                        r.margin_width
3029                    } else {
3030                        r.screen_margin_width
3031                    }
3032                })
3033                .unwrap_or_else(|| {
3034                    if reflowable {
3035                        context.settings.reader.margin_width
3036                    } else {
3037                        0
3038                    }
3039                });
3040            let min_margin_width = context.settings.reader.min_margin_width;
3041            let max_margin_width = context.settings.reader.max_margin_width;
3042            let entries = (min_margin_width..=max_margin_width)
3043                .map(|mw| {
3044                    EntryKind::RadioButton(
3045                        format!("{}", mw),
3046                        EntryId::SetMarginWidth(mw),
3047                        mw == margin_width,
3048                    )
3049                })
3050                .collect();
3051            let margin_width_menu = Menu::new(
3052                rect,
3053                ViewId::MarginWidthMenu,
3054                MenuKind::DropDown,
3055                entries,
3056                context,
3057            );
3058            rq.add(RenderData::new(
3059                margin_width_menu.id(),
3060                *margin_width_menu.rect(),
3061                UpdateMode::Gui,
3062            ));
3063            self.children
3064                .push(Box::new(margin_width_menu) as Box<dyn View>);
3065        }
3066    }
3067
3068    fn toggle_page_menu(
3069        &mut self,
3070        rect: Rectangle,
3071        enable: Option<bool>,
3072        rq: &mut RenderQueue,
3073        context: &mut Context,
3074    ) {
3075        if let Some(index) = locate_by_id(self, ViewId::PageMenu) {
3076            if let Some(true) = enable {
3077                return;
3078            }
3079
3080            rq.add(RenderData::expose(
3081                *self.child(index).rect(),
3082                UpdateMode::Gui,
3083            ));
3084            self.children.remove(index);
3085        } else {
3086            if let Some(false) = enable {
3087                return;
3088            }
3089
3090            let has_name = self
3091                .info
3092                .reader
3093                .as_ref()
3094                .map_or(false, |r| r.page_names.contains_key(&self.current_page));
3095
3096            let mut entries = vec![EntryKind::Command("Name".to_string(), EntryId::SetPageName)];
3097            if has_name {
3098                entries.push(EntryKind::Command(
3099                    "Remove Name".to_string(),
3100                    EntryId::RemovePageName,
3101                ));
3102            }
3103            let names = self
3104                .info
3105                .reader
3106                .as_ref()
3107                .map(|r| {
3108                    r.page_names
3109                        .iter()
3110                        .map(|(i, s)| EntryKind::Command(s.to_string(), EntryId::GoTo(*i)))
3111                        .collect::<Vec<EntryKind>>()
3112                })
3113                .unwrap_or_default();
3114            if !names.is_empty() {
3115                entries.push(EntryKind::Separator);
3116                entries.push(EntryKind::SubMenu("Go To".to_string(), names));
3117            }
3118
3119            let page_menu = Menu::new(rect, ViewId::PageMenu, MenuKind::DropDown, entries, context);
3120            rq.add(RenderData::new(
3121                page_menu.id(),
3122                *page_menu.rect(),
3123                UpdateMode::Gui,
3124            ));
3125            self.children.push(Box::new(page_menu) as Box<dyn View>);
3126        }
3127    }
3128
3129    fn toggle_margin_cropper_menu(
3130        &mut self,
3131        rect: Rectangle,
3132        enable: Option<bool>,
3133        rq: &mut RenderQueue,
3134        context: &mut Context,
3135    ) {
3136        if let Some(index) = locate_by_id(self, ViewId::MarginCropperMenu) {
3137            if let Some(true) = enable {
3138                return;
3139            }
3140
3141            rq.add(RenderData::expose(
3142                *self.child(index).rect(),
3143                UpdateMode::Gui,
3144            ));
3145            self.children.remove(index);
3146        } else {
3147            if let Some(false) = enable {
3148                return;
3149            }
3150
3151            let current_page = self.current_page;
3152            let is_split = self
3153                .info
3154                .reader
3155                .as_ref()
3156                .and_then(|r| r.cropping_margins.as_ref().map(CroppingMargins::is_split));
3157
3158            let mut entries = vec![
3159                EntryKind::RadioButton(
3160                    "Any".to_string(),
3161                    EntryId::ApplyCroppings(current_page, PageScheme::Any),
3162                    is_split.is_some() && !is_split.unwrap(),
3163                ),
3164                EntryKind::RadioButton(
3165                    "Even/Odd".to_string(),
3166                    EntryId::ApplyCroppings(current_page, PageScheme::EvenOdd),
3167                    is_split.is_some() && is_split.unwrap(),
3168                ),
3169            ];
3170
3171            let is_applied = self
3172                .info
3173                .reader
3174                .as_ref()
3175                .map(|r| r.cropping_margins.is_some())
3176                .unwrap_or(false);
3177            if is_applied {
3178                entries.extend_from_slice(&[
3179                    EntryKind::Separator,
3180                    EntryKind::Command("Remove".to_string(), EntryId::RemoveCroppings),
3181                ]);
3182            }
3183
3184            let margin_cropper_menu = Menu::new(
3185                rect,
3186                ViewId::MarginCropperMenu,
3187                MenuKind::DropDown,
3188                entries,
3189                context,
3190            );
3191            rq.add(RenderData::new(
3192                margin_cropper_menu.id(),
3193                *margin_cropper_menu.rect(),
3194                UpdateMode::Gui,
3195            ));
3196            self.children
3197                .push(Box::new(margin_cropper_menu) as Box<dyn View>);
3198        }
3199    }
3200
3201    fn toggle_search_menu(
3202        &mut self,
3203        rect: Rectangle,
3204        enable: Option<bool>,
3205        rq: &mut RenderQueue,
3206        context: &mut Context,
3207    ) {
3208        if let Some(index) = locate_by_id(self, ViewId::SearchMenu) {
3209            if let Some(true) = enable {
3210                return;
3211            }
3212
3213            rq.add(RenderData::expose(
3214                *self.child(index).rect(),
3215                UpdateMode::Gui,
3216            ));
3217            self.children.remove(index);
3218        } else {
3219            if let Some(false) = enable {
3220                return;
3221            }
3222
3223            let entries = vec![
3224                EntryKind::RadioButton(
3225                    "Forward".to_string(),
3226                    EntryId::SearchDirection(LinearDir::Forward),
3227                    self.search_direction == LinearDir::Forward,
3228                ),
3229                EntryKind::RadioButton(
3230                    "Backward".to_string(),
3231                    EntryId::SearchDirection(LinearDir::Backward),
3232                    self.search_direction == LinearDir::Backward,
3233                ),
3234            ];
3235
3236            let search_menu = Menu::new(
3237                rect,
3238                ViewId::SearchMenu,
3239                MenuKind::Contextual,
3240                entries,
3241                context,
3242            );
3243            rq.add(RenderData::new(
3244                search_menu.id(),
3245                *search_menu.rect(),
3246                UpdateMode::Gui,
3247            ));
3248            self.children.push(Box::new(search_menu) as Box<dyn View>);
3249        }
3250    }
3251
3252    fn set_font_size(
3253        &mut self,
3254        font_size: f32,
3255        hub: &Hub,
3256        rq: &mut RenderQueue,
3257        context: &mut Context,
3258    ) {
3259        if Arc::strong_count(&self.doc) > 1 {
3260            return;
3261        }
3262
3263        if let Some(ref mut r) = self.info.reader {
3264            r.font_size = Some(font_size);
3265        }
3266
3267        let (width, height) = context.display.dims;
3268        {
3269            let mut doc = self.doc.lock().unwrap();
3270
3271            doc.layout(width, height, font_size, CURRENT_DEVICE.dpi);
3272
3273            if self.synthetic {
3274                let current_page = self.current_page.min(doc.pages_count() - 1);
3275                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3276                    self.current_page = location;
3277                }
3278            } else {
3279                let ratio = doc.pages_count() / self.pages_count;
3280                self.pages_count = doc.pages_count();
3281                self.current_page = (ratio * self.current_page).min(self.pages_count - 1);
3282            }
3283        }
3284
3285        self.cache.clear();
3286        self.text.clear();
3287        self.update(None, hub, rq, context);
3288        self.update_tool_bar(rq, context);
3289        self.update_bottom_bar(rq);
3290    }
3291
3292    fn set_text_align(
3293        &mut self,
3294        text_align: TextAlign,
3295        hub: &Hub,
3296        rq: &mut RenderQueue,
3297        context: &mut Context,
3298    ) {
3299        if Arc::strong_count(&self.doc) > 1 {
3300            return;
3301        }
3302
3303        if let Some(ref mut r) = self.info.reader {
3304            r.text_align = Some(text_align);
3305        }
3306
3307        {
3308            let mut doc = self.doc.lock().unwrap();
3309            doc.set_text_align(text_align);
3310
3311            if self.synthetic {
3312                let current_page = self.current_page.min(doc.pages_count() - 1);
3313                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3314                    self.current_page = location;
3315                }
3316            } else {
3317                self.pages_count = doc.pages_count();
3318                self.current_page = self.current_page.min(self.pages_count - 1);
3319            }
3320        }
3321
3322        self.cache.clear();
3323        self.text.clear();
3324        self.update(None, hub, rq, context);
3325        self.update_tool_bar(rq, context);
3326        self.update_bottom_bar(rq);
3327    }
3328
3329    fn set_font_family(
3330        &mut self,
3331        font_family: &str,
3332        hub: &Hub,
3333        rq: &mut RenderQueue,
3334        context: &mut Context,
3335    ) {
3336        if Arc::strong_count(&self.doc) > 1 {
3337            return;
3338        }
3339
3340        if let Some(ref mut r) = self.info.reader {
3341            r.font_family = Some(font_family.to_string());
3342        }
3343
3344        {
3345            let mut doc = self.doc.lock().unwrap();
3346            let font_path = if font_family == DEFAULT_FONT_FAMILY {
3347                "fonts"
3348            } else {
3349                &context.settings.reader.font_path
3350            };
3351
3352            doc.set_font_family(font_family, font_path);
3353
3354            if self.synthetic {
3355                let current_page = self.current_page.min(doc.pages_count() - 1);
3356                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3357                    self.current_page = location;
3358                }
3359            } else {
3360                self.pages_count = doc.pages_count();
3361                self.current_page = self.current_page.min(self.pages_count - 1);
3362            }
3363        }
3364
3365        self.cache.clear();
3366        self.text.clear();
3367        self.update(None, hub, rq, context);
3368        self.update_tool_bar(rq, context);
3369        self.update_bottom_bar(rq);
3370    }
3371
3372    fn set_line_height(
3373        &mut self,
3374        line_height: f32,
3375        hub: &Hub,
3376        rq: &mut RenderQueue,
3377        context: &mut Context,
3378    ) {
3379        if Arc::strong_count(&self.doc) > 1 {
3380            return;
3381        }
3382
3383        if let Some(ref mut r) = self.info.reader {
3384            r.line_height = Some(line_height);
3385        }
3386
3387        {
3388            let mut doc = self.doc.lock().unwrap();
3389            doc.set_line_height(line_height);
3390
3391            if self.synthetic {
3392                let current_page = self.current_page.min(doc.pages_count() - 1);
3393                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3394                    self.current_page = location;
3395                }
3396            } else {
3397                self.pages_count = doc.pages_count();
3398                self.current_page = self.current_page.min(self.pages_count - 1);
3399            }
3400        }
3401
3402        self.cache.clear();
3403        self.text.clear();
3404        self.update(None, hub, rq, context);
3405        self.update_tool_bar(rq, context);
3406        self.update_bottom_bar(rq);
3407    }
3408
3409    fn set_margin_width(
3410        &mut self,
3411        width: i32,
3412        hub: &Hub,
3413        rq: &mut RenderQueue,
3414        context: &mut Context,
3415    ) {
3416        if Arc::strong_count(&self.doc) > 1 {
3417            return;
3418        }
3419
3420        if let Some(ref mut r) = self.info.reader {
3421            if self.reflowable {
3422                r.margin_width = Some(width);
3423            } else {
3424                if width == 0 {
3425                    r.screen_margin_width = None;
3426                } else {
3427                    r.screen_margin_width = Some(width);
3428                }
3429            }
3430        }
3431
3432        if self.reflowable {
3433            let mut doc = self.doc.lock().unwrap();
3434            doc.set_margin_width(width);
3435
3436            if self.synthetic {
3437                let current_page = self.current_page.min(doc.pages_count() - 1);
3438                if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
3439                    self.current_page = location;
3440                }
3441            } else {
3442                self.pages_count = doc.pages_count();
3443                self.current_page = self.current_page.min(self.pages_count - 1);
3444            }
3445        } else {
3446            let next_margin_width = mm_to_px(width as f32, CURRENT_DEVICE.dpi) as i32;
3447            if self.view_port.zoom_mode == ZoomMode::FitToWidth {
3448                // Apply the scale change.
3449                let ratio = (self.rect.width() as i32 - 2 * next_margin_width) as f32
3450                    / (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32;
3451                self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32;
3452            } else {
3453                // Keep the center still.
3454                self.view_port.page_offset += pt!(next_margin_width - self.view_port.margin_width);
3455            }
3456            self.view_port.margin_width = next_margin_width;
3457        }
3458
3459        self.text.clear();
3460        self.cache.clear();
3461        self.update(None, hub, rq, context);
3462        self.update_tool_bar(rq, context);
3463        self.update_bottom_bar(rq);
3464    }
3465
3466    fn toggle_bookmark(&mut self, rq: &mut RenderQueue) {
3467        if let Some(ref mut r) = self.info.reader {
3468            if !r.bookmarks.insert(self.current_page) {
3469                r.bookmarks.remove(&self.current_page);
3470            }
3471        }
3472        let dpi = CURRENT_DEVICE.dpi;
3473        let thickness = scale_by_dpi(3.0, dpi) as u16;
3474        let radius = mm_to_px(0.4, dpi) as i32 + thickness as i32;
3475        let center = pt!(self.rect.max.x - 5 * radius, self.rect.min.y + 5 * radius);
3476        let rect = Rectangle::from_disk(center, radius);
3477        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
3478    }
3479
3480    fn set_contrast_exponent(
3481        &mut self,
3482        exponent: f32,
3483        hub: &Hub,
3484        rq: &mut RenderQueue,
3485        context: &mut Context,
3486    ) {
3487        if let Some(ref mut r) = self.info.reader {
3488            r.contrast_exponent = Some(exponent);
3489        }
3490        self.contrast.exponent = exponent;
3491        self.update(None, hub, rq, context);
3492        self.update_tool_bar(rq, context);
3493    }
3494
3495    fn set_contrast_gray(
3496        &mut self,
3497        gray: f32,
3498        hub: &Hub,
3499        rq: &mut RenderQueue,
3500        context: &mut Context,
3501    ) {
3502        if let Some(ref mut r) = self.info.reader {
3503            r.contrast_gray = Some(gray);
3504        }
3505        self.contrast.gray = gray;
3506        self.update(None, hub, rq, context);
3507        self.update_tool_bar(rq, context);
3508    }
3509
3510    fn set_zoom_mode(
3511        &mut self,
3512        zoom_mode: ZoomMode,
3513        reset_page_offset: bool,
3514        hub: &Hub,
3515        rq: &mut RenderQueue,
3516        context: &Context,
3517    ) {
3518        if self.view_port.zoom_mode == zoom_mode {
3519            return;
3520        }
3521
3522        if let Some(index) = locate_by_id(self, ViewId::TitleMenu) {
3523            self.child_mut(index)
3524                .child_mut(1)
3525                .downcast_mut::<MenuEntry>()
3526                .unwrap()
3527                .set_disabled(zoom_mode != ZoomMode::FitToWidth, rq);
3528        }
3529
3530        self.view_port.zoom_mode = zoom_mode;
3531        if reset_page_offset {
3532            self.view_port.page_offset = pt!(0, 0);
3533        }
3534        self.cache.clear();
3535        self.update(None, hub, rq, context);
3536    }
3537
3538    fn set_scroll_mode(
3539        &mut self,
3540        scroll_mode: ScrollMode,
3541        hub: &Hub,
3542        rq: &mut RenderQueue,
3543        context: &Context,
3544    ) {
3545        if self.view_port.scroll_mode == scroll_mode
3546            || self.view_port.zoom_mode != ZoomMode::FitToWidth
3547        {
3548            return;
3549        }
3550        self.view_port.scroll_mode = scroll_mode;
3551        self.view_port.page_offset = pt!(0, 0);
3552        self.update(None, hub, rq, context);
3553    }
3554
3555    fn crop_margins(
3556        &mut self,
3557        index: usize,
3558        margin: &Margin,
3559        hub: &Hub,
3560        rq: &mut RenderQueue,
3561        context: &Context,
3562    ) {
3563        if self.view_port.zoom_mode != ZoomMode::FitToPage {
3564            let Resource { pixmap, frame, .. } = self.cache.get(&index).unwrap();
3565            let offset = frame.min + self.view_port.page_offset;
3566            let x_ratio = offset.x as f32 / pixmap.width as f32;
3567            let y_ratio = offset.y as f32 / pixmap.height as f32;
3568            let dims = {
3569                let doc = self.doc.lock().unwrap();
3570                doc.dims(index).unwrap()
3571            };
3572            let scale = scaling_factor(
3573                &self.rect,
3574                margin,
3575                self.view_port.margin_width,
3576                dims,
3577                self.view_port.zoom_mode,
3578            );
3579            if x_ratio >= margin.left && x_ratio <= (1.0 - margin.right) {
3580                self.view_port.page_offset.x = (scale * (x_ratio - margin.left) * dims.0) as i32;
3581            } else {
3582                self.view_port.page_offset.x = 0;
3583            }
3584            if y_ratio >= margin.top && y_ratio <= (1.0 - margin.bottom) {
3585                self.view_port.page_offset.y = (scale * (y_ratio - margin.top) * dims.1) as i32;
3586            } else {
3587                self.view_port.page_offset.y = 0;
3588            }
3589        }
3590        if let Some(r) = self.info.reader.as_mut() {
3591            if r.cropping_margins.is_none() {
3592                r.cropping_margins = Some(CroppingMargins::Any(Margin::default()));
3593            }
3594            for c in r.cropping_margins.iter_mut() {
3595                *c.margin_mut(index) = margin.clone();
3596            }
3597        }
3598        self.cache.clear();
3599        self.update(None, hub, rq, context);
3600    }
3601
3602    fn toc(&self) -> Option<Vec<TocEntry>> {
3603        let mut index = 0;
3604        self.info
3605            .toc
3606            .as_ref()
3607            .map(|simple_toc| self.toc_aux(simple_toc, &mut index))
3608    }
3609
3610    fn toc_aux(&self, simple_toc: &[SimpleTocEntry], index: &mut usize) -> Vec<TocEntry> {
3611        let mut toc = Vec::new();
3612        for entry in simple_toc {
3613            *index += 1;
3614            match entry {
3615                SimpleTocEntry::Leaf(title, location)
3616                | SimpleTocEntry::Container(title, location, _) => {
3617                    let current_title = title.clone();
3618                    let current_location = match location {
3619                        TocLocation::Uri(uri) if uri.starts_with('\'') => self
3620                            .find_page_by_name(&uri[1..])
3621                            .map(Location::Exact)
3622                            .unwrap_or_else(|| location.clone().into()),
3623                        _ => location.clone().into(),
3624                    };
3625                    let current_index = *index;
3626                    let current_children = if let SimpleTocEntry::Container(_, _, children) = entry
3627                    {
3628                        self.toc_aux(children, index)
3629                    } else {
3630                        Vec::new()
3631                    };
3632                    toc.push(TocEntry {
3633                        title: current_title,
3634                        location: current_location,
3635                        index: current_index,
3636                        children: current_children,
3637                    });
3638                }
3639            }
3640        }
3641        toc
3642    }
3643
3644    fn find_page_by_name(&self, name: &str) -> Option<usize> {
3645        self.info.reader.as_ref().and_then(|r| {
3646            if let Ok(a) = name.parse::<u32>() {
3647                r.page_names
3648                    .iter()
3649                    .filter_map(|(i, s)| s.parse::<u32>().ok().map(|b| (b, i)))
3650                    .filter(|(b, _)| *b <= a)
3651                    .max_by(|x, y| x.0.cmp(&y.0))
3652                    .map(|(b, i)| *i + (a - b) as usize)
3653            } else if let Some(a) = name.chars().next().and_then(|c| c.to_alphabetic_digit()) {
3654                r.page_names
3655                    .iter()
3656                    .filter_map(|(i, s)| {
3657                        s.chars()
3658                            .next()
3659                            .and_then(|c| c.to_alphabetic_digit())
3660                            .map(|c| (c, i))
3661                    })
3662                    .filter(|(b, _)| *b <= a)
3663                    .max_by(|x, y| x.0.cmp(&y.0))
3664                    .map(|(b, i)| *i + (a - b) as usize)
3665            } else if let Ok(a) = Roman::from_str(name) {
3666                r.page_names
3667                    .iter()
3668                    .filter_map(|(i, s)| Roman::from_str(s).ok().map(|b| (*b, i)))
3669                    .filter(|(b, _)| *b <= *a)
3670                    .max_by(|x, y| x.0.cmp(&y.0))
3671                    .map(|(b, i)| *i + (*a - b) as usize)
3672            } else {
3673                None
3674            }
3675        })
3676    }
3677
3678    fn text_excerpt(&self, sel: [TextLocation; 2]) -> Option<String> {
3679        let [start, end] = sel;
3680        let parts = self
3681            .text
3682            .values()
3683            .flatten()
3684            .filter(|bnd| bnd.location >= start && bnd.location <= end)
3685            .map(|bnd| bnd.text.as_str())
3686            .collect::<Vec<&str>>();
3687
3688        if parts.is_empty() {
3689            return None;
3690        }
3691
3692        let ws = word_separator(&self.info.language);
3693        let mut text = parts[0].to_string();
3694
3695        for p in &parts[1..] {
3696            if text.ends_with('\u{00AD}') {
3697                text.pop();
3698            } else if !text.ends_with('-') {
3699                text.push_str(ws);
3700            }
3701            text += p;
3702        }
3703
3704        Some(text)
3705    }
3706
3707    fn selected_text(&self) -> Option<String> {
3708        self.selection
3709            .as_ref()
3710            .and_then(|sel| self.text_excerpt([sel.start, sel.end]))
3711    }
3712
3713    fn text_rect(&self, sel: [TextLocation; 2]) -> Option<Rectangle> {
3714        let [start, end] = sel;
3715        let mut result: Option<Rectangle> = None;
3716
3717        for chunk in &self.chunks {
3718            if let Some(words) = self.text.get(&chunk.location) {
3719                for word in words {
3720                    if word.location >= start && word.location <= end {
3721                        let rect =
3722                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
3723                        if let Some(ref mut r) = result {
3724                            r.absorb(&rect);
3725                        } else {
3726                            result = Some(rect);
3727                        }
3728                    }
3729                }
3730            }
3731        }
3732
3733        result
3734    }
3735
3736    fn render_results(&self, rq: &mut RenderQueue) {
3737        for chunk in &self.chunks {
3738            if let Some(groups) = self
3739                .search
3740                .as_ref()
3741                .and_then(|s| s.highlights.get(&chunk.location))
3742            {
3743                for rects in groups {
3744                    let mut rect_opt: Option<Rectangle> = None;
3745                    for rect in rects {
3746                        let rect =
3747                            (*rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
3748                        if let Some(ref mut r) = rect_opt {
3749                            r.absorb(&rect);
3750                        } else {
3751                            rect_opt = Some(rect);
3752                        }
3753                    }
3754                    if let Some(rect) = rect_opt {
3755                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
3756                    }
3757                }
3758            }
3759        }
3760    }
3761
3762    fn selection_rect(&self) -> Option<Rectangle> {
3763        self.selection
3764            .as_ref()
3765            .and_then(|sel| self.text_rect([sel.start, sel.end]))
3766    }
3767
3768    fn find_annotation_ref(&mut self, sel: [TextLocation; 2]) -> Option<&Annotation> {
3769        self.info.reader.as_ref().and_then(|r| {
3770            r.annotations
3771                .iter()
3772                .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])
3773        })
3774    }
3775
3776    fn find_annotation_mut(&mut self, sel: [TextLocation; 2]) -> Option<&mut Annotation> {
3777        self.info.reader.as_mut().and_then(|r| {
3778            r.annotations
3779                .iter_mut()
3780                .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])
3781        })
3782    }
3783
3784    fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
3785        if let Some(index) = locate::<TopBar>(self) {
3786            if let Some(top_bar) = self.child_mut(index).downcast_mut::<TopBar>() {
3787                top_bar.reseed(rq, context);
3788            }
3789        }
3790
3791        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
3792    }
3793
3794    fn quit(&mut self, context: &mut Context) {
3795        if let Some(ref mut s) = self.search {
3796            s.running.store(false, AtomicOrdering::Relaxed);
3797        }
3798
3799        if self.ephemeral {
3800            return;
3801        }
3802
3803        if let Some(ref mut r) = self.info.reader {
3804            r.current_page = self.current_page;
3805            r.pages_count = self.pages_count;
3806            r.finished = self.finished;
3807            r.dithered = context.fb.dithered();
3808
3809            if self.view_port.zoom_mode == ZoomMode::FitToPage {
3810                r.zoom_mode = None;
3811                r.page_offset = None;
3812            } else {
3813                r.zoom_mode = Some(self.view_port.zoom_mode);
3814                r.page_offset = Some(self.view_port.page_offset);
3815            }
3816
3817            if self.view_port.zoom_mode == ZoomMode::FitToWidth {
3818                r.scroll_mode = Some(self.view_port.scroll_mode);
3819            } else {
3820                r.scroll_mode = None;
3821            }
3822
3823            r.rotation = Some(CURRENT_DEVICE.to_canonical(context.display.rotation));
3824
3825            if (self.contrast.exponent - DEFAULT_CONTRAST_EXPONENT).abs() > f32::EPSILON {
3826                r.contrast_exponent = Some(self.contrast.exponent);
3827                if (self.contrast.gray - DEFAULT_CONTRAST_GRAY).abs() > f32::EPSILON {
3828                    r.contrast_gray = Some(self.contrast.gray);
3829                } else {
3830                    r.contrast_gray = None;
3831                }
3832            } else {
3833                r.contrast_exponent = None;
3834                r.contrast_gray = None;
3835            }
3836
3837            context.library.sync_reader_info(&self.info.file.path, r);
3838        }
3839    }
3840
3841    fn scale_page(
3842        &mut self,
3843        center: Point,
3844        factor: f32,
3845        hub: &Hub,
3846        rq: &mut RenderQueue,
3847        context: &mut Context,
3848    ) {
3849        if self.cache.is_empty() {
3850            return;
3851        }
3852
3853        let current_factor = if let ZoomMode::Custom(sf) = self.view_port.zoom_mode {
3854            sf
3855        } else {
3856            self.cache[&self.current_page].scale
3857        };
3858
3859        if let Some(chunk) = self.chunks.iter().find(|chunk| {
3860            let chunk_rect = chunk.frame - chunk.frame.min + chunk.position;
3861            chunk_rect.includes(center)
3862        }) {
3863            let smw = self.view_port.margin_width;
3864            let frame = self.cache[&chunk.location].frame;
3865            self.current_page = chunk.location;
3866            self.view_port.page_offset = Point::from(
3867                factor * Vec2::from(center - chunk.position + chunk.frame.min - frame.min),
3868            ) - pt!(
3869                self.rect.width() as i32 / 2 - smw,
3870                self.rect.height() as i32 / 2 - smw
3871            );
3872
3873            self.set_zoom_mode(
3874                ZoomMode::Custom(current_factor * factor),
3875                false,
3876                hub,
3877                rq,
3878                context,
3879            );
3880        }
3881    }
3882}
3883
3884impl View for Reader {
3885    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, _bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
3886    fn handle_event(
3887        &mut self,
3888        evt: &Event,
3889        hub: &Hub,
3890        _bus: &mut Bus,
3891        rq: &mut RenderQueue,
3892        context: &mut Context,
3893    ) -> bool {
3894        match *evt {
3895            Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => {
3896                let (_, dir) = CURRENT_DEVICE.mirroring_scheme();
3897                let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4;
3898                hub.send(Event::Select(EntryId::Rotate(n))).ok();
3899                true
3900            }
3901            Event::Gesture(GestureEvent::Swipe { dir, start, end })
3902                if self.rect.includes(start) =>
3903            {
3904                match self.view_port.zoom_mode {
3905                    ZoomMode::FitToPage | ZoomMode::FitToWidth => {
3906                        match dir {
3907                            Dir::West => self.go_to_neighbor(CycleDir::Next, hub, rq, context),
3908                            Dir::East => self.go_to_neighbor(CycleDir::Previous, hub, rq, context),
3909                            Dir::South | Dir::North => {
3910                                self.vertical_scroll(start.y - end.y, hub, rq, context)
3911                            }
3912                        };
3913                    }
3914                    ZoomMode::Custom(_) => {
3915                        match dir {
3916                            Dir::West | Dir::East => {
3917                                self.directional_scroll(pt!(start.x - end.x, 0), hub, rq, context)
3918                            }
3919                            Dir::South | Dir::North => {
3920                                self.directional_scroll(pt!(0, start.y - end.y), hub, rq, context)
3921                            }
3922                        };
3923                    }
3924                }
3925                true
3926            }
3927            Event::Gesture(GestureEvent::SlantedSwipe { start, end, .. })
3928                if self.rect.includes(start) =>
3929            {
3930                if let ZoomMode::Custom(_) = self.view_port.zoom_mode {
3931                    self.directional_scroll(start - end, hub, rq, context);
3932                }
3933                true
3934            }
3935            Event::Gesture(GestureEvent::Spread {
3936                axis: Axis::Horizontal,
3937                center,
3938                ..
3939            }) if self.rect.includes(center) => {
3940                if !self.reflowable {
3941                    self.set_zoom_mode(ZoomMode::FitToWidth, true, hub, rq, context);
3942                }
3943                true
3944            }
3945            Event::Gesture(GestureEvent::Pinch {
3946                axis: Axis::Horizontal,
3947                center,
3948                ..
3949            }) if self.rect.includes(center) => {
3950                self.set_zoom_mode(ZoomMode::FitToPage, true, hub, rq, context);
3951                true
3952            }
3953            Event::Gesture(GestureEvent::Spread {
3954                axis: Axis::Vertical,
3955                center,
3956                ..
3957            }) if self.rect.includes(center) => {
3958                if !self.reflowable {
3959                    self.set_scroll_mode(ScrollMode::Screen, hub, rq, context);
3960                }
3961                true
3962            }
3963            Event::Gesture(GestureEvent::Pinch {
3964                axis: Axis::Vertical,
3965                center,
3966                ..
3967            }) if self.rect.includes(center) => {
3968                if !self.reflowable {
3969                    self.set_scroll_mode(ScrollMode::Page, hub, rq, context);
3970                }
3971                true
3972            }
3973            Event::Gesture(GestureEvent::Spread {
3974                axis: Axis::Diagonal,
3975                center,
3976                factor,
3977            })
3978            | Event::Gesture(GestureEvent::Pinch {
3979                axis: Axis::Diagonal,
3980                center,
3981                factor,
3982            }) if factor.is_finite() && self.rect.includes(center) => {
3983                self.scale_page(center, factor, hub, rq, context);
3984                true
3985            }
3986            Event::Gesture(GestureEvent::Arrow { dir, .. }) => {
3987                match dir {
3988                    Dir::West => {
3989                        if self.search.is_none() {
3990                            self.go_to_chapter(CycleDir::Previous, hub, rq, context);
3991                        } else {
3992                            self.go_to_results_page(0, hub, rq, context);
3993                        }
3994                    }
3995                    Dir::East => {
3996                        if self.search.is_none() {
3997                            self.go_to_chapter(CycleDir::Next, hub, rq, context);
3998                        } else {
3999                            let last_page = self.search.as_ref().unwrap().highlights.len() - 1;
4000                            self.go_to_results_page(last_page, hub, rq, context);
4001                        }
4002                    }
4003                    Dir::North => {
4004                        self.search_direction = LinearDir::Backward;
4005                        self.toggle_search_bar(true, hub, rq, context);
4006                    }
4007                    Dir::South => {
4008                        self.search_direction = LinearDir::Forward;
4009                        self.toggle_search_bar(true, hub, rq, context);
4010                    }
4011                }
4012                true
4013            }
4014            Event::Gesture(GestureEvent::Corner { dir, .. }) => {
4015                match dir {
4016                    DiagDir::NorthWest => self.go_to_bookmark(CycleDir::Previous, hub, rq, context),
4017                    DiagDir::NorthEast => self.go_to_bookmark(CycleDir::Next, hub, rq, context),
4018                    DiagDir::SouthEast => match context.settings.reader.bottom_right_gesture {
4019                        BottomRightGestureAction::ToggleDithered => {
4020                            hub.send(Event::Select(EntryId::ToggleDithered)).ok();
4021                        }
4022                        BottomRightGestureAction::ToggleInverted => {
4023                            hub.send(Event::Select(EntryId::ToggleInverted)).ok();
4024                        }
4025                    },
4026                    DiagDir::SouthWest => {
4027                        if context.settings.frontlight_presets.len() > 1 {
4028                            if context.settings.frontlight {
4029                                let lightsensor_level = if CURRENT_DEVICE.has_lightsensor() {
4030                                    context.lightsensor.level().ok()
4031                                } else {
4032                                    None
4033                                };
4034                                if let Some(ref frontlight_levels) = guess_frontlight(
4035                                    lightsensor_level,
4036                                    &context.settings.frontlight_presets,
4037                                ) {
4038                                    let LightLevels { intensity, warmth } = *frontlight_levels;
4039                                    context.frontlight.set_intensity(intensity);
4040                                    context.frontlight.set_warmth(warmth);
4041                                }
4042                            }
4043                        } else {
4044                            hub.send(Event::ToggleFrontlight).ok();
4045                        }
4046                    }
4047                };
4048                true
4049            }
4050            Event::Gesture(GestureEvent::MultiCorner { dir, .. }) => {
4051                match dir {
4052                    DiagDir::NorthWest => {
4053                        self.go_to_annotation(CycleDir::Previous, hub, rq, context)
4054                    }
4055                    DiagDir::NorthEast => self.go_to_annotation(CycleDir::Next, hub, rq, context),
4056                    _ => (),
4057                }
4058                true
4059            }
4060            Event::Gesture(GestureEvent::Cross(_)) => {
4061                self.quit(context);
4062                hub.send(Event::Back).ok();
4063                true
4064            }
4065            Event::Gesture(GestureEvent::Diamond(_)) => {
4066                self.toggle_bars(None, hub, rq, context);
4067                true
4068            }
4069            Event::Gesture(GestureEvent::HoldButtonShort(code, ..)) => {
4070                match code {
4071                    ButtonCode::Backward => {
4072                        self.go_to_chapter(CycleDir::Previous, hub, rq, context)
4073                    }
4074                    ButtonCode::Forward => self.go_to_chapter(CycleDir::Next, hub, rq, context),
4075                    _ => (),
4076                }
4077                self.held_buttons.insert(code);
4078                true
4079            }
4080            Event::Device(DeviceEvent::Button {
4081                code,
4082                status: ButtonStatus::Released,
4083                ..
4084            }) => {
4085                if !self.held_buttons.remove(&code) {
4086                    match code {
4087                        ButtonCode::Backward => {
4088                            if self.search.is_none() {
4089                                self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4090                            } else {
4091                                self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4092                            }
4093                        }
4094                        ButtonCode::Forward => {
4095                            if self.search.is_none() {
4096                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4097                            } else {
4098                                self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4099                            }
4100                        }
4101                        _ => (),
4102                    }
4103                }
4104                true
4105            }
4106            Event::Device(DeviceEvent::Finger {
4107                position,
4108                status: FingerStatus::Motion,
4109                id,
4110                ..
4111            }) if self.state == State::Selection(id) => {
4112                let mut nearest_word = None;
4113                let mut dmin = u32::MAX;
4114                let dmax =
4115                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4116                let mut rects = Vec::new();
4117
4118                for chunk in &self.chunks {
4119                    for word in &self.text[&chunk.location] {
4120                        let rect =
4121                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4122                        rects.push((rect, word.location));
4123                        let d = position.rdist2(&rect);
4124                        if d < dmax && d < dmin {
4125                            dmin = d;
4126                            nearest_word = Some(word.clone());
4127                        }
4128                    }
4129                }
4130
4131                let selection = self.selection.as_mut().unwrap();
4132
4133                if let Some(word) = nearest_word {
4134                    let old_start = selection.start;
4135                    let old_end = selection.end;
4136                    let (start, end) = word.location.min_max(selection.anchor);
4137
4138                    if start == old_start && end == old_end {
4139                        return true;
4140                    }
4141
4142                    let (start_low, start_high) = old_start.min_max(start);
4143                    let (end_low, end_high) = old_end.min_max(end);
4144
4145                    if start_low != start_high {
4146                        if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4147                            let mut rect = rects[i].0;
4148                            while rects[i].1 < start_high {
4149                                let next_rect = rects[i + 1].0;
4150                                if rect.max.y.min(next_rect.max.y) - rect.min.y.max(next_rect.min.y)
4151                                    > rect.height().min(next_rect.height()) as i32 / 2
4152                                {
4153                                    if rects[i + 1].1 == start_high {
4154                                        if rect.min.x < next_rect.min.x {
4155                                            rect.max.x = next_rect.min.x;
4156                                        } else {
4157                                            rect.min.x = next_rect.max.x;
4158                                        }
4159                                        rect.min.y = rect.min.y.min(next_rect.min.y);
4160                                        rect.max.y = rect.max.y.max(next_rect.max.y);
4161                                    } else {
4162                                        rect.absorb(&next_rect);
4163                                    }
4164                                } else {
4165                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4166                                    rect = next_rect;
4167                                }
4168                                i += 1;
4169                            }
4170                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4171                        }
4172                    }
4173
4174                    if end_low != end_high {
4175                        if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4176                            let mut rect = rects[i].0;
4177                            while rects[i].1 > end_low {
4178                                let prev_rect = rects[i - 1].0;
4179                                if rect.max.y.min(prev_rect.max.y) - rect.min.y.max(prev_rect.min.y)
4180                                    > rect.height().min(prev_rect.height()) as i32 / 2
4181                                {
4182                                    if rects[i - 1].1 == end_low {
4183                                        if rect.min.x > prev_rect.min.x {
4184                                            rect.min.x = prev_rect.max.x;
4185                                        } else {
4186                                            rect.max.x = prev_rect.min.x;
4187                                        }
4188                                        rect.min.y = rect.min.y.min(prev_rect.min.y);
4189                                        rect.max.y = rect.max.y.max(prev_rect.max.y);
4190                                    } else {
4191                                        rect.absorb(&prev_rect);
4192                                    }
4193                                } else {
4194                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4195                                    rect = prev_rect;
4196                                }
4197                                i -= 1;
4198                            }
4199                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4200                        }
4201                    }
4202
4203                    selection.start = start;
4204                    selection.end = end;
4205                }
4206                true
4207            }
4208            Event::Device(DeviceEvent::Finger {
4209                status: FingerStatus::Up,
4210                position,
4211                id,
4212                ..
4213            }) if self.state == State::Selection(id) => {
4214                self.state = State::Idle;
4215                let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4216                self.toggle_selection_menu(
4217                    Rectangle::from_disk(position, radius),
4218                    Some(true),
4219                    rq,
4220                    context,
4221                );
4222                true
4223            }
4224            Event::Gesture(GestureEvent::Tap(center))
4225                if self.state == State::AdjustSelection && self.rect.includes(center) =>
4226            {
4227                let mut found = None;
4228                let mut dmin = u32::MAX;
4229                let dmax =
4230                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4231                let mut rects = Vec::new();
4232
4233                for chunk in &self.chunks {
4234                    for word in &self.text[&chunk.location] {
4235                        let rect =
4236                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4237                        rects.push((rect, word.location));
4238                        let d = center.rdist2(&rect);
4239                        if d < dmax && d < dmin {
4240                            dmin = d;
4241                            found = Some((word.clone(), rects.len() - 1));
4242                        }
4243                    }
4244                }
4245
4246                let selection = self.selection.as_mut().unwrap();
4247
4248                if let Some((word, index)) = found {
4249                    let old_start = selection.start;
4250                    let old_end = selection.end;
4251
4252                    let (start, end) = if word.location <= old_start {
4253                        (word.location, old_end)
4254                    } else if word.location >= old_end {
4255                        (old_start, word.location)
4256                    } else {
4257                        let (start_index, end_index) = (
4258                            rects.iter().position(|(_, loc)| *loc == old_start),
4259                            rects.iter().position(|(_, loc)| *loc == old_end),
4260                        );
4261                        match (start_index, end_index) {
4262                            (Some(s), Some(e)) => {
4263                                if index - s > e - index {
4264                                    (old_start, word.location)
4265                                } else {
4266                                    (word.location, old_end)
4267                                }
4268                            }
4269                            (Some(..), None) => (word.location, old_end),
4270                            (None, Some(..)) => (old_start, word.location),
4271                            (None, None) => (old_start, old_end),
4272                        }
4273                    };
4274
4275                    if start == old_start && end == old_end {
4276                        return true;
4277                    }
4278
4279                    let (start_low, start_high) = old_start.min_max(start);
4280                    let (end_low, end_high) = old_end.min_max(end);
4281
4282                    if start_low != start_high {
4283                        if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4284                            let mut rect = rects[i].0;
4285                            while i < rects.len() - 1 && rects[i].1 < start_high {
4286                                let next_rect = rects[i + 1].0;
4287                                if rect.min.y < next_rect.max.y && next_rect.min.y < rect.max.y {
4288                                    if rects[i + 1].1 == start_high {
4289                                        if rect.min.x < next_rect.min.x {
4290                                            rect.max.x = next_rect.min.x;
4291                                        } else {
4292                                            rect.min.x = next_rect.max.x;
4293                                        }
4294                                        rect.min.y = rect.min.y.min(next_rect.min.y);
4295                                        rect.max.y = rect.max.y.max(next_rect.max.y);
4296                                    } else {
4297                                        rect.absorb(&next_rect);
4298                                    }
4299                                } else {
4300                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4301                                    rect = next_rect;
4302                                }
4303                                i += 1;
4304                            }
4305                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4306                        }
4307                    }
4308
4309                    if end_low != end_high {
4310                        if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4311                            let mut rect = rects[i].0;
4312                            while i > 0 && rects[i].1 > end_low {
4313                                let prev_rect = rects[i - 1].0;
4314                                if rect.min.y < prev_rect.max.y && prev_rect.min.y < rect.max.y {
4315                                    if rects[i - 1].1 == end_low {
4316                                        if rect.min.x > prev_rect.min.x {
4317                                            rect.min.x = prev_rect.max.x;
4318                                        } else {
4319                                            rect.max.x = prev_rect.min.x;
4320                                        }
4321                                        rect.min.y = rect.min.y.min(prev_rect.min.y);
4322                                        rect.max.y = rect.max.y.max(prev_rect.max.y);
4323                                    } else {
4324                                        rect.absorb(&prev_rect);
4325                                    }
4326                                } else {
4327                                    rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4328                                    rect = prev_rect;
4329                                }
4330                                i -= 1;
4331                            }
4332                            rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4333                        }
4334                    }
4335
4336                    selection.start = start;
4337                    selection.end = end;
4338                }
4339                true
4340            }
4341            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
4342                if self.focus.is_some() {
4343                    return true;
4344                }
4345
4346                let mut nearest_link = None;
4347                let mut dmin = u32::MAX;
4348                let dmax =
4349                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4350
4351                for chunk in &self.chunks {
4352                    let (links, _) = self
4353                        .doc
4354                        .lock()
4355                        .ok()
4356                        .and_then(|mut doc| doc.links(Location::Exact(chunk.location)))
4357                        .unwrap_or((Vec::new(), 0));
4358                    for link in links {
4359                        let rect =
4360                            (link.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4361                        let d = center.rdist2(&rect);
4362                        if d < dmax && d < dmin {
4363                            dmin = d;
4364                            nearest_link = Some(link.clone());
4365                        }
4366                    }
4367                }
4368
4369                if let Some(link) = nearest_link.take() {
4370                    let pdf_page = Regex::new(r"^#page=(\d+).*$").unwrap();
4371                    let djvu_page = Regex::new(r"^#([+-])?(\d+)$").unwrap();
4372                    let toc_page = Regex::new(r"^@(.+)$").unwrap();
4373                    if let Some(caps) = toc_page.captures(&link.text) {
4374                        let loc_opt = if caps[1].chars().all(|c| c.is_digit(10)) {
4375                            caps[1].parse::<usize>().map(Location::Exact).ok()
4376                        } else {
4377                            Some(Location::Uri(caps[1].to_string()))
4378                        };
4379                        if let Some(location) = loc_opt {
4380                            self.quit(context);
4381                            hub.send(Event::Back).ok();
4382                            hub.send(Event::GoToLocation(location)).ok();
4383                        }
4384                    } else if let Some(caps) = pdf_page.captures(&link.text) {
4385                        if let Ok(index) = caps[1].parse::<usize>() {
4386                            self.go_to_page(index.saturating_sub(1), true, hub, rq, context);
4387                        }
4388                    } else if let Some(caps) = djvu_page.captures(&link.text) {
4389                        if let Ok(mut index) = caps[2].parse::<usize>() {
4390                            let prefix = caps.get(1).map(|m| m.as_str());
4391                            match prefix {
4392                                Some("-") => index = self.current_page.saturating_sub(index),
4393                                Some("+") => index += self.current_page,
4394                                _ => index = index.saturating_sub(1),
4395                            }
4396                            self.go_to_page(index, true, hub, rq, context);
4397                        }
4398                    } else {
4399                        let mut doc = self.doc.lock().unwrap();
4400                        let loc = Location::LocalUri(self.current_page, link.text.clone());
4401                        if let Some(location) = doc.resolve_location(loc) {
4402                            hub.send(Event::GoTo(location)).ok();
4403                        } else if link.text.starts_with("https:") || link.text.starts_with("http:")
4404                        {
4405                            if let Some(path) = context.settings.external_urls_queue.as_ref() {
4406                                if let Ok(mut file) =
4407                                    OpenOptions::new().create(true).append(true).open(path)
4408                                {
4409                                    if let Err(e) = writeln!(file, "{}", link.text) {
4410                                        error!("Couldn't write to {}: {:#}.", path.display(), e);
4411                                    } else {
4412                                        let message = format!("Queued {}.", link.text);
4413                                        let notif = Notification::new(
4414                                            None, message, false, hub, rq, context,
4415                                        );
4416                                        self.children.push(Box::new(notif) as Box<dyn View>);
4417                                    }
4418                                }
4419                            }
4420                        } else {
4421                            error!("Can't resolve URI: {}.", link.text);
4422                        }
4423                    }
4424                    return true;
4425                }
4426
4427                if let ZoomMode::Custom(_) = self.view_port.zoom_mode {
4428                    let dx = self.rect.width() as i32 - 2 * self.view_port.margin_width;
4429                    let dy = self.rect.height() as i32 - 2 * self.view_port.margin_width;
4430                    match Region::from_point(
4431                        center,
4432                        self.rect,
4433                        context.settings.reader.strip_width,
4434                        context.settings.reader.corner_width,
4435                    ) {
4436                        Region::Corner(diag_dir) => match diag_dir {
4437                            DiagDir::NorthEast => {
4438                                self.directional_scroll(pt!(dx, -dy), hub, rq, context)
4439                            }
4440                            DiagDir::SouthEast => {
4441                                self.directional_scroll(pt!(dx, dy), hub, rq, context)
4442                            }
4443                            DiagDir::SouthWest => {
4444                                self.directional_scroll(pt!(-dx, dy), hub, rq, context)
4445                            }
4446                            DiagDir::NorthWest => {
4447                                self.directional_scroll(pt!(-dx, -dy), hub, rq, context)
4448                            }
4449                        },
4450                        Region::Strip(dir) => match dir {
4451                            Dir::North => self.directional_scroll(pt!(0, -dy), hub, rq, context),
4452                            Dir::East => self.directional_scroll(pt!(dx, 0), hub, rq, context),
4453                            Dir::South => self.directional_scroll(pt!(0, dy), hub, rq, context),
4454                            Dir::West => self.directional_scroll(pt!(-dx, 0), hub, rq, context),
4455                        },
4456                        Region::Center => self.toggle_bars(None, hub, rq, context),
4457                    }
4458
4459                    return true;
4460                }
4461
4462                match Region::from_point(
4463                    center,
4464                    self.rect,
4465                    context.settings.reader.strip_width,
4466                    context.settings.reader.corner_width,
4467                ) {
4468                    Region::Corner(diag_dir) => match diag_dir {
4469                        DiagDir::NorthWest => self.go_to_last_page(hub, rq, context),
4470                        DiagDir::NorthEast => self.toggle_bookmark(rq),
4471                        DiagDir::SouthEast => {
4472                            if self.search.is_none() {
4473                                match context.settings.reader.south_east_corner {
4474                                    SouthEastCornerAction::GoToPage => {
4475                                        hub.send(Event::Toggle(ToggleEvent::View(
4476                                            ViewId::GoToPage,
4477                                        )))
4478                                        .ok();
4479                                    }
4480                                    SouthEastCornerAction::NextPage => {
4481                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4482                                    }
4483                                }
4484                            } else {
4485                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4486                            }
4487                        }
4488                        DiagDir::SouthWest => {
4489                            if self.search.is_none() {
4490                                if self.ephemeral
4491                                    && self.info.file.path == PathBuf::from(MEM_SCHEME)
4492                                {
4493                                    self.quit(context);
4494                                    hub.send(Event::Back).ok();
4495                                } else {
4496                                    hub.send(Event::Show(ViewId::TableOfContents)).ok();
4497                                }
4498                            } else {
4499                                self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4500                            }
4501                        }
4502                    },
4503                    Region::Strip(dir) => match dir {
4504                        Dir::West => {
4505                            if self.search.is_none() {
4506                                match context.settings.reader.west_strip {
4507                                    WestStripAction::PreviousPage => {
4508                                        self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4509                                    }
4510                                    WestStripAction::NextPage => {
4511                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4512                                    }
4513                                    WestStripAction::None => (),
4514                                }
4515                            } else {
4516                                self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4517                            }
4518                        }
4519                        Dir::East => {
4520                            if self.search.is_none() {
4521                                match context.settings.reader.east_strip {
4522                                    EastStripAction::PreviousPage => {
4523                                        self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4524                                    }
4525                                    EastStripAction::NextPage => {
4526                                        self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4527                                    }
4528                                    EastStripAction::None => (),
4529                                }
4530                            } else {
4531                                self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4532                            }
4533                        }
4534                        Dir::South => match context.settings.reader.south_strip {
4535                            SouthStripAction::ToggleBars => {
4536                                self.toggle_bars(None, hub, rq, context);
4537                            }
4538                            SouthStripAction::NextPage => {
4539                                self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4540                            }
4541                        },
4542                        Dir::North => self.toggle_bars(None, hub, rq, context),
4543                    },
4544                    Region::Center => self.toggle_bars(None, hub, rq, context),
4545                }
4546
4547                true
4548            }
4549            Event::Gesture(GestureEvent::HoldFingerShort(center, id))
4550                if self.rect.includes(center) =>
4551            {
4552                if self.focus.is_some() {
4553                    return true;
4554                }
4555
4556                let mut found = None;
4557                let mut dmin = u32::MAX;
4558                let dmax =
4559                    (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4560
4561                if let Some(rect) = self.selection_rect() {
4562                    let d = center.rdist2(&rect);
4563                    if d < dmax {
4564                        self.state = State::Idle;
4565                        let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4566                        self.toggle_selection_menu(
4567                            Rectangle::from_disk(center, radius),
4568                            Some(true),
4569                            rq,
4570                            context,
4571                        );
4572                    }
4573                    return true;
4574                }
4575
4576                for chunk in &self.chunks {
4577                    for word in &self.text[&chunk.location] {
4578                        let rect =
4579                            (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4580                        let d = center.rdist2(&rect);
4581                        if d < dmax && d < dmin {
4582                            dmin = d;
4583                            found = Some((word.clone(), rect));
4584                        }
4585                    }
4586                }
4587
4588                if let Some((nearest_word, rect)) = found {
4589                    let anchor = nearest_word.location;
4590                    if let Some(annot) = self
4591                        .annotations
4592                        .values()
4593                        .flatten()
4594                        .find(|annot| anchor >= annot.selection[0] && anchor <= annot.selection[1])
4595                        .cloned()
4596                    {
4597                        let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4598                        self.toggle_annotation_menu(
4599                            &annot,
4600                            Rectangle::from_disk(center, radius),
4601                            Some(true),
4602                            rq,
4603                            context,
4604                        );
4605                    } else {
4606                        self.selection = Some(Selection {
4607                            start: anchor,
4608                            end: anchor,
4609                            anchor,
4610                        });
4611                        self.state = State::Selection(id);
4612                        rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4613                    }
4614                }
4615
4616                true
4617            }
4618            Event::Gesture(GestureEvent::HoldFingerLong(center, _))
4619                if self.rect.includes(center) =>
4620            {
4621                if let Some(text) = self.selected_text() {
4622                    let query = text
4623                        .trim_matches(|c: char| !c.is_alphanumeric())
4624                        .to_string();
4625                    let language = self.info.language.clone();
4626                    hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
4627                        query,
4628                        language,
4629                    })))
4630                    .ok();
4631                }
4632                self.selection = None;
4633                self.state = State::Idle;
4634                true
4635            }
4636            Event::Update(mode) => {
4637                self.update(Some(mode), hub, rq, context);
4638                true
4639            }
4640            Event::LoadPixmap(location) => {
4641                self.load_pixmap(location);
4642                true
4643            }
4644            Event::Submit(ViewId::GoToPageInput, ref text) => {
4645                let re = Regex::new(r#"^([-+'])?(.+)$"#).unwrap();
4646                if let Some(caps) = re.captures(text) {
4647                    let prefix = caps.get(1).map(|m| m.as_str());
4648                    if prefix == Some("'") {
4649                        if let Some(location) = self.find_page_by_name(&caps[2]) {
4650                            self.go_to_page(location, true, hub, rq, context);
4651                        }
4652                    } else {
4653                        if text == "_" {
4654                            let location =
4655                                (context.rng.next_u64() % self.pages_count as u64) as usize;
4656                            self.go_to_page(location, true, hub, rq, context);
4657                        } else if text == "(" {
4658                            self.go_to_page(0, true, hub, rq, context);
4659                        } else if text == ")" {
4660                            self.go_to_page(
4661                                self.pages_count.saturating_sub(1),
4662                                true,
4663                                hub,
4664                                rq,
4665                                context,
4666                            );
4667                        } else if let Some(percent) = text.strip_suffix('%') {
4668                            if let Ok(number) = percent.parse::<f64>() {
4669                                let location =
4670                                    (number.max(0.0).min(100.0) / 100.0 * self.pages_count as f64)
4671                                        .round() as usize;
4672                                self.go_to_page(location, true, hub, rq, context);
4673                            }
4674                        } else if let Ok(number) = caps[2].parse::<f64>() {
4675                            let location = {
4676                                let bpp = if self.synthetic { BYTES_PER_PAGE } else { 1.0 };
4677                                let mut index = (number * bpp).max(0.0).round() as usize;
4678                                match prefix {
4679                                    Some("-") => index = self.current_page.saturating_sub(index),
4680                                    Some("+") => index += self.current_page,
4681                                    _ => index = index.saturating_sub(1 / (bpp as usize)),
4682                                }
4683                                index
4684                            };
4685                            self.go_to_page(location, true, hub, rq, context);
4686                        }
4687                    }
4688                }
4689                true
4690            }
4691            Event::Submit(ViewId::GoToResultsPageInput, ref text) => {
4692                if let Ok(index) = text.parse::<usize>() {
4693                    self.go_to_results_page(index.saturating_sub(1), hub, rq, context);
4694                }
4695                true
4696            }
4697            Event::Submit(ViewId::NamePageInput, ref text) => {
4698                if !text.is_empty() {
4699                    if let Some(ref mut r) = self.info.reader {
4700                        r.page_names.insert(self.current_page, text.to_string());
4701                    }
4702                }
4703                self.toggle_keyboard(false, None, hub, rq, context);
4704                true
4705            }
4706            Event::Submit(ViewId::EditNoteInput, ref note) => {
4707                let selection = self.selection.take().map(|sel| [sel.start, sel.end]);
4708
4709                if let Some(sel) = selection {
4710                    let text = self.text_excerpt(sel).unwrap();
4711                    if let Some(r) = self.info.reader.as_mut() {
4712                        r.annotations.push(Annotation {
4713                            selection: sel,
4714                            note: note.to_string(),
4715                            text,
4716                            modified: Local::now().naive_local(),
4717                        });
4718                    }
4719                    if let Some(rect) = self.text_rect(sel) {
4720                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4721                    }
4722                } else {
4723                    if let Some(sel) = self.target_annotation.take() {
4724                        if let Some(annot) = self.find_annotation_mut(sel) {
4725                            annot.note = note.to_string();
4726                            annot.modified = Local::now().naive_local();
4727                        }
4728                        if let Some(rect) = self.text_rect(sel) {
4729                            rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4730                        }
4731                    }
4732                }
4733
4734                self.update_annotations();
4735                self.toggle_keyboard(false, None, hub, rq, context);
4736                true
4737            }
4738            Event::Submit(ViewId::ReaderSearchInput, ref text) => {
4739                match make_query(text) {
4740                    Some(query) => {
4741                        self.search(text, query, hub, rq);
4742                        self.toggle_keyboard(false, None, hub, rq, context);
4743                        self.toggle_results_bar(true, rq, context);
4744                    }
4745                    None => {
4746                        let notif = Notification::new(
4747                            None,
4748                            "Invalid search query.".to_string(),
4749                            false,
4750                            hub,
4751                            rq,
4752                            context,
4753                        );
4754                        self.children.push(Box::new(notif) as Box<dyn View>);
4755                    }
4756                }
4757                true
4758            }
4759            Event::Page(dir) => {
4760                self.go_to_neighbor(dir, hub, rq, context);
4761                true
4762            }
4763            Event::GoTo(location) | Event::Select(EntryId::GoTo(location)) => {
4764                self.go_to_page(location, true, hub, rq, context);
4765                true
4766            }
4767            Event::GoToLocation(ref location) => {
4768                let offset_opt = {
4769                    let mut doc = self.doc.lock().unwrap();
4770                    doc.resolve_location(location.clone())
4771                };
4772                if let Some(offset) = offset_opt {
4773                    self.go_to_page(offset, true, hub, rq, context);
4774                }
4775                true
4776            }
4777            Event::Chapter(dir) => {
4778                self.go_to_chapter(dir, hub, rq, context);
4779                true
4780            }
4781            Event::ResultsPage(dir) => {
4782                self.go_to_results_neighbor(dir, hub, rq, context);
4783                true
4784            }
4785            Event::CropMargins(ref margin) => {
4786                let current_page = self.current_page;
4787                self.crop_margins(current_page, margin.as_ref(), hub, rq, context);
4788                true
4789            }
4790            Event::Toggle(ToggleEvent::View(ViewId::TopBottomBars)) => {
4791                self.toggle_bars(None, hub, rq, context);
4792                true
4793            }
4794            Event::Toggle(ToggleEvent::View(ViewId::GoToPage)) => {
4795                self.toggle_go_to_page(None, ViewId::GoToPage, hub, rq, context);
4796                true
4797            }
4798            Event::Toggle(ToggleEvent::View(ViewId::GoToResultsPage)) => {
4799                self.toggle_go_to_page(None, ViewId::GoToResultsPage, hub, rq, context);
4800                true
4801            }
4802            Event::Slider(SliderId::FontSize, font_size, FingerStatus::Up) => {
4803                self.set_font_size(font_size, hub, rq, context);
4804                true
4805            }
4806            Event::Slider(SliderId::ContrastExponent, exponent, FingerStatus::Up) => {
4807                self.set_contrast_exponent(exponent, hub, rq, context);
4808                true
4809            }
4810            Event::Slider(SliderId::ContrastGray, gray, FingerStatus::Up) => {
4811                self.set_contrast_gray(gray, hub, rq, context);
4812                true
4813            }
4814            Event::ToggleNear(ViewId::TitleMenu, rect) => {
4815                self.toggle_title_menu(rect, None, rq, context);
4816                true
4817            }
4818            Event::ToggleNear(ViewId::MainMenu, rect) => {
4819                toggle_main_menu(self, rect, None, rq, context);
4820                true
4821            }
4822            Event::ToggleNear(ViewId::BatteryMenu, rect) => {
4823                toggle_battery_menu(self, rect, None, rq, context);
4824                true
4825            }
4826            Event::ToggleNear(ViewId::ClockMenu, rect) => {
4827                toggle_clock_menu(self, rect, None, rq, context);
4828                true
4829            }
4830            Event::ToggleNear(ViewId::MarginCropperMenu, rect) => {
4831                self.toggle_margin_cropper_menu(rect, None, rq, context);
4832                true
4833            }
4834            Event::ToggleNear(ViewId::SearchMenu, rect) => {
4835                self.toggle_search_menu(rect, None, rq, context);
4836                true
4837            }
4838            Event::ToggleNear(ViewId::FontFamilyMenu, rect) => {
4839                self.toggle_font_family_menu(rect, None, rq, context);
4840                true
4841            }
4842            Event::ToggleNear(ViewId::FontSizeMenu, rect) => {
4843                self.toggle_font_size_menu(rect, None, rq, context);
4844                true
4845            }
4846            Event::ToggleNear(ViewId::TextAlignMenu, rect) => {
4847                self.toggle_text_align_menu(rect, None, rq, context);
4848                true
4849            }
4850            Event::ToggleNear(ViewId::MarginWidthMenu, rect) => {
4851                self.toggle_margin_width_menu(rect, None, rq, context);
4852                true
4853            }
4854            Event::ToggleNear(ViewId::LineHeightMenu, rect) => {
4855                self.toggle_line_height_menu(rect, None, rq, context);
4856                true
4857            }
4858            Event::ToggleNear(ViewId::ContrastExponentMenu, rect) => {
4859                self.toggle_contrast_exponent_menu(rect, None, rq, context);
4860                true
4861            }
4862            Event::ToggleNear(ViewId::ContrastGrayMenu, rect) => {
4863                self.toggle_contrast_gray_menu(rect, None, rq, context);
4864                true
4865            }
4866            Event::ToggleNear(ViewId::PageMenu, rect) => {
4867                self.toggle_page_menu(rect, None, rq, context);
4868                true
4869            }
4870            Event::Close(ViewId::MainMenu) => {
4871                toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
4872                true
4873            }
4874            Event::Close(ViewId::SearchBar) => {
4875                self.toggle_results_bar(false, rq, context);
4876                self.toggle_search_bar(false, hub, rq, context);
4877                if let Some(ref mut s) = self.search {
4878                    s.running.store(false, AtomicOrdering::Relaxed);
4879                    self.render_results(rq);
4880                    self.search = None;
4881                }
4882                true
4883            }
4884            Event::Close(ViewId::GoToPage) => {
4885                self.toggle_go_to_page(Some(false), ViewId::GoToPage, hub, rq, context);
4886                true
4887            }
4888            Event::Close(ViewId::GoToResultsPage) => {
4889                self.toggle_go_to_page(Some(false), ViewId::GoToResultsPage, hub, rq, context);
4890                true
4891            }
4892            Event::Close(ViewId::SelectionMenu) => {
4893                if self.state == State::Idle && self.target_annotation.is_none() {
4894                    if let Some(rect) = self.selection_rect() {
4895                        self.selection = None;
4896                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4897                    }
4898                }
4899                false
4900            }
4901            Event::Close(ViewId::EditNote) => {
4902                self.toggle_edit_note(None, Some(false), hub, rq, context);
4903                if let Some(rect) = self.selection_rect() {
4904                    self.selection = None;
4905                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4906                }
4907                self.target_annotation = None;
4908                false
4909            }
4910            Event::Close(ViewId::NamePage) => {
4911                self.toggle_keyboard(false, None, hub, rq, context);
4912                false
4913            }
4914            Event::Show(ViewId::TableOfContents) => {
4915                {
4916                    self.toggle_bars(Some(false), hub, rq, context);
4917                }
4918                let mut doc = self.doc.lock().unwrap();
4919                if let Some(toc) = self
4920                    .toc()
4921                    .or_else(|| doc.toc())
4922                    .filter(|toc| !toc.is_empty())
4923                {
4924                    let chap = doc.chapter(self.current_page, &toc).map(|(c, _)| c);
4925                    let chap_index = chap.map_or(usize::MAX, |chap| chap.index);
4926                    let html = toc_as_html(&toc, chap_index);
4927                    let link_uri = chap.and_then(|chap| match chap.location {
4928                        Location::Uri(ref uri) => Some(format!("@{}", uri)),
4929                        Location::Exact(offset) => Some(format!("@{}", offset)),
4930                        _ => None,
4931                    });
4932                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4933                }
4934                true
4935            }
4936            Event::Select(EntryId::Annotations) => {
4937                self.toggle_bars(Some(false), hub, rq, context);
4938                let mut starts = self
4939                    .annotations
4940                    .values()
4941                    .flatten()
4942                    .map(|annot| annot.selection[0])
4943                    .collect::<Vec<TextLocation>>();
4944                starts.sort();
4945                let active_range = starts.first().cloned().zip(starts.last().cloned());
4946                if let Some(mut annotations) =
4947                    self.info.reader.as_ref().map(|r| &r.annotations).cloned()
4948                {
4949                    annotations.sort_by(|a, b| a.selection[0].cmp(&b.selection[0]));
4950                    let html = annotations_as_html(&annotations, active_range);
4951                    let link_uri = annotations
4952                        .iter()
4953                        .filter(|annot| annot.selection[0].location() <= self.current_page)
4954                        .max_by_key(|annot| annot.selection[0])
4955                        .map(|annot| format!("@{}", annot.selection[0].location()));
4956                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4957                }
4958                true
4959            }
4960            Event::Select(EntryId::Bookmarks) => {
4961                self.toggle_bars(Some(false), hub, rq, context);
4962                if let Some(bookmarks) = self.info.reader.as_ref().map(|r| &r.bookmarks) {
4963                    let html = bookmarks_as_html(bookmarks, self.current_page, self.synthetic);
4964                    let link_uri = bookmarks
4965                        .range(..=self.current_page)
4966                        .next_back()
4967                        .map(|index| format!("@{}", index));
4968                    hub.send(Event::OpenHtml(html, link_uri)).ok();
4969                }
4970                true
4971            }
4972            Event::Show(ViewId::SearchBar) => {
4973                self.toggle_search_bar(true, hub, rq, context);
4974                true
4975            }
4976            Event::Show(ViewId::MarginCropper) => {
4977                self.toggle_margin_cropper(true, hub, rq, context);
4978                true
4979            }
4980            Event::Close(ViewId::MarginCropper) => {
4981                self.toggle_margin_cropper(false, hub, rq, context);
4982                true
4983            }
4984            Event::SearchResult(location, ref rects) => {
4985                if self.search.is_none() {
4986                    return true;
4987                }
4988
4989                let mut results_count = 0;
4990
4991                if let Some(ref mut s) = self.search {
4992                    let pages_count = s.highlights.len();
4993                    s.highlights
4994                        .entry(location)
4995                        .or_insert_with(Vec::new)
4996                        .push(rects.clone());
4997                    s.results_count += 1;
4998                    results_count = s.results_count;
4999                    if results_count > 1
5000                        && location <= self.current_page
5001                        && s.highlights.len() > pages_count
5002                    {
5003                        s.current_page += 1;
5004                    }
5005                }
5006
5007                self.update_results_bar(rq);
5008
5009                if results_count == 1 {
5010                    self.toggle_results_bar(false, rq, context);
5011                    self.toggle_search_bar(false, hub, rq, context);
5012                    self.go_to_page(location, true, hub, rq, context);
5013                } else if location == self.current_page {
5014                    self.update(None, hub, rq, context);
5015                }
5016
5017                true
5018            }
5019            Event::EndOfSearch => {
5020                let results_count = self
5021                    .search
5022                    .as_ref()
5023                    .map(|s| s.results_count)
5024                    .unwrap_or(usize::MAX);
5025                if results_count == 0 {
5026                    let notif = Notification::new(
5027                        None,
5028                        "No search results.".to_string(),
5029                        false,
5030                        hub,
5031                        rq,
5032                        context,
5033                    );
5034                    self.children.push(Box::new(notif) as Box<dyn View>);
5035                    self.toggle_search_bar(true, hub, rq, context);
5036                    hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok();
5037                }
5038                true
5039            }
5040            Event::Select(EntryId::AnnotateSelection) => {
5041                self.toggle_edit_note(None, Some(true), hub, rq, context);
5042                true
5043            }
5044            Event::Select(EntryId::HighlightSelection) => {
5045                if let Some(sel) = self.selection.take() {
5046                    let text = self.text_excerpt([sel.start, sel.end]).unwrap();
5047                    if let Some(r) = self.info.reader.as_mut() {
5048                        r.annotations.push(Annotation {
5049                            selection: [sel.start, sel.end],
5050                            note: String::new(),
5051                            text,
5052                            modified: Local::now().naive_local(),
5053                        });
5054                    }
5055                    if let Some(rect) = self.text_rect([sel.start, sel.end]) {
5056                        rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5057                    }
5058                    self.update_annotations();
5059                }
5060
5061                true
5062            }
5063            Event::Select(EntryId::DefineSelection) => {
5064                if let Some(text) = self.selected_text() {
5065                    let query = text
5066                        .trim_matches(|c: char| !c.is_alphanumeric())
5067                        .to_string();
5068                    let language = self.info.language.clone();
5069                    hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
5070                        query,
5071                        language,
5072                    })))
5073                    .ok();
5074                }
5075                self.selection = None;
5076                true
5077            }
5078            Event::Select(EntryId::SearchForSelection) => {
5079                if let Some(text) = self.selected_text() {
5080                    let text = text.trim_matches(|c: char| !c.is_alphanumeric());
5081                    match make_query(text) {
5082                        Some(query) => {
5083                            self.search(text, query, hub, rq);
5084                        }
5085                        None => {
5086                            let notif = Notification::new(
5087                                None,
5088                                "Invalid search query.".to_string(),
5089                                false,
5090                                hub,
5091                                rq,
5092                                context,
5093                            );
5094                            self.children.push(Box::new(notif) as Box<dyn View>);
5095                        }
5096                    }
5097                }
5098                if let Some(rect) = self.selection_rect() {
5099                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5100                }
5101                self.selection = None;
5102                true
5103            }
5104            Event::Select(EntryId::GoToSelectedPageName) => {
5105                if let Some(loc) = self.selected_text().and_then(|text| {
5106                    let end = text
5107                        .find(|c: char| {
5108                            !c.is_ascii_digit()
5109                                && Digit::from_char(c).is_err()
5110                                && !c.is_ascii_uppercase()
5111                        })
5112                        .unwrap_or_else(|| text.len());
5113                    self.find_page_by_name(&text[..end])
5114                }) {
5115                    self.go_to_page(loc, true, hub, rq, context);
5116                }
5117                if let Some(rect) = self.selection_rect() {
5118                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5119                }
5120                self.selection = None;
5121                true
5122            }
5123            Event::Select(EntryId::AdjustSelection) => {
5124                self.state = State::AdjustSelection;
5125                true
5126            }
5127            Event::Select(EntryId::EditAnnotationNote(sel)) => {
5128                let text = self
5129                    .find_annotation_ref(sel)
5130                    .map(|annot| annot.note.clone());
5131                self.toggle_edit_note(text, Some(true), hub, rq, context);
5132                self.target_annotation = Some(sel);
5133                true
5134            }
5135            Event::Select(EntryId::RemoveAnnotationNote(sel)) => {
5136                if let Some(annot) = self.find_annotation_mut(sel) {
5137                    annot.note.clear();
5138                    annot.modified = Local::now().naive_local();
5139                    self.update_annotations();
5140                }
5141                if let Some(rect) = self.text_rect(sel) {
5142                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5143                }
5144                true
5145            }
5146            Event::Select(EntryId::RemoveAnnotation(sel)) => {
5147                if let Some(annotations) = self.info.reader.as_mut().map(|r| &mut r.annotations) {
5148                    annotations.retain(|annot| {
5149                        annot.selection[0] != sel[0] || annot.selection[1] != sel[1]
5150                    });
5151                    self.update_annotations();
5152                }
5153                if let Some(rect) = self.text_rect(sel) {
5154                    rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5155                }
5156                true
5157            }
5158            Event::Select(EntryId::SetZoomMode(zoom_mode)) => {
5159                self.set_zoom_mode(zoom_mode, true, hub, rq, context);
5160                true
5161            }
5162            Event::Select(EntryId::SetScrollMode(scroll_mode)) => {
5163                self.set_scroll_mode(scroll_mode, hub, rq, context);
5164                true
5165            }
5166            Event::Select(EntryId::Save) => {
5167                let name = format!(
5168                    "{}-{}.{}",
5169                    self.info.title.to_lowercase().replace(' ', "_"),
5170                    Local::now().format("%Y%m%d_%H%M%S"),
5171                    self.info.file.kind
5172                );
5173                let doc = self.doc.lock().unwrap();
5174                let msg = match doc.save(&name) {
5175                    Err(e) => format!("{}", e),
5176                    Ok(()) => format!("Saved {}.", name),
5177                };
5178                let notif = Notification::new(None, msg, false, hub, rq, context);
5179                self.children.push(Box::new(notif) as Box<dyn View>);
5180                true
5181            }
5182            Event::Select(EntryId::ApplyCroppings(index, scheme)) => {
5183                self.info.reader.as_mut().map(|r| {
5184                    if r.cropping_margins.is_none() {
5185                        r.cropping_margins = Some(CroppingMargins::Any(Margin::default()));
5186                    }
5187                    r.cropping_margins.as_mut().map(|c| c.apply(index, scheme))
5188                });
5189                true
5190            }
5191            Event::Select(EntryId::RemoveCroppings) => {
5192                if let Some(r) = self.info.reader.as_mut() {
5193                    r.cropping_margins = None;
5194                }
5195                self.cache.clear();
5196                self.update(None, hub, rq, context);
5197                true
5198            }
5199            Event::Select(EntryId::SearchDirection(dir)) => {
5200                self.search_direction = dir;
5201                true
5202            }
5203            Event::Select(EntryId::SetFontFamily(ref font_family)) => {
5204                self.set_font_family(font_family, hub, rq, context);
5205                true
5206            }
5207            Event::Select(EntryId::SetTextAlign(text_align)) => {
5208                self.set_text_align(text_align, hub, rq, context);
5209                true
5210            }
5211            Event::Select(EntryId::SetFontSize(v)) => {
5212                let font_size = self
5213                    .info
5214                    .reader
5215                    .as_ref()
5216                    .and_then(|r| r.font_size)
5217                    .unwrap_or(context.settings.reader.font_size);
5218                let font_size = font_size - 1.0 + v as f32 / 10.0;
5219                self.set_font_size(font_size, hub, rq, context);
5220                true
5221            }
5222            Event::Select(EntryId::SetMarginWidth(width)) => {
5223                self.set_margin_width(width, hub, rq, context);
5224                true
5225            }
5226            Event::Select(EntryId::SetLineHeight(v)) => {
5227                let line_height = 1.0 + v as f32 / 10.0;
5228                self.set_line_height(line_height, hub, rq, context);
5229                true
5230            }
5231            Event::Select(EntryId::SetContrastExponent(v)) => {
5232                let exponent = 1.0 + v as f32 / 2.0;
5233                self.set_contrast_exponent(exponent, hub, rq, context);
5234                true
5235            }
5236            Event::Select(EntryId::SetContrastGray(v)) => {
5237                let gray = ((1 << 8) - (1 << (8 - v))) as f32;
5238                self.set_contrast_gray(gray, hub, rq, context);
5239                true
5240            }
5241            Event::Select(EntryId::SetPageName) => {
5242                self.toggle_name_page(None, hub, rq, context);
5243                true
5244            }
5245            Event::Select(EntryId::RemovePageName) => {
5246                if let Some(ref mut r) = self.info.reader {
5247                    r.page_names.remove(&self.current_page);
5248                }
5249                true
5250            }
5251            Event::Select(EntryId::ToggleInverted) => {
5252                self.update_noninverted_regions(!context.fb.inverted());
5253                false
5254            }
5255            Event::Reseed => {
5256                self.reseed(rq, context);
5257                true
5258            }
5259            Event::ToggleFrontlight => {
5260                if let Some(index) = locate::<TopBar>(self) {
5261                    self.child_mut(index)
5262                        .downcast_mut::<TopBar>()
5263                        .unwrap()
5264                        .update_frontlight_icon(rq, context);
5265                }
5266                true
5267            }
5268            Event::Device(DeviceEvent::Button {
5269                code: ButtonCode::Home,
5270                status: ButtonStatus::Pressed,
5271                ..
5272            }) => {
5273                self.quit(context);
5274                hub.send(Event::Back).ok();
5275                true
5276            }
5277            Event::Select(EntryId::Quit)
5278            | Event::Select(EntryId::Reboot)
5279            | Event::Select(EntryId::Restart)
5280            | Event::Back
5281            | Event::Suspend => {
5282                self.quit(context);
5283                false
5284            }
5285            Event::Focus(v) => {
5286                if self.focus != v {
5287                    if let Some(ViewId::ReaderSearchInput) = v {
5288                        self.toggle_results_bar(false, rq, context);
5289                        if let Some(ref mut s) = self.search {
5290                            s.running.store(false, AtomicOrdering::Relaxed);
5291                        }
5292                        self.render_results(rq);
5293                        self.search = None;
5294                    }
5295                    self.focus = v;
5296                    if v.is_some() {
5297                        self.toggle_keyboard(true, v, hub, rq, context);
5298                    }
5299                }
5300                true
5301            }
5302            _ => false,
5303        }
5304    }
5305
5306    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts), fields(rect = ?rect)))]
5307    fn render(&self, fb: &mut dyn Framebuffer, rect: Rectangle, _fonts: &mut Fonts) {
5308        fb.draw_rectangle(&rect, WHITE);
5309
5310        for chunk in &self.chunks {
5311            let Resource {
5312                ref pixmap, scale, ..
5313            } = self.cache[&chunk.location];
5314            let chunk_rect = chunk.frame - chunk.frame.min + chunk.position;
5315
5316            if let Some(region_rect) = rect.intersection(&chunk_rect) {
5317                let chunk_frame = region_rect - chunk.position + chunk.frame.min;
5318                let chunk_position = region_rect.min;
5319                fb.draw_framed_pixmap_contrast(
5320                    pixmap,
5321                    &chunk_frame,
5322                    chunk_position,
5323                    self.contrast.exponent,
5324                    self.contrast.gray,
5325                );
5326
5327                if let Some(rects) = self.noninverted_regions.get(&chunk.location) {
5328                    for r in rects {
5329                        let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5330                        if let Some(ref image_rect) = rect.intersection(&region_rect) {
5331                            fb.invert_region(image_rect);
5332                        }
5333                    }
5334                }
5335
5336                if let Some(groups) = self
5337                    .search
5338                    .as_ref()
5339                    .and_then(|s| s.highlights.get(&chunk.location))
5340                {
5341                    for rects in groups {
5342                        let mut last_rect: Option<Rectangle> = None;
5343                        for r in rects {
5344                            let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5345                            if let Some(ref search_rect) = rect.intersection(&region_rect) {
5346                                fb.invert_region(search_rect);
5347                            }
5348                            if let Some(last) = last_rect {
5349                                if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5350                                    > rect.height().min(last.height()) as i32 / 2
5351                                    && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5352                                {
5353                                    let space = if last.max.x < rect.min.x {
5354                                        rect![
5355                                            last.max.x,
5356                                            (last.min.y + rect.min.y) / 2,
5357                                            rect.min.x,
5358                                            (last.max.y + rect.max.y) / 2
5359                                        ]
5360                                    } else {
5361                                        rect![
5362                                            rect.max.x,
5363                                            (last.min.y + rect.min.y) / 2,
5364                                            last.min.x,
5365                                            (last.max.y + rect.max.y) / 2
5366                                        ]
5367                                    };
5368                                    if let Some(ref res_rect) = space.intersection(&region_rect) {
5369                                        fb.invert_region(res_rect);
5370                                    }
5371                                }
5372                            }
5373                            last_rect = Some(rect);
5374                        }
5375                    }
5376                }
5377
5378                if let Some(annotations) = self.annotations.get(&chunk.location) {
5379                    for annot in annotations {
5380                        let drift = if annot.note.is_empty() {
5381                            HIGHLIGHT_DRIFT
5382                        } else {
5383                            ANNOTATION_DRIFT
5384                        };
5385                        let [start, end] = annot.selection;
5386                        if let Some(text) = self.text.get(&chunk.location) {
5387                            let mut last_rect: Option<Rectangle> = None;
5388                            for word in text
5389                                .iter()
5390                                .filter(|w| w.location >= start && w.location <= end)
5391                            {
5392                                let rect = (word.rect * scale).to_rect() - chunk.frame.min
5393                                    + chunk.position;
5394                                if let Some(ref sel_rect) = rect.intersection(&region_rect) {
5395                                    fb.shift_region(sel_rect, drift);
5396                                }
5397                                if let Some(last) = last_rect {
5398                                    // Are `rect` and `last` on the same line?
5399                                    if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5400                                        > rect.height().min(last.height()) as i32 / 2
5401                                        && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5402                                    {
5403                                        let space = if last.max.x < rect.min.x {
5404                                            rect![
5405                                                last.max.x,
5406                                                (last.min.y + rect.min.y) / 2,
5407                                                rect.min.x,
5408                                                (last.max.y + rect.max.y) / 2
5409                                            ]
5410                                        } else {
5411                                            rect![
5412                                                rect.max.x,
5413                                                (last.min.y + rect.min.y) / 2,
5414                                                last.min.x,
5415                                                (last.max.y + rect.max.y) / 2
5416                                            ]
5417                                        };
5418                                        if let Some(ref sel_rect) = space.intersection(&region_rect)
5419                                        {
5420                                            fb.shift_region(sel_rect, drift);
5421                                        }
5422                                    }
5423                                }
5424                                last_rect = Some(rect);
5425                            }
5426                        }
5427                    }
5428                }
5429
5430                if let Some(sel) = self.selection.as_ref() {
5431                    if let Some(text) = self.text.get(&chunk.location) {
5432                        let mut last_rect: Option<Rectangle> = None;
5433                        for word in text
5434                            .iter()
5435                            .filter(|w| w.location >= sel.start && w.location <= sel.end)
5436                        {
5437                            let rect =
5438                                (word.rect * scale).to_rect() - chunk.frame.min + chunk.position;
5439                            if let Some(ref sel_rect) = rect.intersection(&region_rect) {
5440                                fb.invert_region(sel_rect);
5441                            }
5442                            if let Some(last) = last_rect {
5443                                if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5444                                    > rect.height().min(last.height()) as i32 / 2
5445                                    && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5446                                {
5447                                    let space = if last.max.x < rect.min.x {
5448                                        rect![
5449                                            last.max.x,
5450                                            (last.min.y + rect.min.y) / 2,
5451                                            rect.min.x,
5452                                            (last.max.y + rect.max.y) / 2
5453                                        ]
5454                                    } else {
5455                                        rect![
5456                                            rect.max.x,
5457                                            (last.min.y + rect.min.y) / 2,
5458                                            last.min.x,
5459                                            (last.max.y + rect.max.y) / 2
5460                                        ]
5461                                    };
5462                                    if let Some(ref sel_rect) = space.intersection(&region_rect) {
5463                                        fb.invert_region(sel_rect);
5464                                    }
5465                                }
5466                            }
5467                            last_rect = Some(rect);
5468                        }
5469                    }
5470                }
5471            }
5472        }
5473
5474        if self
5475            .info
5476            .reader
5477            .as_ref()
5478            .map_or(false, |r| r.bookmarks.contains(&self.current_page))
5479        {
5480            let dpi = CURRENT_DEVICE.dpi;
5481            let thickness = scale_by_dpi(3.0, dpi) as u16;
5482            let radius = mm_to_px(0.4, dpi) as i32 + thickness as i32;
5483            let center = pt!(self.rect.max.x - 5 * radius, self.rect.min.y + 5 * radius);
5484            fb.draw_rounded_rectangle_with_border(
5485                &Rectangle::from_disk(center, radius),
5486                &CornerSpec::Uniform(radius),
5487                &BorderSpec {
5488                    thickness,
5489                    color: WHITE,
5490                },
5491                &BLACK,
5492            );
5493        }
5494    }
5495
5496    fn render_rect(&self, rect: &Rectangle) -> Rectangle {
5497        rect.intersection(&self.rect).unwrap_or(self.rect)
5498    }
5499
5500    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
5501        if !self.children.is_empty() {
5502            let dpi = CURRENT_DEVICE.dpi;
5503            let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
5504            let (small_thickness, big_thickness) = halves(thickness);
5505            let (small_height, big_height) = (
5506                scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
5507                scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
5508            );
5509            let mut floating_layer_start = 0;
5510
5511            self.children.retain(|child| !child.is::<Menu>());
5512
5513            if self.children[0].is::<TopBar>() {
5514                let top_bar_rect = rect![
5515                    rect.min.x,
5516                    rect.min.y,
5517                    rect.max.x,
5518                    small_height - small_thickness
5519                ];
5520                self.children[0].resize(top_bar_rect, hub, rq, context);
5521                let separator_rect = rect![
5522                    rect.min.x,
5523                    small_height - small_thickness,
5524                    rect.max.x,
5525                    small_height + big_thickness
5526                ];
5527                self.children[1].resize(separator_rect, hub, rq, context);
5528            } else if self.children[0].is::<Filler>() {
5529                let mut index = 1;
5530                if self.children[index].is::<SearchBar>() {
5531                    let sb_rect = rect![
5532                        rect.min.x,
5533                        rect.max.y - (3 * big_height + 2 * small_height) as i32 + big_thickness,
5534                        rect.max.x,
5535                        rect.max.y - (3 * big_height + small_height) as i32 - small_thickness
5536                    ];
5537                    self.children[index].resize(sb_rect, hub, rq, context);
5538                    self.children[index - 1].resize(
5539                        rect![
5540                            rect.min.x,
5541                            sb_rect.min.y - thickness,
5542                            rect.max.x,
5543                            sb_rect.min.y
5544                        ],
5545                        hub,
5546                        rq,
5547                        context,
5548                    );
5549                    index += 2;
5550                }
5551                if self.children[index].is::<Keyboard>() {
5552                    let kb_rect = rect![
5553                        rect.min.x,
5554                        rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
5555                        rect.max.x,
5556                        rect.max.y - small_height - small_thickness
5557                    ];
5558                    self.children[index].resize(kb_rect, hub, rq, context);
5559                    self.children[index + 1].resize(
5560                        rect![
5561                            rect.min.x,
5562                            kb_rect.max.y,
5563                            rect.max.x,
5564                            kb_rect.max.y + thickness
5565                        ],
5566                        hub,
5567                        rq,
5568                        context,
5569                    );
5570                    let kb_rect = *self.children[index].rect();
5571                    self.children[index - 1].resize(
5572                        rect![
5573                            rect.min.x,
5574                            kb_rect.min.y - thickness,
5575                            rect.max.x,
5576                            kb_rect.min.y
5577                        ],
5578                        hub,
5579                        rq,
5580                        context,
5581                    );
5582                    index += 2;
5583                }
5584                floating_layer_start = index;
5585            }
5586
5587            if let Some(mut index) = locate::<BottomBar>(self) {
5588                floating_layer_start = index + 1;
5589                let separator_rect = rect![
5590                    rect.min.x,
5591                    rect.max.y - small_height - small_thickness,
5592                    rect.max.x,
5593                    rect.max.y - small_height + big_thickness
5594                ];
5595                self.children[index - 1].resize(separator_rect, hub, rq, context);
5596                let bottom_bar_rect = rect![
5597                    rect.min.x,
5598                    rect.max.y - small_height + big_thickness,
5599                    rect.max.x,
5600                    rect.max.y
5601                ];
5602                self.children[index].resize(bottom_bar_rect, hub, rq, context);
5603
5604                index -= 2;
5605
5606                while index > 2 {
5607                    let bar_height = if self.children[index].is::<ToolBar>() {
5608                        2 * big_height
5609                    } else if self.children[index].is::<Keyboard>() {
5610                        3 * big_height
5611                    } else {
5612                        small_height
5613                    } as i32;
5614
5615                    let y_max = self.children[index + 1].rect().min.y;
5616                    let bar_rect = rect![
5617                        rect.min.x,
5618                        y_max - bar_height + thickness,
5619                        rect.max.x,
5620                        y_max
5621                    ];
5622                    self.children[index].resize(bar_rect, hub, rq, context);
5623                    let y_max = self.children[index].rect().min.y;
5624                    let sp_rect = rect![rect.min.x, y_max - thickness, rect.max.x, y_max];
5625                    self.children[index - 1].resize(sp_rect, hub, rq, context);
5626
5627                    index -= 2;
5628                }
5629            }
5630
5631            for i in floating_layer_start..self.children.len() {
5632                self.children[i].resize(rect, hub, rq, context);
5633            }
5634        }
5635
5636        match self.view_port.zoom_mode {
5637            ZoomMode::FitToWidth => {
5638                // Apply the scale change.
5639                let ratio = (rect.width() as i32 - 2 * self.view_port.margin_width) as f32
5640                    / (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32;
5641                self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32;
5642            }
5643            ZoomMode::Custom(_) => {
5644                // Keep the center still.
5645                self.view_port.page_offset += pt!(
5646                    self.rect.width() as i32 - rect.width() as i32,
5647                    self.rect.height() as i32 - rect.height() as i32
5648                ) / 2;
5649            }
5650            _ => (),
5651        }
5652
5653        self.rect = rect;
5654
5655        if self.reflowable {
5656            let font_size = self
5657                .info
5658                .reader
5659                .as_ref()
5660                .and_then(|r| r.font_size)
5661                .unwrap_or(context.settings.reader.font_size);
5662            let mut doc = self.doc.lock().unwrap();
5663            doc.layout(rect.width(), rect.height(), font_size, CURRENT_DEVICE.dpi);
5664            let current_page = self.current_page.min(doc.pages_count() - 1);
5665            if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
5666                self.current_page = location;
5667            }
5668            self.text.clear();
5669        }
5670
5671        self.cache.clear();
5672        self.update(Some(UpdateMode::Full), hub, rq, context);
5673    }
5674
5675    fn might_rotate(&self) -> bool {
5676        self.search.is_none()
5677    }
5678
5679    fn is_background(&self) -> bool {
5680        true
5681    }
5682
5683    fn rect(&self) -> &Rectangle {
5684        &self.rect
5685    }
5686
5687    fn rect_mut(&mut self) -> &mut Rectangle {
5688        &mut self.rect
5689    }
5690
5691    fn children(&self) -> &Vec<Box<dyn View>> {
5692        &self.children
5693    }
5694
5695    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
5696        &mut self.children
5697    }
5698
5699    fn id(&self) -> Id {
5700        self.id
5701    }
5702}