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