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