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