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