1pub mod action_label;
13pub mod battery;
14pub mod button;
15pub mod calculator;
16pub mod clock;
17pub mod common;
18pub mod dialog;
19pub mod dictionary;
20pub mod file_chooser;
21pub mod filler;
22pub mod frontlight;
23pub mod home;
24pub mod icon;
25pub mod image;
26pub mod input_field;
27pub mod intermission;
28pub mod key;
29pub mod keyboard;
30pub mod label;
31pub mod labeled_icon;
32pub mod menu;
33pub mod menu_entry;
34pub mod named_input;
35pub mod navigation;
36pub mod notification;
37pub mod ota;
38
39pub use self::notification::NotificationEvent;
40pub mod page_label;
41pub mod preset;
42pub mod presets_list;
43pub mod reader;
44pub mod rotation_values;
45pub mod rounded_button;
46pub mod search_bar;
47pub mod settings_editor;
48pub mod sketch;
49pub mod slider;
50pub mod toggle;
51pub mod toggleable_keyboard;
52pub mod top_bar;
53pub mod touch_events;
54
55use self::calculator::LineOrigin;
56use self::key::KeyKind;
57use crate::color::Color;
58use crate::context::Context;
59use crate::document::{Location, TextLocation};
60use crate::font::Fonts;
61use crate::framebuffer::{Framebuffer, UpdateMode};
62use crate::geom::{Boundary, CycleDir, LinearDir, Rectangle};
63use crate::gesture::GestureEvent;
64use crate::input::{DeviceEvent, FingerStatus};
65use crate::metadata::{
66 Info, Margin, PageScheme, ScrollMode, SimpleStatus, SortMethod, TextAlign, ZoomMode,
67};
68use crate::settings::{self, ButtonScheme, FirstColumn, RotationLock, SecondColumn};
69use crate::view::ota::OtaEntryId;
70use downcast_rs::{impl_downcast, Downcast};
71use fxhash::FxHashMap;
72use std::collections::VecDeque;
73use std::fmt::{self, Debug};
74use std::ops::{Deref, DerefMut};
75use std::path::PathBuf;
76use std::sync::atomic::{AtomicU64, Ordering};
77use std::sync::mpsc::Sender;
78use std::time::{Duration, Instant};
79use tracing::error;
80
81pub const THICKNESS_SMALL: f32 = 1.0;
83pub const THICKNESS_MEDIUM: f32 = 2.0;
84pub const THICKNESS_LARGE: f32 = 3.0;
85
86pub const BORDER_RADIUS_SMALL: f32 = 6.0;
88pub const BORDER_RADIUS_MEDIUM: f32 = 9.0;
89pub const BORDER_RADIUS_LARGE: f32 = 12.0;
90
91pub const SMALL_BAR_HEIGHT: f32 = 121.0;
94pub const BIG_BAR_HEIGHT: f32 = 163.0;
95
96pub const CLOSE_IGNITION_DELAY: Duration = Duration::from_millis(150);
97
98pub type Bus = VecDeque<Event>;
99pub type Hub = Sender<Event>;
100
101pub trait View: Downcast {
102 fn handle_event(
103 &mut self,
104 evt: &Event,
105 hub: &Hub,
106 bus: &mut Bus,
107 rq: &mut RenderQueue,
108 context: &mut Context,
109 ) -> bool;
110 fn render(&self, fb: &mut dyn Framebuffer, rect: Rectangle, fonts: &mut Fonts);
111 fn rect(&self) -> &Rectangle;
112 fn rect_mut(&mut self) -> &mut Rectangle;
113 fn children(&self) -> &Vec<Box<dyn View>>;
114 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>>;
115 fn id(&self) -> Id;
116
117 fn render_rect(&self, _rect: &Rectangle) -> Rectangle {
118 *self.rect()
119 }
120
121 fn resize(
122 &mut self,
123 rect: Rectangle,
124 _hub: &Hub,
125 _rq: &mut RenderQueue,
126 _context: &mut Context,
127 ) {
128 *self.rect_mut() = rect;
129 }
130
131 fn child(&self, index: usize) -> &dyn View {
132 self.children()[index].as_ref()
133 }
134
135 fn child_mut(&mut self, index: usize) -> &mut dyn View {
136 self.children_mut()[index].as_mut()
137 }
138
139 fn len(&self) -> usize {
140 self.children().len()
141 }
142
143 fn might_skip(&self, _evt: &Event) -> bool {
144 false
145 }
146
147 fn might_rotate(&self) -> bool {
148 true
149 }
150
151 fn is_background(&self) -> bool {
152 false
153 }
154
155 fn view_id(&self) -> Option<ViewId> {
156 None
157 }
158}
159
160impl_downcast!(View);
161
162impl Debug for Box<dyn View> {
163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 write!(f, "Box<dyn View>")
165 }
166}
167
168#[cfg_attr(feature = "otel", tracing::instrument(skip(view, hub, parent_bus, rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
175pub fn handle_event(
176 view: &mut dyn View,
177 evt: &Event,
178 hub: &Hub,
179 parent_bus: &mut Bus,
180 rq: &mut RenderQueue,
181 context: &mut Context,
182) -> bool {
183 if view.len() > 0 {
184 let mut captured = false;
185
186 if view.might_skip(evt) {
187 return captured;
188 }
189
190 let mut child_bus: Bus = VecDeque::with_capacity(1);
191
192 for i in (0..view.len()).rev() {
193 if handle_event(view.child_mut(i), evt, hub, &mut child_bus, rq, context) {
194 captured = true;
195 break;
196 }
197 }
198
199 let mut temp_bus: Bus = VecDeque::with_capacity(1);
200
201 child_bus
202 .retain(|child_evt| !view.handle_event(child_evt, hub, &mut temp_bus, rq, context));
203
204 parent_bus.append(&mut child_bus);
205 parent_bus.append(&mut temp_bus);
206
207 captured || view.handle_event(evt, hub, parent_bus, rq, context)
208 } else {
209 view.handle_event(evt, hub, parent_bus, rq, context)
210 }
211}
212
213#[cfg_attr(feature = "otel", tracing::instrument(skip(view, ids, rects, bgs, fb, fonts, updating), fields(wait = wait)))]
217pub fn render(
218 view: &dyn View,
219 wait: bool,
220 ids: &FxHashMap<Id, Vec<Rectangle>>,
221 rects: &mut Vec<Rectangle>,
222 bgs: &mut Vec<Rectangle>,
223 fb: &mut dyn Framebuffer,
224 fonts: &mut Fonts,
225 updating: &mut Vec<UpdateData>,
226) {
227 let mut render_rects = Vec::new();
228
229 if view.len() == 0 || view.is_background() {
230 for rect in ids
231 .get(&view.id())
232 .cloned()
233 .into_iter()
234 .flatten()
235 .chain(rects.iter().filter_map(|r| r.intersection(view.rect())))
236 .chain(bgs.iter().filter_map(|r| r.intersection(view.rect())))
237 {
238 let render_rect = view.render_rect(&rect);
239
240 if wait {
241 updating.retain(|update| {
242 let overlaps = render_rect.overlaps(&update.rect);
243 if overlaps && !update.has_completed() {
244 fb.wait(update.token)
245 .map_err(|e| {
246 error!("Can't wait for {}, {}: {:#}", update.token, update.rect, e)
247 })
248 .ok();
249 }
250 !overlaps
251 });
252 }
253
254 view.render(fb, rect, fonts);
255 render_rects.push(render_rect);
256
257 if *view.rect() == render_rect {
259 break;
260 }
261 }
262 } else {
263 bgs.extend(ids.get(&view.id()).cloned().into_iter().flatten());
264 }
265
266 for rect in render_rects.into_iter() {
268 if rects.is_empty() {
269 rects.push(rect);
270 } else {
271 if let Some(last) = rects.last_mut() {
272 if rect.extends(last) {
273 last.absorb(&rect);
274 let mut i = rects.len();
275 while i > 1 && rects[i - 1].extends(&rects[i - 2]) {
276 if let Some(rect) = rects.pop() {
277 if let Some(last) = rects.last_mut() {
278 last.absorb(&rect);
279 }
280 }
281 i -= 1;
282 }
283 } else {
284 let mut i = rects.len();
285 while i > 0 && !rects[i - 1].contains(&rect) {
286 i -= 1;
287 }
288 if i == 0 {
289 rects.push(rect);
290 }
291 }
292 }
293 }
294 }
295
296 for i in 0..view.len() {
297 render(view.child(i), wait, ids, rects, bgs, fb, fonts, updating);
298 }
299}
300
301#[inline]
302pub fn process_render_queue(
303 view: &dyn View,
304 rq: &mut RenderQueue,
305 context: &mut Context,
306 updating: &mut Vec<UpdateData>,
307) {
308 for ((mode, wait), pairs) in rq.drain() {
309 let mut ids = FxHashMap::default();
310 let mut rects = Vec::new();
311 let mut bgs = Vec::new();
312
313 for (id, rect) in pairs.into_iter().rev() {
314 if let Some(id) = id {
315 ids.entry(id).or_insert_with(Vec::new).push(rect);
316 } else {
317 bgs.push(rect);
318 }
319 }
320
321 render(
322 view,
323 wait,
324 &ids,
325 &mut rects,
326 &mut bgs,
327 context.fb.as_mut(),
328 &mut context.fonts,
329 updating,
330 );
331
332 for rect in rects {
333 match context.fb.update(&rect, mode) {
334 Ok(token) => {
335 updating.push(UpdateData {
336 token,
337 rect,
338 time: Instant::now(),
339 });
340 }
341 Err(err) => {
342 error!("Can't update {}: {:#}.", rect, err);
343 }
344 }
345 }
346 }
347}
348
349#[inline]
350pub fn wait_for_all(updating: &mut Vec<UpdateData>, context: &mut Context) {
351 for update in updating.drain(..) {
352 if update.has_completed() {
353 continue;
354 }
355 context
356 .fb
357 .wait(update.token)
358 .map_err(|e| error!("Can't wait for {}, {}: {:#}", update.token, update.rect, e))
359 .ok();
360 }
361}
362
363#[derive(Debug, Clone)]
364pub enum ToggleEvent {
365 View(ViewId),
366 Setting(settings_editor::ToggleSettings),
367}
368
369#[derive(Debug, Clone)]
370pub enum Event {
371 Device(DeviceEvent),
372 Gesture(GestureEvent),
373 Keyboard(KeyboardEvent),
374 Key(KeyKind),
375 Open(Box<Info>),
376 OpenHtml(String, Option<String>),
377 LoadPixmap(usize),
378 Update(UpdateMode),
379 RefreshBookPreview(PathBuf, Option<PathBuf>),
380 Invalid(PathBuf),
381 Notification(NotificationEvent),
382 #[deprecated(note = "Use Event::Notification(NotificationEvent::Show) instead")]
383 Notify(String),
384 Page(CycleDir),
385 ResultsPage(CycleDir),
386 GoTo(usize),
387 GoToLocation(Location),
388 ResultsGoTo(usize),
389 CropMargins(Box<Margin>),
390 Chapter(CycleDir),
391 SelectDirectory(PathBuf),
392 ToggleSelectDirectory(PathBuf),
393 NavigationBarResized(i32),
394 Focus(Option<ViewId>),
436 Select(EntryId),
437 PropagateSelect(EntryId),
438 EditLanguages,
439 Define(String),
440 Submit(ViewId, String),
441 Slider(SliderId, f32, FingerStatus),
442 ToggleNear(ViewId, Rectangle),
443 ToggleInputHistoryMenu(ViewId, Rectangle),
444 ToggleBookMenu(Rectangle, usize),
445 TogglePresetMenu(Rectangle, usize),
446 SubMenu(Rectangle, Vec<EntryKind>),
447 OpenSettingsCategory(settings_editor::Category),
448 SelectSettingsCategory(settings_editor::Category),
449 UpdateSettings(Box<settings::Settings>),
450 EditLibrary(usize),
451 UpdateLibrary(usize, Box<settings::LibrarySettings>),
452 AddLibrary,
453 DeleteLibrary(usize),
454 ProcessLine(LineOrigin, String),
455 History(CycleDir, bool),
456 #[deprecated(note = "Use Event::NewToggle(ToggleEvent::View(ViewID)) instead")]
457 Toggle(ViewId),
458 NewToggle(ToggleEvent),
460 Show(ViewId),
461 Close(ViewId),
462 CloseSub(ViewId),
463 Search(String),
464 SearchResult(usize, Vec<Boundary>),
465 FetcherAddDocument(u32, Box<Info>),
466 FetcherRemoveDocument(u32, PathBuf),
467 FetcherSearch {
468 id: u32,
469 path: Option<PathBuf>,
470 query: Option<String>,
471 sort_by: Option<(SortMethod, bool)>,
472 },
473 CheckFetcher(u32),
474 EndOfSearch,
475 Finished,
476 ClockTick,
477 BatteryTick,
478 ToggleFrontlight,
479 Load(PathBuf),
480 LoadPreset(usize),
481 Scroll(i32),
482 Save,
483 Guess,
484 CheckBattery,
485 SetWifi(bool),
486 MightSuspend,
487 PrepareSuspend,
488 Suspend,
489 Share,
490 PrepareShare,
491 Validate,
492 Cancel,
493 Reseed,
494 Back,
495 Quit,
496 WakeUp,
497 Hold(EntryId),
498 FileChooserClosed(Option<PathBuf>),
501}
502
503#[derive(Debug, Clone, Eq, PartialEq)]
504pub enum AppCmd {
505 Sketch,
506 Calculator,
507 Dictionary { query: String, language: String },
508 SettingsEditor,
509 TouchEvents,
510 RotationValues,
511}
512
513#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
514pub enum ViewId {
515 Home,
516 Reader,
517 SortMenu,
518 MainMenu,
519 TitleMenu,
520 SelectionMenu,
521 AnnotationMenu,
522 BatteryMenu,
523 ClockMenu,
524 SearchTargetMenu,
525 InputHistoryMenu,
526 KeyboardLayoutMenu,
527 Frontlight,
528 Dictionary,
529 FontSizeMenu,
530 TextAlignMenu,
531 FontFamilyMenu,
532 MarginWidthMenu,
533 ContrastExponentMenu,
534 ContrastGrayMenu,
535 LineHeightMenu,
536 DirectoryMenu,
537 BookMenu,
538 LibraryMenu,
539 PageMenu,
540 PresetMenu,
541 MarginCropperMenu,
542 SearchMenu,
543 SettingsMenu,
545 SettingsValueMenu,
546 SettingsCategoryEditor,
547 LibraryEditor,
548 LibraryRename,
549 LibraryRenameInput,
550 AutoSuspendInput,
551 AutoPowerOffInput,
552 SettingsRetentionInput,
553 IntermissionSuspendInput,
554 IntermissionPowerOffInput,
555 IntermissionShareInput,
556 SketchMenu,
557 RenameDocument,
558 RenameDocumentInput,
559 GoToPage,
560 GoToPageInput,
561 GoToResultsPage,
562 GoToResultsPageInput,
563 NamePage,
564 NamePageInput,
565 EditNote,
566 EditNoteInput,
567 EditLanguages,
568 EditLanguagesInput,
569 HomeSearchInput,
570 ReaderSearchInput,
571 DictionarySearchInput,
572 CalculatorInput,
573 SearchBar,
574 AddressBar,
575 AddressBarInput,
576 Keyboard,
577 AboutDialog,
578 ShareDialog,
579 MarginCropper,
580 TopBottomBars,
581 TableOfContents,
582 MessageNotif(Id),
583 SubMenu(u8),
584 Ota(ota::OtaViewId),
585 FileChooser,
586}
587
588#[derive(Debug, Copy, Clone, Eq, PartialEq)]
589pub enum SliderId {
590 FontSize,
591 LightIntensity,
592 LightWarmth,
593 ContrastExponent,
594 ContrastGray,
595}
596
597impl SliderId {
598 pub fn label(self) -> String {
599 match self {
600 SliderId::LightIntensity => "Intensity".to_string(),
601 SliderId::LightWarmth => "Warmth".to_string(),
602 SliderId::FontSize => "Font Size".to_string(),
603 SliderId::ContrastExponent => "Contrast Exponent".to_string(),
604 SliderId::ContrastGray => "Contrast Gray".to_string(),
605 }
606 }
607}
608
609#[derive(Debug, Clone)]
610pub enum Align {
611 Left(i32),
612 Right(i32),
613 Center,
614}
615
616impl Align {
617 #[inline]
618 pub fn offset(&self, width: i32, container_width: i32) -> i32 {
619 match *self {
620 Align::Left(dx) => dx,
621 Align::Right(dx) => container_width - width - dx,
622 Align::Center => (container_width - width) / 2,
623 }
624 }
625}
626
627#[derive(Debug, Copy, Clone)]
628pub enum KeyboardEvent {
629 Append(char),
630 Partial(char),
631 Move { target: TextKind, dir: LinearDir },
632 Delete { target: TextKind, dir: LinearDir },
633 Submit,
634}
635
636#[derive(Debug, Copy, Clone)]
637pub enum TextKind {
638 Char,
639 Word,
640 Extremum,
641}
642
643#[derive(Debug, Clone)]
644pub enum EntryKind {
645 Message(String, Option<String>),
646 Command(String, EntryId),
647 CheckBox(String, EntryId, bool),
648 RadioButton(String, EntryId, bool),
649 SubMenu(String, Vec<EntryKind>),
650 More(Vec<EntryKind>),
651 Separator,
652}
653
654#[derive(Debug, Clone, Eq, PartialEq)]
655pub enum EntryId {
656 About,
657 SystemInfo,
658 OpenDocumentation,
659 LoadLibrary(usize),
660 Load(PathBuf),
661 Flush,
662 Save,
663 Import,
664 CleanUp,
665 Sort(SortMethod),
666 ReverseOrder,
667 EmptyTrash,
668 Rename(PathBuf),
669 Remove(PathBuf),
670 CopyTo(PathBuf, usize),
671 MoveTo(PathBuf, usize),
672 AddDirectory(PathBuf),
673 SelectDirectory(PathBuf),
674 ToggleSelectDirectory(PathBuf),
675 SetStatus(PathBuf, SimpleStatus),
676 SearchAuthor(String),
677 RemovePreset(usize),
678 FirstColumn(FirstColumn),
679 SecondColumn(SecondColumn),
680 ThumbnailPreviews,
681 ApplyCroppings(usize, PageScheme),
682 RemoveCroppings,
683 SetZoomMode(ZoomMode),
684 SetScrollMode(ScrollMode),
685 SetPageName,
686 RemovePageName,
687 HighlightSelection,
688 AnnotateSelection,
689 DefineSelection,
690 SearchForSelection,
691 AdjustSelection,
692 Annotations,
693 Bookmarks,
694 RemoveAnnotation([TextLocation; 2]),
695 EditAnnotationNote([TextLocation; 2]),
696 RemoveAnnotationNote([TextLocation; 2]),
697 GoTo(usize),
698 GoToSelectedPageName,
699 SearchDirection(LinearDir),
700 SetButtonScheme(ButtonScheme),
701 SetFontFamily(String),
702 SetFontSize(i32),
703 SetTextAlign(TextAlign),
704 SetMarginWidth(i32),
705 SetLineHeight(i32),
706 SetContrastExponent(i32),
707 SetContrastGray(i32),
708 SetRotationLock(Option<RotationLock>),
709 SetSearchTarget(Option<String>),
710 SetInputText(ViewId, String),
711 SetKeyboardLayout(String),
712 EditLibraryName,
714 EditLibraryPath,
715 SetLibraryMode(settings::LibraryMode),
716 DeleteLibrary(usize),
717 SetIntermission(settings::IntermKind, settings::IntermissionDisplay),
718 EditIntermissionImage(settings::IntermKind),
719 ToggleShowHidden,
720 #[deprecated(note = "Use ToggleEvent::Settings instead")]
721 ToggleSleepCover,
722 #[deprecated(note = "Use ToggleEvent::Settings instead")]
723 ToggleAutoShare,
724 EditAutoSuspend,
725 EditAutoPowerOff,
726 EditSettingsRetention,
727 ToggleFuzzy,
728 ToggleInverted,
729 ToggleDithered,
730 ToggleWifi,
731 Rotate(i8),
732 Launch(AppCmd),
733 SetPenSize(i32),
734 SetPenColor(Color),
735 TogglePenDynamism,
736 ReloadDictionaries,
737 New,
738 Refresh,
739 TakeScreenshot,
740 Restart,
741 Reboot,
742 Quit,
743 CheckForUpdates,
744 FileEntry(PathBuf),
745 Ota(OtaEntryId),
746}
747
748impl EntryKind {
749 pub fn is_separator(&self) -> bool {
750 matches!(*self, EntryKind::Separator)
751 }
752
753 pub fn text(&self) -> &str {
754 match *self {
755 EntryKind::Message(ref s, ..)
756 | EntryKind::Command(ref s, ..)
757 | EntryKind::CheckBox(ref s, ..)
758 | EntryKind::RadioButton(ref s, ..)
759 | EntryKind::SubMenu(ref s, ..) => s,
760 EntryKind::More(..) => "More",
761 _ => "",
762 }
763 }
764
765 pub fn get(&self) -> Option<bool> {
766 match *self {
767 EntryKind::CheckBox(_, _, v) | EntryKind::RadioButton(_, _, v) => Some(v),
768 _ => None,
769 }
770 }
771
772 pub fn set(&mut self, value: bool) {
773 match *self {
774 EntryKind::CheckBox(_, _, ref mut v) | EntryKind::RadioButton(_, _, ref mut v) => {
775 *v = value
776 }
777 _ => (),
778 }
779 }
780}
781
782pub struct RenderData {
783 pub id: Option<Id>,
784 pub rect: Rectangle,
785 pub mode: UpdateMode,
786 pub wait: bool,
787}
788
789impl RenderData {
790 pub fn new(id: Id, rect: Rectangle, mode: UpdateMode) -> RenderData {
791 RenderData {
792 id: Some(id),
793 rect,
794 mode,
795 wait: true,
796 }
797 }
798
799 pub fn no_wait(id: Id, rect: Rectangle, mode: UpdateMode) -> RenderData {
800 RenderData {
801 id: Some(id),
802 rect,
803 mode,
804 wait: false,
805 }
806 }
807
808 pub fn expose(rect: Rectangle, mode: UpdateMode) -> RenderData {
809 RenderData {
810 id: None,
811 rect,
812 mode,
813 wait: true,
814 }
815 }
816}
817
818pub struct UpdateData {
819 pub token: u32,
820 pub time: Instant,
821 pub rect: Rectangle,
822}
823
824pub const MAX_UPDATE_DELAY: Duration = Duration::from_millis(600);
825
826impl UpdateData {
827 pub fn has_completed(&self) -> bool {
828 self.time.elapsed() >= MAX_UPDATE_DELAY
829 }
830}
831
832type RQ = FxHashMap<(UpdateMode, bool), Vec<(Option<Id>, Rectangle)>>;
833pub struct RenderQueue(RQ);
834
835impl RenderQueue {
836 pub fn new() -> RenderQueue {
837 RenderQueue(FxHashMap::default())
838 }
839
840 pub fn add(&mut self, data: RenderData) {
841 self.entry((data.mode, data.wait))
842 .or_insert_with(|| Vec::new())
843 .push((data.id, data.rect));
844 }
845
846 #[cfg(test)]
847 pub fn is_empty(&self) -> bool {
848 self.0.is_empty()
849 }
850
851 #[cfg(test)]
852 pub fn len(&self) -> usize {
853 self.0.values().map(|v| v.len()).sum()
854 }
855}
856
857impl Default for RenderQueue {
858 fn default() -> Self {
859 Self::new()
860 }
861}
862
863impl Deref for RenderQueue {
864 type Target = RQ;
865
866 fn deref(&self) -> &Self::Target {
867 &self.0
868 }
869}
870
871impl DerefMut for RenderQueue {
872 fn deref_mut(&mut self) -> &mut Self::Target {
873 &mut self.0
874 }
875}
876
877pub static ID_FEEDER: IdFeeder = IdFeeder::new(1);
878pub struct IdFeeder(AtomicU64);
879pub type Id = u64;
880
881impl IdFeeder {
882 pub const fn new(id: Id) -> Self {
883 IdFeeder(AtomicU64::new(id))
884 }
885
886 pub fn next(&self) -> Id {
887 self.0.fetch_add(1, Ordering::Relaxed)
888 }
889}