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