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::{BUTTON_DIAMETER, MarginCropper};
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 BYTES_PER_PAGE, BoundedText, Document, Location, Neighbors, TextLocation, open,
20};
21use crate::document::{
22 SimpleTocEntry, TocEntry, TocLocation, annotations_as_html, bookmarks_as_html, toc_as_html,
23};
24use crate::font::Fonts;
25use crate::font::family_names;
26use crate::framebuffer::{Framebuffer, Pixmap, UpdateMode};
27use crate::frontlight::LightLevels;
28use crate::geom::{Axis, CycleDir, DiagDir, Dir, LinearDir, Region, halves};
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::{
34 Annotation, FileInfo, Info, PageScheme, ReaderInfo, ScrollMode, TextAlign, ZoomMode,
35};
36use crate::metadata::{CroppingMargins, Margin, make_query};
37use crate::metadata::{DEFAULT_CONTRAST_EXPONENT, DEFAULT_CONTRAST_GRAY};
38use crate::settings::{
39 BottomRightGestureAction, EastStripAction, FinishedAction, SouthEastCornerAction,
40 SouthStripAction, WestStripAction, guess_frontlight,
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::{BIG_BAR_HEIGHT, SMALL_BAR_HEIGHT, THICKNESS_MEDIUM};
58use crate::view::{EntryId, EntryKind, ID_FEEDER, Id, SliderId, ViewId};
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 if let Err(error) = context.frontlight.set_intensity(intensity)
4036 {
4037 tracing::error!(error = %error, "failed to set frontlight intensity");
4038 }
4039 if let Err(error) = context.frontlight.set_warmth(warmth) {
4040 tracing::error!(error = %error, "failed to set frontlight warmth");
4041 }
4042 }
4043 }
4044 } else {
4045 hub.send(Event::ToggleFrontlight).ok();
4046 }
4047 }
4048 };
4049 true
4050 }
4051 Event::Gesture(GestureEvent::MultiCorner { dir, .. }) => {
4052 match dir {
4053 DiagDir::NorthWest => {
4054 self.go_to_annotation(CycleDir::Previous, hub, rq, context)
4055 }
4056 DiagDir::NorthEast => self.go_to_annotation(CycleDir::Next, hub, rq, context),
4057 _ => (),
4058 }
4059 true
4060 }
4061 Event::Gesture(GestureEvent::Cross(_)) => {
4062 self.quit(context);
4063 hub.send(Event::Back).ok();
4064 true
4065 }
4066 Event::Gesture(GestureEvent::Diamond(_)) => {
4067 self.toggle_bars(None, hub, rq, context);
4068 true
4069 }
4070 Event::Gesture(GestureEvent::HoldButtonShort(code, ..)) => {
4071 match code {
4072 ButtonCode::Backward => {
4073 self.go_to_chapter(CycleDir::Previous, hub, rq, context)
4074 }
4075 ButtonCode::Forward => self.go_to_chapter(CycleDir::Next, hub, rq, context),
4076 _ => (),
4077 }
4078 self.held_buttons.insert(code);
4079 true
4080 }
4081 Event::Device(DeviceEvent::Button {
4082 code,
4083 status: ButtonStatus::Released,
4084 ..
4085 }) => {
4086 if !self.held_buttons.remove(&code) {
4087 match code {
4088 ButtonCode::Backward => {
4089 if self.search.is_none() {
4090 self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4091 } else {
4092 self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4093 }
4094 }
4095 ButtonCode::Forward => {
4096 if self.search.is_none() {
4097 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4098 } else {
4099 self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4100 }
4101 }
4102 _ => (),
4103 }
4104 }
4105 true
4106 }
4107 Event::Device(DeviceEvent::Finger {
4108 position,
4109 status: FingerStatus::Motion,
4110 id,
4111 ..
4112 }) if self.state == State::Selection(id) => {
4113 let mut nearest_word = None;
4114 let mut dmin = u32::MAX;
4115 let dmax =
4116 (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4117 let mut rects = Vec::new();
4118
4119 for chunk in &self.chunks {
4120 for word in &self.text[&chunk.location] {
4121 let rect =
4122 (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4123 rects.push((rect, word.location));
4124 let d = position.rdist2(&rect);
4125 if d < dmax && d < dmin {
4126 dmin = d;
4127 nearest_word = Some(word.clone());
4128 }
4129 }
4130 }
4131
4132 let selection = self.selection.as_mut().unwrap();
4133
4134 if let Some(word) = nearest_word {
4135 let old_start = selection.start;
4136 let old_end = selection.end;
4137 let (start, end) = word.location.min_max(selection.anchor);
4138
4139 if start == old_start && end == old_end {
4140 return true;
4141 }
4142
4143 let (start_low, start_high) = old_start.min_max(start);
4144 let (end_low, end_high) = old_end.min_max(end);
4145
4146 if start_low != start_high {
4147 if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4148 let mut rect = rects[i].0;
4149 while rects[i].1 < start_high {
4150 let next_rect = rects[i + 1].0;
4151 if rect.max.y.min(next_rect.max.y) - rect.min.y.max(next_rect.min.y)
4152 > rect.height().min(next_rect.height()) as i32 / 2
4153 {
4154 if rects[i + 1].1 == start_high {
4155 if rect.min.x < next_rect.min.x {
4156 rect.max.x = next_rect.min.x;
4157 } else {
4158 rect.min.x = next_rect.max.x;
4159 }
4160 rect.min.y = rect.min.y.min(next_rect.min.y);
4161 rect.max.y = rect.max.y.max(next_rect.max.y);
4162 } else {
4163 rect.absorb(&next_rect);
4164 }
4165 } else {
4166 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4167 rect = next_rect;
4168 }
4169 i += 1;
4170 }
4171 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4172 }
4173 }
4174
4175 if end_low != end_high {
4176 if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4177 let mut rect = rects[i].0;
4178 while rects[i].1 > end_low {
4179 let prev_rect = rects[i - 1].0;
4180 if rect.max.y.min(prev_rect.max.y) - rect.min.y.max(prev_rect.min.y)
4181 > rect.height().min(prev_rect.height()) as i32 / 2
4182 {
4183 if rects[i - 1].1 == end_low {
4184 if rect.min.x > prev_rect.min.x {
4185 rect.min.x = prev_rect.max.x;
4186 } else {
4187 rect.max.x = prev_rect.min.x;
4188 }
4189 rect.min.y = rect.min.y.min(prev_rect.min.y);
4190 rect.max.y = rect.max.y.max(prev_rect.max.y);
4191 } else {
4192 rect.absorb(&prev_rect);
4193 }
4194 } else {
4195 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4196 rect = prev_rect;
4197 }
4198 i -= 1;
4199 }
4200 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4201 }
4202 }
4203
4204 selection.start = start;
4205 selection.end = end;
4206 }
4207 true
4208 }
4209 Event::Device(DeviceEvent::Finger {
4210 status: FingerStatus::Up,
4211 position,
4212 id,
4213 ..
4214 }) if self.state == State::Selection(id) => {
4215 self.state = State::Idle;
4216 let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4217 self.toggle_selection_menu(
4218 Rectangle::from_disk(position, radius),
4219 Some(true),
4220 rq,
4221 context,
4222 );
4223 true
4224 }
4225 Event::Gesture(GestureEvent::Tap(center))
4226 if self.state == State::AdjustSelection && self.rect.includes(center) =>
4227 {
4228 let mut found = None;
4229 let mut dmin = u32::MAX;
4230 let dmax =
4231 (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4232 let mut rects = Vec::new();
4233
4234 for chunk in &self.chunks {
4235 for word in &self.text[&chunk.location] {
4236 let rect =
4237 (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4238 rects.push((rect, word.location));
4239 let d = center.rdist2(&rect);
4240 if d < dmax && d < dmin {
4241 dmin = d;
4242 found = Some((word.clone(), rects.len() - 1));
4243 }
4244 }
4245 }
4246
4247 let selection = self.selection.as_mut().unwrap();
4248
4249 if let Some((word, index)) = found {
4250 let old_start = selection.start;
4251 let old_end = selection.end;
4252
4253 let (start, end) = if word.location <= old_start {
4254 (word.location, old_end)
4255 } else if word.location >= old_end {
4256 (old_start, word.location)
4257 } else {
4258 let (start_index, end_index) = (
4259 rects.iter().position(|(_, loc)| *loc == old_start),
4260 rects.iter().position(|(_, loc)| *loc == old_end),
4261 );
4262 match (start_index, end_index) {
4263 (Some(s), Some(e)) => {
4264 if index - s > e - index {
4265 (old_start, word.location)
4266 } else {
4267 (word.location, old_end)
4268 }
4269 }
4270 (Some(..), None) => (word.location, old_end),
4271 (None, Some(..)) => (old_start, word.location),
4272 (None, None) => (old_start, old_end),
4273 }
4274 };
4275
4276 if start == old_start && end == old_end {
4277 return true;
4278 }
4279
4280 let (start_low, start_high) = old_start.min_max(start);
4281 let (end_low, end_high) = old_end.min_max(end);
4282
4283 if start_low != start_high {
4284 if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) {
4285 let mut rect = rects[i].0;
4286 while i < rects.len() - 1 && rects[i].1 < start_high {
4287 let next_rect = rects[i + 1].0;
4288 if rect.min.y < next_rect.max.y && next_rect.min.y < rect.max.y {
4289 if rects[i + 1].1 == start_high {
4290 if rect.min.x < next_rect.min.x {
4291 rect.max.x = next_rect.min.x;
4292 } else {
4293 rect.min.x = next_rect.max.x;
4294 }
4295 rect.min.y = rect.min.y.min(next_rect.min.y);
4296 rect.max.y = rect.max.y.max(next_rect.max.y);
4297 } else {
4298 rect.absorb(&next_rect);
4299 }
4300 } else {
4301 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4302 rect = next_rect;
4303 }
4304 i += 1;
4305 }
4306 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4307 }
4308 }
4309
4310 if end_low != end_high {
4311 if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) {
4312 let mut rect = rects[i].0;
4313 while i > 0 && rects[i].1 > end_low {
4314 let prev_rect = rects[i - 1].0;
4315 if rect.min.y < prev_rect.max.y && prev_rect.min.y < rect.max.y {
4316 if rects[i - 1].1 == end_low {
4317 if rect.min.x > prev_rect.min.x {
4318 rect.min.x = prev_rect.max.x;
4319 } else {
4320 rect.max.x = prev_rect.min.x;
4321 }
4322 rect.min.y = rect.min.y.min(prev_rect.min.y);
4323 rect.max.y = rect.max.y.max(prev_rect.max.y);
4324 } else {
4325 rect.absorb(&prev_rect);
4326 }
4327 } else {
4328 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4329 rect = prev_rect;
4330 }
4331 i -= 1;
4332 }
4333 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4334 }
4335 }
4336
4337 selection.start = start;
4338 selection.end = end;
4339 }
4340 true
4341 }
4342 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
4343 if self.focus.is_some() {
4344 return true;
4345 }
4346
4347 let mut nearest_link = None;
4348 let mut dmin = u32::MAX;
4349 let dmax =
4350 (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4351
4352 for chunk in &self.chunks {
4353 let (links, _) = self
4354 .doc
4355 .lock()
4356 .ok()
4357 .and_then(|mut doc| doc.links(Location::Exact(chunk.location)))
4358 .unwrap_or((Vec::new(), 0));
4359 for link in links {
4360 let rect =
4361 (link.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4362 let d = center.rdist2(&rect);
4363 if d < dmax && d < dmin {
4364 dmin = d;
4365 nearest_link = Some(link.clone());
4366 }
4367 }
4368 }
4369
4370 if let Some(link) = nearest_link.take() {
4371 let pdf_page = Regex::new(r"^#page=(\d+).*$").unwrap();
4372 let djvu_page = Regex::new(r"^#([+-])?(\d+)$").unwrap();
4373 let toc_page = Regex::new(r"^@(.+)$").unwrap();
4374 if let Some(caps) = toc_page.captures(&link.text) {
4375 let loc_opt = if caps[1].chars().all(|c| c.is_digit(10)) {
4376 caps[1].parse::<usize>().map(Location::Exact).ok()
4377 } else {
4378 Some(Location::Uri(caps[1].to_string()))
4379 };
4380 if let Some(location) = loc_opt {
4381 self.quit(context);
4382 hub.send(Event::Back).ok();
4383 hub.send(Event::GoToLocation(location)).ok();
4384 }
4385 } else if let Some(caps) = pdf_page.captures(&link.text) {
4386 if let Ok(index) = caps[1].parse::<usize>() {
4387 self.go_to_page(index.saturating_sub(1), true, hub, rq, context);
4388 }
4389 } else if let Some(caps) = djvu_page.captures(&link.text) {
4390 if let Ok(mut index) = caps[2].parse::<usize>() {
4391 let prefix = caps.get(1).map(|m| m.as_str());
4392 match prefix {
4393 Some("-") => index = self.current_page.saturating_sub(index),
4394 Some("+") => index += self.current_page,
4395 _ => index = index.saturating_sub(1),
4396 }
4397 self.go_to_page(index, true, hub, rq, context);
4398 }
4399 } else {
4400 let mut doc = self.doc.lock().unwrap();
4401 let loc = Location::LocalUri(self.current_page, link.text.clone());
4402 if let Some(location) = doc.resolve_location(loc) {
4403 hub.send(Event::GoTo(location)).ok();
4404 } else if link.text.starts_with("https:") || link.text.starts_with("http:")
4405 {
4406 if let Some(path) = context.settings.external_urls_queue.as_ref() {
4407 let path = CURRENT_DEVICE.install_path(path);
4408 if let Ok(mut file) =
4409 OpenOptions::new().create(true).append(true).open(&path)
4410 {
4411 if let Err(e) = writeln!(file, "{}", link.text) {
4412 error!("Couldn't write to {}: {:#}.", path.display(), e);
4413 } else {
4414 let message = format!("Queued {}.", link.text);
4415 let notif = Notification::new(
4416 None, message, false, hub, rq, context,
4417 );
4418 self.children.push(Box::new(notif) as Box<dyn View>);
4419 }
4420 }
4421 }
4422 } else {
4423 error!("Can't resolve URI: {}.", link.text);
4424 }
4425 }
4426 return true;
4427 }
4428
4429 if let ZoomMode::Custom(_) = self.view_port.zoom_mode {
4430 let dx = self.rect.width() as i32 - 2 * self.view_port.margin_width;
4431 let dy = self.rect.height() as i32 - 2 * self.view_port.margin_width;
4432 match Region::from_point(
4433 center,
4434 self.rect,
4435 context.settings.reader.strip_width,
4436 context.settings.reader.corner_width,
4437 ) {
4438 Region::Corner(diag_dir) => match diag_dir {
4439 DiagDir::NorthEast => {
4440 self.directional_scroll(pt!(dx, -dy), hub, rq, context)
4441 }
4442 DiagDir::SouthEast => {
4443 self.directional_scroll(pt!(dx, dy), hub, rq, context)
4444 }
4445 DiagDir::SouthWest => {
4446 self.directional_scroll(pt!(-dx, dy), hub, rq, context)
4447 }
4448 DiagDir::NorthWest => {
4449 self.directional_scroll(pt!(-dx, -dy), hub, rq, context)
4450 }
4451 },
4452 Region::Strip(dir) => match dir {
4453 Dir::North => self.directional_scroll(pt!(0, -dy), hub, rq, context),
4454 Dir::East => self.directional_scroll(pt!(dx, 0), hub, rq, context),
4455 Dir::South => self.directional_scroll(pt!(0, dy), hub, rq, context),
4456 Dir::West => self.directional_scroll(pt!(-dx, 0), hub, rq, context),
4457 },
4458 Region::Center => self.toggle_bars(None, hub, rq, context),
4459 }
4460
4461 return true;
4462 }
4463
4464 match Region::from_point(
4465 center,
4466 self.rect,
4467 context.settings.reader.strip_width,
4468 context.settings.reader.corner_width,
4469 ) {
4470 Region::Corner(diag_dir) => match diag_dir {
4471 DiagDir::NorthWest => self.go_to_last_page(hub, rq, context),
4472 DiagDir::NorthEast => self.toggle_bookmark(rq),
4473 DiagDir::SouthEast => {
4474 if self.search.is_none() {
4475 match context.settings.reader.south_east_corner {
4476 SouthEastCornerAction::GoToPage => {
4477 hub.send(Event::Toggle(ToggleEvent::View(
4478 ViewId::GoToPage,
4479 )))
4480 .ok();
4481 }
4482 SouthEastCornerAction::NextPage => {
4483 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4484 }
4485 }
4486 } else {
4487 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4488 }
4489 }
4490 DiagDir::SouthWest => {
4491 if self.search.is_none() {
4492 if self.ephemeral
4493 && self.info.file.path == PathBuf::from(MEM_SCHEME)
4494 {
4495 self.quit(context);
4496 hub.send(Event::Back).ok();
4497 } else {
4498 hub.send(Event::Show(ViewId::TableOfContents)).ok();
4499 }
4500 } else {
4501 self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4502 }
4503 }
4504 },
4505 Region::Strip(dir) => match dir {
4506 Dir::West => {
4507 if self.search.is_none() {
4508 match context.settings.reader.west_strip {
4509 WestStripAction::PreviousPage => {
4510 self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4511 }
4512 WestStripAction::NextPage => {
4513 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4514 }
4515 WestStripAction::None => (),
4516 }
4517 } else {
4518 self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context);
4519 }
4520 }
4521 Dir::East => {
4522 if self.search.is_none() {
4523 match context.settings.reader.east_strip {
4524 EastStripAction::PreviousPage => {
4525 self.go_to_neighbor(CycleDir::Previous, hub, rq, context);
4526 }
4527 EastStripAction::NextPage => {
4528 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4529 }
4530 EastStripAction::None => (),
4531 }
4532 } else {
4533 self.go_to_results_neighbor(CycleDir::Next, hub, rq, context);
4534 }
4535 }
4536 Dir::South => match context.settings.reader.south_strip {
4537 SouthStripAction::ToggleBars => {
4538 self.toggle_bars(None, hub, rq, context);
4539 }
4540 SouthStripAction::NextPage => {
4541 self.go_to_neighbor(CycleDir::Next, hub, rq, context);
4542 }
4543 },
4544 Dir::North => self.toggle_bars(None, hub, rq, context),
4545 },
4546 Region::Center => self.toggle_bars(None, hub, rq, context),
4547 }
4548
4549 true
4550 }
4551 Event::Gesture(GestureEvent::HoldFingerShort(center, id))
4552 if self.rect.includes(center) =>
4553 {
4554 if self.focus.is_some() {
4555 return true;
4556 }
4557
4558 let mut found = None;
4559 let mut dmin = u32::MAX;
4560 let dmax =
4561 (scale_by_dpi(RECT_DIST_JITTER, CURRENT_DEVICE.dpi) as i32).pow(2) as u32;
4562
4563 if let Some(rect) = self.selection_rect() {
4564 let d = center.rdist2(&rect);
4565 if d < dmax {
4566 self.state = State::Idle;
4567 let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4568 self.toggle_selection_menu(
4569 Rectangle::from_disk(center, radius),
4570 Some(true),
4571 rq,
4572 context,
4573 );
4574 }
4575 return true;
4576 }
4577
4578 for chunk in &self.chunks {
4579 for word in &self.text[&chunk.location] {
4580 let rect =
4581 (word.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position;
4582 let d = center.rdist2(&rect);
4583 if d < dmax && d < dmin {
4584 dmin = d;
4585 found = Some((word.clone(), rect));
4586 }
4587 }
4588 }
4589
4590 if let Some((nearest_word, rect)) = found {
4591 let anchor = nearest_word.location;
4592 if let Some(annot) = self
4593 .annotations
4594 .values()
4595 .flatten()
4596 .find(|annot| anchor >= annot.selection[0] && anchor <= annot.selection[1])
4597 .cloned()
4598 {
4599 let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32;
4600 self.toggle_annotation_menu(
4601 &annot,
4602 Rectangle::from_disk(center, radius),
4603 Some(true),
4604 rq,
4605 context,
4606 );
4607 } else {
4608 self.selection = Some(Selection {
4609 start: anchor,
4610 end: anchor,
4611 anchor,
4612 });
4613 self.state = State::Selection(id);
4614 rq.add(RenderData::new(self.id, rect, UpdateMode::Fast));
4615 }
4616 }
4617
4618 true
4619 }
4620 Event::Gesture(GestureEvent::HoldFingerLong(center, _))
4621 if self.rect.includes(center) =>
4622 {
4623 if let Some(text) = self.selected_text() {
4624 let query = text
4625 .trim_matches(|c: char| !c.is_alphanumeric())
4626 .to_string();
4627 let language = self.info.language.clone();
4628 hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
4629 query,
4630 language,
4631 })))
4632 .ok();
4633 }
4634 self.selection = None;
4635 self.state = State::Idle;
4636 true
4637 }
4638 Event::Update(mode) => {
4639 self.update(Some(mode), hub, rq, context);
4640 true
4641 }
4642 Event::LoadPixmap(location) => {
4643 self.load_pixmap(location);
4644 true
4645 }
4646 Event::Submit(ViewId::GoToPageInput, ref text) => {
4647 let re = Regex::new(r#"^([-+'])?(.+)$"#).unwrap();
4648 if let Some(caps) = re.captures(text) {
4649 let prefix = caps.get(1).map(|m| m.as_str());
4650 if prefix == Some("'") {
4651 if let Some(location) = self.find_page_by_name(&caps[2]) {
4652 self.go_to_page(location, true, hub, rq, context);
4653 }
4654 } else {
4655 if text == "_" {
4656 let location =
4657 (context.rng.next_u64() % self.pages_count as u64) as usize;
4658 self.go_to_page(location, true, hub, rq, context);
4659 } else if text == "(" {
4660 self.go_to_page(0, true, hub, rq, context);
4661 } else if text == ")" {
4662 self.go_to_page(
4663 self.pages_count.saturating_sub(1),
4664 true,
4665 hub,
4666 rq,
4667 context,
4668 );
4669 } else if let Some(percent) = text.strip_suffix('%') {
4670 if let Ok(number) = percent.parse::<f64>() {
4671 let location =
4672 (number.max(0.0).min(100.0) / 100.0 * self.pages_count as f64)
4673 .round() as usize;
4674 self.go_to_page(location, true, hub, rq, context);
4675 }
4676 } else if let Ok(number) = caps[2].parse::<f64>() {
4677 let location = {
4678 let bpp = if self.synthetic { BYTES_PER_PAGE } else { 1.0 };
4679 let mut index = (number * bpp).max(0.0).round() as usize;
4680 match prefix {
4681 Some("-") => index = self.current_page.saturating_sub(index),
4682 Some("+") => index += self.current_page,
4683 _ => index = index.saturating_sub(1 / (bpp as usize)),
4684 }
4685 index
4686 };
4687 self.go_to_page(location, true, hub, rq, context);
4688 }
4689 }
4690 }
4691 true
4692 }
4693 Event::Submit(ViewId::GoToResultsPageInput, ref text) => {
4694 if let Ok(index) = text.parse::<usize>() {
4695 self.go_to_results_page(index.saturating_sub(1), hub, rq, context);
4696 }
4697 true
4698 }
4699 Event::Submit(ViewId::NamePageInput, ref text) => {
4700 if !text.is_empty() {
4701 if let Some(ref mut r) = self.info.reader {
4702 r.page_names.insert(self.current_page, text.to_string());
4703 }
4704 }
4705 self.toggle_keyboard(false, None, hub, rq, context);
4706 true
4707 }
4708 Event::Submit(ViewId::EditNoteInput, ref note) => {
4709 let selection = self.selection.take().map(|sel| [sel.start, sel.end]);
4710
4711 if let Some(sel) = selection {
4712 let text = self.text_excerpt(sel).unwrap();
4713 if let Some(r) = self.info.reader.as_mut() {
4714 r.annotations.push(Annotation {
4715 selection: sel,
4716 note: note.to_string(),
4717 text,
4718 modified: Local::now().naive_local(),
4719 });
4720 }
4721 if let Some(rect) = self.text_rect(sel) {
4722 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4723 }
4724 } else {
4725 if let Some(sel) = self.target_annotation.take() {
4726 if let Some(annot) = self.find_annotation_mut(sel) {
4727 annot.note = note.to_string();
4728 annot.modified = Local::now().naive_local();
4729 }
4730 if let Some(rect) = self.text_rect(sel) {
4731 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4732 }
4733 }
4734 }
4735
4736 self.update_annotations();
4737 self.toggle_keyboard(false, None, hub, rq, context);
4738 true
4739 }
4740 Event::Submit(ViewId::ReaderSearchInput, ref text) => {
4741 match make_query(text) {
4742 Some(query) => {
4743 self.search(text, query, hub, rq);
4744 self.toggle_keyboard(false, None, hub, rq, context);
4745 self.toggle_results_bar(true, rq, context);
4746 }
4747 None => {
4748 let notif = Notification::new(
4749 None,
4750 "Invalid search query.".to_string(),
4751 false,
4752 hub,
4753 rq,
4754 context,
4755 );
4756 self.children.push(Box::new(notif) as Box<dyn View>);
4757 }
4758 }
4759 true
4760 }
4761 Event::Page(dir) => {
4762 self.go_to_neighbor(dir, hub, rq, context);
4763 true
4764 }
4765 Event::GoTo(location) | Event::Select(EntryId::GoTo(location)) => {
4766 self.go_to_page(location, true, hub, rq, context);
4767 true
4768 }
4769 Event::GoToLocation(ref location) => {
4770 let offset_opt = {
4771 let mut doc = self.doc.lock().unwrap();
4772 doc.resolve_location(location.clone())
4773 };
4774 if let Some(offset) = offset_opt {
4775 self.go_to_page(offset, true, hub, rq, context);
4776 }
4777 true
4778 }
4779 Event::Chapter(dir) => {
4780 self.go_to_chapter(dir, hub, rq, context);
4781 true
4782 }
4783 Event::ResultsPage(dir) => {
4784 self.go_to_results_neighbor(dir, hub, rq, context);
4785 true
4786 }
4787 Event::CropMargins(ref margin) => {
4788 let current_page = self.current_page;
4789 self.crop_margins(current_page, margin.as_ref(), hub, rq, context);
4790 true
4791 }
4792 Event::Toggle(ToggleEvent::View(ViewId::TopBottomBars)) => {
4793 self.toggle_bars(None, hub, rq, context);
4794 true
4795 }
4796 Event::Toggle(ToggleEvent::View(ViewId::GoToPage)) => {
4797 self.toggle_go_to_page(None, ViewId::GoToPage, hub, rq, context);
4798 true
4799 }
4800 Event::Toggle(ToggleEvent::View(ViewId::GoToResultsPage)) => {
4801 self.toggle_go_to_page(None, ViewId::GoToResultsPage, hub, rq, context);
4802 true
4803 }
4804 Event::Slider(SliderId::FontSize, font_size, FingerStatus::Up) => {
4805 self.set_font_size(font_size, hub, rq, context);
4806 true
4807 }
4808 Event::Slider(SliderId::ContrastExponent, exponent, FingerStatus::Up) => {
4809 self.set_contrast_exponent(exponent, hub, rq, context);
4810 true
4811 }
4812 Event::Slider(SliderId::ContrastGray, gray, FingerStatus::Up) => {
4813 self.set_contrast_gray(gray, hub, rq, context);
4814 true
4815 }
4816 Event::ToggleNear(ViewId::TitleMenu, rect) => {
4817 self.toggle_title_menu(rect, None, rq, context);
4818 true
4819 }
4820 Event::ToggleNear(ViewId::MainMenu, rect) => {
4821 toggle_main_menu(self, rect, None, rq, context);
4822 true
4823 }
4824 Event::ToggleNear(ViewId::BatteryMenu, rect) => {
4825 toggle_battery_menu(self, rect, None, rq, context);
4826 true
4827 }
4828 Event::ToggleNear(ViewId::ClockMenu, rect) => {
4829 toggle_clock_menu(self, rect, None, rq, context);
4830 true
4831 }
4832 Event::ToggleNear(ViewId::MarginCropperMenu, rect) => {
4833 self.toggle_margin_cropper_menu(rect, None, rq, context);
4834 true
4835 }
4836 Event::ToggleNear(ViewId::SearchMenu, rect) => {
4837 self.toggle_search_menu(rect, None, rq, context);
4838 true
4839 }
4840 Event::ToggleNear(ViewId::FontFamilyMenu, rect) => {
4841 self.toggle_font_family_menu(rect, None, rq, context);
4842 true
4843 }
4844 Event::ToggleNear(ViewId::FontSizeMenu, rect) => {
4845 self.toggle_font_size_menu(rect, None, rq, context);
4846 true
4847 }
4848 Event::ToggleNear(ViewId::TextAlignMenu, rect) => {
4849 self.toggle_text_align_menu(rect, None, rq, context);
4850 true
4851 }
4852 Event::ToggleNear(ViewId::MarginWidthMenu, rect) => {
4853 self.toggle_margin_width_menu(rect, None, rq, context);
4854 true
4855 }
4856 Event::ToggleNear(ViewId::LineHeightMenu, rect) => {
4857 self.toggle_line_height_menu(rect, None, rq, context);
4858 true
4859 }
4860 Event::ToggleNear(ViewId::ContrastExponentMenu, rect) => {
4861 self.toggle_contrast_exponent_menu(rect, None, rq, context);
4862 true
4863 }
4864 Event::ToggleNear(ViewId::ContrastGrayMenu, rect) => {
4865 self.toggle_contrast_gray_menu(rect, None, rq, context);
4866 true
4867 }
4868 Event::ToggleNear(ViewId::PageMenu, rect) => {
4869 self.toggle_page_menu(rect, None, rq, context);
4870 true
4871 }
4872 Event::Close(ViewId::MainMenu) => {
4873 toggle_main_menu(self, Rectangle::default(), Some(false), rq, context);
4874 true
4875 }
4876 Event::Close(ViewId::SearchBar) => {
4877 self.toggle_results_bar(false, rq, context);
4878 self.toggle_search_bar(false, hub, rq, context);
4879 if let Some(ref mut s) = self.search {
4880 s.running.store(false, AtomicOrdering::Relaxed);
4881 self.render_results(rq);
4882 self.search = None;
4883 }
4884 true
4885 }
4886 Event::Close(ViewId::GoToPage) => {
4887 self.toggle_go_to_page(Some(false), ViewId::GoToPage, hub, rq, context);
4888 true
4889 }
4890 Event::Close(ViewId::GoToResultsPage) => {
4891 self.toggle_go_to_page(Some(false), ViewId::GoToResultsPage, hub, rq, context);
4892 true
4893 }
4894 Event::Close(ViewId::SelectionMenu) => {
4895 if self.state == State::Idle && self.target_annotation.is_none() {
4896 if let Some(rect) = self.selection_rect() {
4897 self.selection = None;
4898 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4899 }
4900 }
4901 false
4902 }
4903 Event::Close(ViewId::EditNote) => {
4904 self.toggle_edit_note(None, Some(false), hub, rq, context);
4905 if let Some(rect) = self.selection_rect() {
4906 self.selection = None;
4907 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
4908 }
4909 self.target_annotation = None;
4910 false
4911 }
4912 Event::Close(ViewId::NamePage) => {
4913 self.toggle_keyboard(false, None, hub, rq, context);
4914 false
4915 }
4916 Event::Show(ViewId::TableOfContents) => {
4917 {
4918 self.toggle_bars(Some(false), hub, rq, context);
4919 }
4920 let mut doc = self.doc.lock().unwrap();
4921 if let Some(toc) = self
4922 .toc()
4923 .or_else(|| doc.toc())
4924 .filter(|toc| !toc.is_empty())
4925 {
4926 let chap = doc.chapter(self.current_page, &toc).map(|(c, _)| c);
4927 let chap_index = chap.map_or(usize::MAX, |chap| chap.index);
4928 let html = toc_as_html(&toc, chap_index);
4929 let link_uri = chap.and_then(|chap| match chap.location {
4930 Location::Uri(ref uri) => Some(format!("@{}", uri)),
4931 Location::Exact(offset) => Some(format!("@{}", offset)),
4932 _ => None,
4933 });
4934 hub.send(Event::OpenHtml(html, link_uri)).ok();
4935 }
4936 true
4937 }
4938 Event::Select(EntryId::Annotations) => {
4939 self.toggle_bars(Some(false), hub, rq, context);
4940 let mut starts = self
4941 .annotations
4942 .values()
4943 .flatten()
4944 .map(|annot| annot.selection[0])
4945 .collect::<Vec<TextLocation>>();
4946 starts.sort();
4947 let active_range = starts.first().cloned().zip(starts.last().cloned());
4948 if let Some(mut annotations) =
4949 self.info.reader.as_ref().map(|r| &r.annotations).cloned()
4950 {
4951 annotations.sort_by(|a, b| a.selection[0].cmp(&b.selection[0]));
4952 let html = annotations_as_html(&annotations, active_range);
4953 let link_uri = annotations
4954 .iter()
4955 .filter(|annot| annot.selection[0].location() <= self.current_page)
4956 .max_by_key(|annot| annot.selection[0])
4957 .map(|annot| format!("@{}", annot.selection[0].location()));
4958 hub.send(Event::OpenHtml(html, link_uri)).ok();
4959 }
4960 true
4961 }
4962 Event::Select(EntryId::Bookmarks) => {
4963 self.toggle_bars(Some(false), hub, rq, context);
4964 if let Some(bookmarks) = self.info.reader.as_ref().map(|r| &r.bookmarks) {
4965 let html = bookmarks_as_html(bookmarks, self.current_page, self.synthetic);
4966 let link_uri = bookmarks
4967 .range(..=self.current_page)
4968 .next_back()
4969 .map(|index| format!("@{}", index));
4970 hub.send(Event::OpenHtml(html, link_uri)).ok();
4971 }
4972 true
4973 }
4974 Event::Show(ViewId::SearchBar) => {
4975 self.toggle_search_bar(true, hub, rq, context);
4976 true
4977 }
4978 Event::Show(ViewId::MarginCropper) => {
4979 self.toggle_margin_cropper(true, hub, rq, context);
4980 true
4981 }
4982 Event::Close(ViewId::MarginCropper) => {
4983 self.toggle_margin_cropper(false, hub, rq, context);
4984 true
4985 }
4986 Event::SearchResult(location, ref rects) => {
4987 if self.search.is_none() {
4988 return true;
4989 }
4990
4991 let mut results_count = 0;
4992
4993 if let Some(ref mut s) = self.search {
4994 let pages_count = s.highlights.len();
4995 s.highlights
4996 .entry(location)
4997 .or_insert_with(Vec::new)
4998 .push(rects.clone());
4999 s.results_count += 1;
5000 results_count = s.results_count;
5001 if results_count > 1
5002 && location <= self.current_page
5003 && s.highlights.len() > pages_count
5004 {
5005 s.current_page += 1;
5006 }
5007 }
5008
5009 self.update_results_bar(rq);
5010
5011 if results_count == 1 {
5012 self.toggle_results_bar(false, rq, context);
5013 self.toggle_search_bar(false, hub, rq, context);
5014 self.go_to_page(location, true, hub, rq, context);
5015 } else if location == self.current_page {
5016 self.update(None, hub, rq, context);
5017 }
5018
5019 true
5020 }
5021 Event::EndOfSearch => {
5022 let results_count = self
5023 .search
5024 .as_ref()
5025 .map(|s| s.results_count)
5026 .unwrap_or(usize::MAX);
5027 if results_count == 0 {
5028 let notif = Notification::new(
5029 None,
5030 "No search results.".to_string(),
5031 false,
5032 hub,
5033 rq,
5034 context,
5035 );
5036 self.children.push(Box::new(notif) as Box<dyn View>);
5037 self.toggle_search_bar(true, hub, rq, context);
5038 hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok();
5039 }
5040 true
5041 }
5042 Event::Select(EntryId::AnnotateSelection) => {
5043 self.toggle_edit_note(None, Some(true), hub, rq, context);
5044 true
5045 }
5046 Event::Select(EntryId::HighlightSelection) => {
5047 if let Some(sel) = self.selection.take() {
5048 let text = self.text_excerpt([sel.start, sel.end]).unwrap();
5049 if let Some(r) = self.info.reader.as_mut() {
5050 r.annotations.push(Annotation {
5051 selection: [sel.start, sel.end],
5052 note: String::new(),
5053 text,
5054 modified: Local::now().naive_local(),
5055 });
5056 }
5057 if let Some(rect) = self.text_rect([sel.start, sel.end]) {
5058 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5059 }
5060 self.update_annotations();
5061 }
5062
5063 true
5064 }
5065 Event::Select(EntryId::DefineSelection) => {
5066 if let Some(text) = self.selected_text() {
5067 let query = text
5068 .trim_matches(|c: char| !c.is_alphanumeric())
5069 .to_string();
5070 let language = self.info.language.clone();
5071 hub.send(Event::Select(EntryId::Launch(AppCmd::Dictionary {
5072 query,
5073 language,
5074 })))
5075 .ok();
5076 }
5077 self.selection = None;
5078 true
5079 }
5080 Event::Select(EntryId::SearchForSelection) => {
5081 if let Some(text) = self.selected_text() {
5082 let text = text.trim_matches(|c: char| !c.is_alphanumeric());
5083 match make_query(text) {
5084 Some(query) => {
5085 self.search(text, query, hub, rq);
5086 }
5087 None => {
5088 let notif = Notification::new(
5089 None,
5090 "Invalid search query.".to_string(),
5091 false,
5092 hub,
5093 rq,
5094 context,
5095 );
5096 self.children.push(Box::new(notif) as Box<dyn View>);
5097 }
5098 }
5099 }
5100 if let Some(rect) = self.selection_rect() {
5101 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5102 }
5103 self.selection = None;
5104 true
5105 }
5106 Event::Select(EntryId::GoToSelectedPageName) => {
5107 if let Some(loc) = self.selected_text().and_then(|text| {
5108 let end = text
5109 .find(|c: char| {
5110 !c.is_ascii_digit()
5111 && Digit::from_char(c).is_err()
5112 && !c.is_ascii_uppercase()
5113 })
5114 .unwrap_or_else(|| text.len());
5115 self.find_page_by_name(&text[..end])
5116 }) {
5117 self.go_to_page(loc, true, hub, rq, context);
5118 }
5119 if let Some(rect) = self.selection_rect() {
5120 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5121 }
5122 self.selection = None;
5123 true
5124 }
5125 Event::Select(EntryId::AdjustSelection) => {
5126 self.state = State::AdjustSelection;
5127 true
5128 }
5129 Event::Select(EntryId::EditAnnotationNote(sel)) => {
5130 let text = self
5131 .find_annotation_ref(sel)
5132 .map(|annot| annot.note.clone());
5133 self.toggle_edit_note(text, Some(true), hub, rq, context);
5134 self.target_annotation = Some(sel);
5135 true
5136 }
5137 Event::Select(EntryId::RemoveAnnotationNote(sel)) => {
5138 if let Some(annot) = self.find_annotation_mut(sel) {
5139 annot.note.clear();
5140 annot.modified = Local::now().naive_local();
5141 self.update_annotations();
5142 }
5143 if let Some(rect) = self.text_rect(sel) {
5144 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5145 }
5146 true
5147 }
5148 Event::Select(EntryId::RemoveAnnotation(sel)) => {
5149 if let Some(annotations) = self.info.reader.as_mut().map(|r| &mut r.annotations) {
5150 annotations.retain(|annot| {
5151 annot.selection[0] != sel[0] || annot.selection[1] != sel[1]
5152 });
5153 self.update_annotations();
5154 }
5155 if let Some(rect) = self.text_rect(sel) {
5156 rq.add(RenderData::new(self.id, rect, UpdateMode::Gui));
5157 }
5158 true
5159 }
5160 Event::Select(EntryId::SetZoomMode(zoom_mode)) => {
5161 self.set_zoom_mode(zoom_mode, true, hub, rq, context);
5162 true
5163 }
5164 Event::Select(EntryId::SetScrollMode(scroll_mode)) => {
5165 self.set_scroll_mode(scroll_mode, hub, rq, context);
5166 true
5167 }
5168 Event::Select(EntryId::Save) => {
5169 let name = format!(
5170 "{}-{}.{}",
5171 self.info.title.to_lowercase().replace(' ', "_"),
5172 Local::now().format("%Y%m%d_%H%M%S"),
5173 self.info.file.kind
5174 );
5175 let doc = self.doc.lock().unwrap();
5176 let msg = match doc.save(&name) {
5177 Err(e) => format!("{}", e),
5178 Ok(()) => format!("Saved {}.", name),
5179 };
5180 let notif = Notification::new(None, msg, false, hub, rq, context);
5181 self.children.push(Box::new(notif) as Box<dyn View>);
5182 true
5183 }
5184 Event::Select(EntryId::ApplyCroppings(index, scheme)) => {
5185 self.info.reader.as_mut().map(|r| {
5186 if r.cropping_margins.is_none() {
5187 r.cropping_margins = Some(CroppingMargins::Any(Margin::default()));
5188 }
5189 r.cropping_margins.as_mut().map(|c| c.apply(index, scheme))
5190 });
5191 true
5192 }
5193 Event::Select(EntryId::RemoveCroppings) => {
5194 if let Some(r) = self.info.reader.as_mut() {
5195 r.cropping_margins = None;
5196 }
5197 self.cache.clear();
5198 self.update(None, hub, rq, context);
5199 true
5200 }
5201 Event::Select(EntryId::SearchDirection(dir)) => {
5202 self.search_direction = dir;
5203 true
5204 }
5205 Event::Select(EntryId::SetFontFamily(ref font_family)) => {
5206 self.set_font_family(font_family, hub, rq, context);
5207 true
5208 }
5209 Event::Select(EntryId::SetTextAlign(text_align)) => {
5210 self.set_text_align(text_align, hub, rq, context);
5211 true
5212 }
5213 Event::Select(EntryId::SetFontSize(v)) => {
5214 let font_size = self
5215 .info
5216 .reader
5217 .as_ref()
5218 .and_then(|r| r.font_size)
5219 .unwrap_or(context.settings.reader.font_size);
5220 let font_size = font_size - 1.0 + v as f32 / 10.0;
5221 self.set_font_size(font_size, hub, rq, context);
5222 true
5223 }
5224 Event::Select(EntryId::SetMarginWidth(width)) => {
5225 self.set_margin_width(width, hub, rq, context);
5226 true
5227 }
5228 Event::Select(EntryId::SetLineHeight(v)) => {
5229 let line_height = 1.0 + v as f32 / 10.0;
5230 self.set_line_height(line_height, hub, rq, context);
5231 true
5232 }
5233 Event::Select(EntryId::SetContrastExponent(v)) => {
5234 let exponent = 1.0 + v as f32 / 2.0;
5235 self.set_contrast_exponent(exponent, hub, rq, context);
5236 true
5237 }
5238 Event::Select(EntryId::SetContrastGray(v)) => {
5239 let gray = ((1 << 8) - (1 << (8 - v))) as f32;
5240 self.set_contrast_gray(gray, hub, rq, context);
5241 true
5242 }
5243 Event::Select(EntryId::SetPageName) => {
5244 self.toggle_name_page(None, hub, rq, context);
5245 true
5246 }
5247 Event::Select(EntryId::RemovePageName) => {
5248 if let Some(ref mut r) = self.info.reader {
5249 r.page_names.remove(&self.current_page);
5250 }
5251 true
5252 }
5253 Event::Select(EntryId::ToggleInverted) => {
5254 self.update_noninverted_regions(!context.fb.inverted());
5255 false
5256 }
5257 Event::Reseed => {
5258 self.reseed(rq, context);
5259 true
5260 }
5261 Event::ToggleFrontlight => {
5262 if let Some(index) = locate::<TopBar>(self) {
5263 self.child_mut(index)
5264 .downcast_mut::<TopBar>()
5265 .unwrap()
5266 .update_frontlight_icon(rq, context);
5267 }
5268 true
5269 }
5270 Event::Device(DeviceEvent::Button {
5271 code: ButtonCode::Home,
5272 status: ButtonStatus::Pressed,
5273 ..
5274 }) => {
5275 self.quit(context);
5276 hub.send(Event::Back).ok();
5277 true
5278 }
5279 Event::Select(EntryId::Quit)
5280 | Event::Select(EntryId::Reboot)
5281 | Event::Select(EntryId::Restart)
5282 | Event::Back
5283 | Event::Suspend => {
5284 self.quit(context);
5285 false
5286 }
5287 Event::Focus(v) => {
5288 if self.focus != v {
5289 if let Some(ViewId::ReaderSearchInput) = v {
5290 self.toggle_results_bar(false, rq, context);
5291 if let Some(ref mut s) = self.search {
5292 s.running.store(false, AtomicOrdering::Relaxed);
5293 }
5294 self.render_results(rq);
5295 self.search = None;
5296 }
5297 self.focus = v;
5298 if v.is_some() {
5299 self.toggle_keyboard(true, v, hub, rq, context);
5300 }
5301 }
5302 true
5303 }
5304 _ => false,
5305 }
5306 }
5307
5308 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, _fonts), fields(rect = ?rect)))]
5309 fn render(&self, fb: &mut dyn Framebuffer, rect: Rectangle, _fonts: &mut Fonts) {
5310 fb.draw_rectangle(&rect, WHITE);
5311
5312 for chunk in &self.chunks {
5313 let Resource {
5314 ref pixmap, scale, ..
5315 } = self.cache[&chunk.location];
5316 let chunk_rect = chunk.frame - chunk.frame.min + chunk.position;
5317
5318 if let Some(region_rect) = rect.intersection(&chunk_rect) {
5319 let chunk_frame = region_rect - chunk.position + chunk.frame.min;
5320 let chunk_position = region_rect.min;
5321 fb.draw_framed_pixmap_contrast(
5322 pixmap,
5323 &chunk_frame,
5324 chunk_position,
5325 self.contrast.exponent,
5326 self.contrast.gray,
5327 );
5328
5329 if let Some(rects) = self.noninverted_regions.get(&chunk.location) {
5330 for r in rects {
5331 let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5332 if let Some(ref image_rect) = rect.intersection(®ion_rect) {
5333 fb.invert_region(image_rect);
5334 }
5335 }
5336 }
5337
5338 if let Some(groups) = self
5339 .search
5340 .as_ref()
5341 .and_then(|s| s.highlights.get(&chunk.location))
5342 {
5343 for rects in groups {
5344 let mut last_rect: Option<Rectangle> = None;
5345 for r in rects {
5346 let rect = (*r * scale).to_rect() - chunk.frame.min + chunk.position;
5347 if let Some(ref search_rect) = rect.intersection(®ion_rect) {
5348 fb.invert_region(search_rect);
5349 }
5350 if let Some(last) = last_rect {
5351 if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5352 > rect.height().min(last.height()) as i32 / 2
5353 && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5354 {
5355 let space = if last.max.x < rect.min.x {
5356 rect![
5357 last.max.x,
5358 (last.min.y + rect.min.y) / 2,
5359 rect.min.x,
5360 (last.max.y + rect.max.y) / 2
5361 ]
5362 } else {
5363 rect![
5364 rect.max.x,
5365 (last.min.y + rect.min.y) / 2,
5366 last.min.x,
5367 (last.max.y + rect.max.y) / 2
5368 ]
5369 };
5370 if let Some(ref res_rect) = space.intersection(®ion_rect) {
5371 fb.invert_region(res_rect);
5372 }
5373 }
5374 }
5375 last_rect = Some(rect);
5376 }
5377 }
5378 }
5379
5380 if let Some(annotations) = self.annotations.get(&chunk.location) {
5381 for annot in annotations {
5382 let drift = if annot.note.is_empty() {
5383 HIGHLIGHT_DRIFT
5384 } else {
5385 ANNOTATION_DRIFT
5386 };
5387 let [start, end] = annot.selection;
5388 if let Some(text) = self.text.get(&chunk.location) {
5389 let mut last_rect: Option<Rectangle> = None;
5390 for word in text
5391 .iter()
5392 .filter(|w| w.location >= start && w.location <= end)
5393 {
5394 let rect = (word.rect * scale).to_rect() - chunk.frame.min
5395 + chunk.position;
5396 if let Some(ref sel_rect) = rect.intersection(®ion_rect) {
5397 fb.shift_region(sel_rect, drift);
5398 }
5399 if let Some(last) = last_rect {
5400 if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5402 > rect.height().min(last.height()) as i32 / 2
5403 && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5404 {
5405 let space = if last.max.x < rect.min.x {
5406 rect![
5407 last.max.x,
5408 (last.min.y + rect.min.y) / 2,
5409 rect.min.x,
5410 (last.max.y + rect.max.y) / 2
5411 ]
5412 } else {
5413 rect![
5414 rect.max.x,
5415 (last.min.y + rect.min.y) / 2,
5416 last.min.x,
5417 (last.max.y + rect.max.y) / 2
5418 ]
5419 };
5420 if let Some(ref sel_rect) = space.intersection(®ion_rect)
5421 {
5422 fb.shift_region(sel_rect, drift);
5423 }
5424 }
5425 }
5426 last_rect = Some(rect);
5427 }
5428 }
5429 }
5430 }
5431
5432 if let Some(sel) = self.selection.as_ref() {
5433 if let Some(text) = self.text.get(&chunk.location) {
5434 let mut last_rect: Option<Rectangle> = None;
5435 for word in text
5436 .iter()
5437 .filter(|w| w.location >= sel.start && w.location <= sel.end)
5438 {
5439 let rect =
5440 (word.rect * scale).to_rect() - chunk.frame.min + chunk.position;
5441 if let Some(ref sel_rect) = rect.intersection(®ion_rect) {
5442 fb.invert_region(sel_rect);
5443 }
5444 if let Some(last) = last_rect {
5445 if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y)
5446 > rect.height().min(last.height()) as i32 / 2
5447 && (last.max.x < rect.min.x || rect.max.x < last.min.x)
5448 {
5449 let space = if last.max.x < rect.min.x {
5450 rect![
5451 last.max.x,
5452 (last.min.y + rect.min.y) / 2,
5453 rect.min.x,
5454 (last.max.y + rect.max.y) / 2
5455 ]
5456 } else {
5457 rect![
5458 rect.max.x,
5459 (last.min.y + rect.min.y) / 2,
5460 last.min.x,
5461 (last.max.y + rect.max.y) / 2
5462 ]
5463 };
5464 if let Some(ref sel_rect) = space.intersection(®ion_rect) {
5465 fb.invert_region(sel_rect);
5466 }
5467 }
5468 }
5469 last_rect = Some(rect);
5470 }
5471 }
5472 }
5473 }
5474 }
5475
5476 if self
5477 .info
5478 .reader
5479 .as_ref()
5480 .map_or(false, |r| r.bookmarks.contains(&self.current_page))
5481 {
5482 let dpi = CURRENT_DEVICE.dpi;
5483 let thickness = scale_by_dpi(3.0, dpi) as u16;
5484 let radius = mm_to_px(0.4, dpi) as i32 + thickness as i32;
5485 let center = pt!(self.rect.max.x - 5 * radius, self.rect.min.y + 5 * radius);
5486 fb.draw_rounded_rectangle_with_border(
5487 &Rectangle::from_disk(center, radius),
5488 &CornerSpec::Uniform(radius),
5489 &BorderSpec {
5490 thickness,
5491 color: WHITE,
5492 },
5493 &BLACK,
5494 );
5495 }
5496 }
5497
5498 fn render_rect(&self, rect: &Rectangle) -> Rectangle {
5499 rect.intersection(&self.rect).unwrap_or(self.rect)
5500 }
5501
5502 fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
5503 if !self.children.is_empty() {
5504 let dpi = CURRENT_DEVICE.dpi;
5505 let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32;
5506 let (small_thickness, big_thickness) = halves(thickness);
5507 let (small_height, big_height) = (
5508 scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32,
5509 scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32,
5510 );
5511 let mut floating_layer_start = 0;
5512
5513 self.children.retain(|child| !child.is::<Menu>());
5514
5515 if self.children[0].is::<TopBar>() {
5516 let top_bar_rect = rect![
5517 rect.min.x,
5518 rect.min.y,
5519 rect.max.x,
5520 small_height - small_thickness
5521 ];
5522 self.children[0].resize(top_bar_rect, hub, rq, context);
5523 let separator_rect = rect![
5524 rect.min.x,
5525 small_height - small_thickness,
5526 rect.max.x,
5527 small_height + big_thickness
5528 ];
5529 self.children[1].resize(separator_rect, hub, rq, context);
5530 } else if self.children[0].is::<Filler>() {
5531 let mut index = 1;
5532 if self.children[index].is::<SearchBar>() {
5533 let sb_rect = rect![
5534 rect.min.x,
5535 rect.max.y - (3 * big_height + 2 * small_height) as i32 + big_thickness,
5536 rect.max.x,
5537 rect.max.y - (3 * big_height + small_height) as i32 - small_thickness
5538 ];
5539 self.children[index].resize(sb_rect, hub, rq, context);
5540 self.children[index - 1].resize(
5541 rect![
5542 rect.min.x,
5543 sb_rect.min.y - thickness,
5544 rect.max.x,
5545 sb_rect.min.y
5546 ],
5547 hub,
5548 rq,
5549 context,
5550 );
5551 index += 2;
5552 }
5553 if self.children[index].is::<Keyboard>() {
5554 let kb_rect = rect![
5555 rect.min.x,
5556 rect.max.y - (small_height + 3 * big_height) as i32 + big_thickness,
5557 rect.max.x,
5558 rect.max.y - small_height - small_thickness
5559 ];
5560 self.children[index].resize(kb_rect, hub, rq, context);
5561 self.children[index + 1].resize(
5562 rect![
5563 rect.min.x,
5564 kb_rect.max.y,
5565 rect.max.x,
5566 kb_rect.max.y + thickness
5567 ],
5568 hub,
5569 rq,
5570 context,
5571 );
5572 let kb_rect = *self.children[index].rect();
5573 self.children[index - 1].resize(
5574 rect![
5575 rect.min.x,
5576 kb_rect.min.y - thickness,
5577 rect.max.x,
5578 kb_rect.min.y
5579 ],
5580 hub,
5581 rq,
5582 context,
5583 );
5584 index += 2;
5585 }
5586 floating_layer_start = index;
5587 }
5588
5589 if let Some(mut index) = locate::<BottomBar>(self) {
5590 floating_layer_start = index + 1;
5591 let separator_rect = rect![
5592 rect.min.x,
5593 rect.max.y - small_height - small_thickness,
5594 rect.max.x,
5595 rect.max.y - small_height + big_thickness
5596 ];
5597 self.children[index - 1].resize(separator_rect, hub, rq, context);
5598 let bottom_bar_rect = rect![
5599 rect.min.x,
5600 rect.max.y - small_height + big_thickness,
5601 rect.max.x,
5602 rect.max.y
5603 ];
5604 self.children[index].resize(bottom_bar_rect, hub, rq, context);
5605
5606 index -= 2;
5607
5608 while index > 2 {
5609 let bar_height = if self.children[index].is::<ToolBar>() {
5610 2 * big_height
5611 } else if self.children[index].is::<Keyboard>() {
5612 3 * big_height
5613 } else {
5614 small_height
5615 } as i32;
5616
5617 let y_max = self.children[index + 1].rect().min.y;
5618 let bar_rect = rect![
5619 rect.min.x,
5620 y_max - bar_height + thickness,
5621 rect.max.x,
5622 y_max
5623 ];
5624 self.children[index].resize(bar_rect, hub, rq, context);
5625 let y_max = self.children[index].rect().min.y;
5626 let sp_rect = rect![rect.min.x, y_max - thickness, rect.max.x, y_max];
5627 self.children[index - 1].resize(sp_rect, hub, rq, context);
5628
5629 index -= 2;
5630 }
5631 }
5632
5633 for i in floating_layer_start..self.children.len() {
5634 self.children[i].resize(rect, hub, rq, context);
5635 }
5636 }
5637
5638 match self.view_port.zoom_mode {
5639 ZoomMode::FitToWidth => {
5640 let ratio = (rect.width() as i32 - 2 * self.view_port.margin_width) as f32
5642 / (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32;
5643 self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32;
5644 }
5645 ZoomMode::Custom(_) => {
5646 self.view_port.page_offset += pt!(
5648 self.rect.width() as i32 - rect.width() as i32,
5649 self.rect.height() as i32 - rect.height() as i32
5650 ) / 2;
5651 }
5652 _ => (),
5653 }
5654
5655 self.rect = rect;
5656
5657 if self.reflowable {
5658 let font_size = self
5659 .info
5660 .reader
5661 .as_ref()
5662 .and_then(|r| r.font_size)
5663 .unwrap_or(context.settings.reader.font_size);
5664 let mut doc = self.doc.lock().unwrap();
5665 doc.layout(rect.width(), rect.height(), font_size, CURRENT_DEVICE.dpi);
5666 let current_page = self.current_page.min(doc.pages_count() - 1);
5667 if let Some(location) = doc.resolve_location(Location::Exact(current_page)) {
5668 self.current_page = location;
5669 }
5670 self.text.clear();
5671 }
5672
5673 self.cache.clear();
5674 self.update(Some(UpdateMode::Full), hub, rq, context);
5675 }
5676
5677 fn might_rotate(&self) -> bool {
5678 self.search.is_none()
5679 }
5680
5681 fn is_background(&self) -> bool {
5682 true
5683 }
5684
5685 fn rect(&self) -> &Rectangle {
5686 &self.rect
5687 }
5688
5689 fn rect_mut(&mut self) -> &mut Rectangle {
5690 &mut self.rect
5691 }
5692
5693 fn children(&self) -> &Vec<Box<dyn View>> {
5694 &self.children
5695 }
5696
5697 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
5698 &mut self.children
5699 }
5700
5701 fn id(&self) -> Id {
5702 self.id
5703 }
5704}