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 {
554 library_index: Option<usize>,
555 },
556 ImportFinished {
558 library_index: Option<usize>,
559 },
560 ThumbnailExtractionFinished {
567 library_index: Option<usize>,
568 },
569 ReindexDictionaries,
576 ReloadDictionaries,
585}
586
587#[derive(Debug, Clone, Eq, PartialEq)]
588pub enum AppCmd {
589 Sketch,
590 Calculator,
591 Dictionary { query: String, language: String },
592 SettingsEditor,
593 TouchEvents,
594 RotationValues,
595}
596
597#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
598pub enum ViewId {
599 Home,
600 Reader,
601 SortMenu,
602 MainMenu,
603 TitleMenu,
604 SelectionMenu,
605 AnnotationMenu,
606 BatteryMenu,
607 ClockMenu,
608 SearchTargetMenu,
609 InputHistoryMenu,
610 KeyboardLayoutMenu,
611 Frontlight,
612 Dictionary,
613 FontSizeMenu,
614 TextAlignMenu,
615 FontFamilyMenu,
616 MarginWidthMenu,
617 ContrastExponentMenu,
618 ContrastGrayMenu,
619 LineHeightMenu,
620 DirectoryMenu,
621 BookMenu,
622 LibraryMenu,
623 PageMenu,
624 PresetMenu,
625 MarginCropperMenu,
626 SearchMenu,
627 SettingsMenu,
629 SettingsValueMenu,
630 SettingsCategoryEditor,
631 LibraryEditor,
632 LibraryRename,
633 LibraryRenameInput,
634 AutoSuspendInput,
635 AutoPowerOffInput,
636 SettingsRetentionInput,
637 IntermissionSuspendInput,
638 IntermissionPowerOffInput,
639 IntermissionShareInput,
640 OtlpEndpointInput,
641 PyroscopeEndpointInput,
642 RefreshRateByKindEditor,
643 RefreshRateKindPairEditor,
644 RefreshRateRegularInput,
645 RefreshRateInvertedInput,
646 RefreshRateByKindRegularInput,
647 RefreshRateByKindInvertedInput,
648 SketchMenu,
649 RenameDocument,
650 RenameDocumentInput,
651 GoToPage,
652 GoToPageInput,
653 GoToResultsPage,
654 GoToResultsPageInput,
655 NamePage,
656 NamePageInput,
657 EditNote,
658 EditNoteInput,
659 EditLanguages,
660 EditLanguagesInput,
661 HomeSearchInput,
662 ReaderSearchInput,
663 DictionarySearchInput,
664 CalculatorInput,
665 SearchBar,
666 AddressBar,
667 AddressBarInput,
668 Keyboard,
669 AboutDialog,
670 ShareDialog,
671 DictionaryDownloadConfirm,
672 MarginCropper,
673 TopBottomBars,
674 TableOfContents,
675 MessageNotif(Id),
676 SubMenu(u8),
677 Ota(ota::OtaViewId),
678 FileChooser,
679}
680
681#[derive(Debug, Copy, Clone, Eq, PartialEq)]
682pub enum SliderId {
683 FontSize,
684 LightIntensity,
685 LightWarmth,
686 ContrastExponent,
687 ContrastGray,
688}
689
690impl SliderId {
691 pub fn label(self) -> String {
692 match self {
693 SliderId::LightIntensity => "Intensity".to_string(),
694 SliderId::LightWarmth => "Warmth".to_string(),
695 SliderId::FontSize => "Font Size".to_string(),
696 SliderId::ContrastExponent => "Contrast Exponent".to_string(),
697 SliderId::ContrastGray => "Contrast Gray".to_string(),
698 }
699 }
700}
701
702#[derive(Debug, Clone)]
703pub enum Align {
704 Left(i32),
705 Right(i32),
706 Center,
707}
708
709impl Align {
710 #[inline]
711 pub fn offset(&self, width: i32, container_width: i32) -> i32 {
712 match *self {
713 Align::Left(dx) => dx,
714 Align::Right(dx) => container_width - width - dx,
715 Align::Center => (container_width - width) / 2,
716 }
717 }
718}
719
720#[derive(Debug, Copy, Clone)]
721pub enum KeyboardEvent {
722 Append(char),
723 Partial(char),
724 Move { target: TextKind, dir: LinearDir },
725 Delete { target: TextKind, dir: LinearDir },
726 Submit,
727}
728
729#[derive(Debug, Copy, Clone)]
730pub enum TextKind {
731 Char,
732 Word,
733 Extremum,
734}
735
736#[derive(Debug, Clone)]
737pub enum EntryKind {
738 Message(String, Option<String>),
739 Command(String, EntryId),
740 CheckBox(String, EntryId, bool),
741 RadioButton(String, EntryId, bool),
742 SubMenu(String, Vec<EntryKind>),
743 More(Vec<EntryKind>),
744 Separator,
745}
746
747#[derive(Debug, Clone, Eq, PartialEq)]
748pub enum EntryId {
749 About,
750 SystemInfo,
751 OpenDocumentation,
752 LoadLibrary(usize),
753 Load(PathBuf),
754 Flush,
755 Save,
756 Import,
757 CleanUp,
758 Sort(SortMethod),
759 ReverseOrder,
760 EmptyTrash,
761 Rename(PathBuf),
762 Remove(PathBuf),
763 CopyTo(PathBuf, usize),
764 MoveTo(PathBuf, usize),
765 AddDirectory(PathBuf),
766 SelectDirectory(PathBuf),
767 ToggleSelectDirectory(PathBuf),
768 SetStatus(PathBuf, SimpleStatus),
769 SearchAuthor(String),
770 RemovePreset(usize),
771 FirstColumn(FirstColumn),
772 SecondColumn(SecondColumn),
773 ThumbnailPreviews,
774 ApplyCroppings(usize, PageScheme),
775 RemoveCroppings,
776 SetZoomMode(ZoomMode),
777 SetScrollMode(ScrollMode),
778 SetPageName,
779 RemovePageName,
780 HighlightSelection,
781 AnnotateSelection,
782 DefineSelection,
783 SearchForSelection,
784 AdjustSelection,
785 Annotations,
786 Bookmarks,
787 RemoveAnnotation([TextLocation; 2]),
788 EditAnnotationNote([TextLocation; 2]),
789 RemoveAnnotationNote([TextLocation; 2]),
790 GoTo(usize),
791 GoToSelectedPageName,
792 SearchDirection(LinearDir),
793 SetButtonScheme(ButtonScheme),
794 SetFontFamily(String),
795 SetFontSize(i32),
796 SetTextAlign(TextAlign),
797 SetMarginWidth(i32),
798 SetLineHeight(i32),
799 SetContrastExponent(i32),
800 SetContrastGray(i32),
801 SetRotationLock(Option<RotationLock>),
802 SetSearchTarget(Option<String>),
803 SetInputText(ViewId, String),
804 SetKeyboardLayout(String),
805 SetLocale(Option<LanguageIdentifier>),
806 EditLibraryName,
808 EditLibraryPath,
809 DeleteLibrary(usize),
810 SetFinishedAction(FinishedAction),
811 SetLibraryFinishedAction(usize, FinishedAction),
812 ClearLibraryFinishedAction(usize),
813 SetIntermission(settings::IntermKind, settings::IntermissionDisplay),
814 EditIntermissionImage(settings::IntermKind),
815 ToggleShowHidden,
816 #[deprecated(note = "Use ToggleEvent::Settings instead")]
817 ToggleSleepCover,
818 #[deprecated(note = "Use ToggleEvent::Settings instead")]
819 ToggleAutoShare,
820 EditAutoSuspend,
821 EditAutoPowerOff,
822 EditSettingsRetention,
823 SetLogLevel(tracing::Level),
824 EditOtlpEndpoint,
825 EditPyroscopeEndpoint,
826 ToggleFuzzy,
827 ToggleInverted,
828 ToggleDithered,
829 ToggleWifi,
830 Rotate(i8),
831 Launch(AppCmd),
832 SetPenSize(i32),
833 SetPenColor(Color),
834 TogglePenDynamism,
835 ReloadDictionaries,
836 RequestDictionaryDownload(String),
837 DownloadDictionary(String),
838 DeleteDictionary(String),
839 New,
840 Refresh,
841 TakeScreenshot,
842 Restart,
843 Reboot,
844 Quit,
845 Suspend,
846 PowerOff,
847 CheckForUpdates,
848 FileEntry(PathBuf),
849 Ota(OtaEntryId),
850 EditRefreshRateByKind(settings::FileExtension),
852 DeleteRefreshRateByKind(settings::FileExtension),
854 AddRefreshRateByKind,
856 ToggleAllowedKind(settings::FileExtension),
858 ToggleDitheredKind(settings::FileExtension),
860}
861
862impl EntryKind {
863 pub fn is_separator(&self) -> bool {
864 matches!(*self, EntryKind::Separator)
865 }
866
867 pub fn text(&self) -> &str {
868 match *self {
869 EntryKind::Message(ref s, ..)
870 | EntryKind::Command(ref s, ..)
871 | EntryKind::CheckBox(ref s, ..)
872 | EntryKind::RadioButton(ref s, ..)
873 | EntryKind::SubMenu(ref s, ..) => s,
874 EntryKind::More(..) => "More",
875 _ => "",
876 }
877 }
878
879 pub fn get(&self) -> Option<bool> {
880 match *self {
881 EntryKind::CheckBox(_, _, v) | EntryKind::RadioButton(_, _, v) => Some(v),
882 _ => None,
883 }
884 }
885
886 pub fn set(&mut self, value: bool) {
887 match *self {
888 EntryKind::CheckBox(_, _, ref mut v) | EntryKind::RadioButton(_, _, ref mut v) => {
889 *v = value
890 }
891 _ => (),
892 }
893 }
894}
895
896pub struct RenderData {
897 pub id: Option<Id>,
898 pub rect: Rectangle,
899 pub mode: UpdateMode,
900 pub wait: bool,
901}
902
903impl RenderData {
904 pub fn new(id: Id, rect: Rectangle, mode: UpdateMode) -> RenderData {
905 RenderData {
906 id: Some(id),
907 rect,
908 mode,
909 wait: true,
910 }
911 }
912
913 pub fn no_wait(id: Id, rect: Rectangle, mode: UpdateMode) -> RenderData {
914 RenderData {
915 id: Some(id),
916 rect,
917 mode,
918 wait: false,
919 }
920 }
921
922 pub fn expose(rect: Rectangle, mode: UpdateMode) -> RenderData {
923 RenderData {
924 id: None,
925 rect,
926 mode,
927 wait: true,
928 }
929 }
930}
931
932pub struct UpdateData {
933 pub token: u32,
934 pub time: Instant,
935 pub rect: Rectangle,
936}
937
938pub const MAX_UPDATE_DELAY: Duration = Duration::from_millis(600);
939
940impl UpdateData {
941 pub fn has_completed(&self) -> bool {
942 self.time.elapsed() >= MAX_UPDATE_DELAY
943 }
944}
945
946type RQ = FxHashMap<(UpdateMode, bool), Vec<(Option<Id>, Rectangle)>>;
947pub struct RenderQueue(RQ);
948
949impl RenderQueue {
950 pub fn new() -> RenderQueue {
951 RenderQueue(FxHashMap::default())
952 }
953
954 pub fn add(&mut self, data: RenderData) {
955 self.entry((data.mode, data.wait))
956 .or_insert_with(|| Vec::new())
957 .push((data.id, data.rect));
958 }
959
960 #[cfg(test)]
961 pub fn is_empty(&self) -> bool {
962 self.0.is_empty()
963 }
964
965 #[cfg(test)]
966 pub fn len(&self) -> usize {
967 self.0.values().map(|v| v.len()).sum()
968 }
969}
970
971impl Default for RenderQueue {
972 fn default() -> Self {
973 Self::new()
974 }
975}
976
977impl Deref for RenderQueue {
978 type Target = RQ;
979
980 fn deref(&self) -> &Self::Target {
981 &self.0
982 }
983}
984
985impl DerefMut for RenderQueue {
986 fn deref_mut(&mut self) -> &mut Self::Target {
987 &mut self.0
988 }
989}
990
991pub static ID_FEEDER: IdFeeder = IdFeeder::new(1);
992pub struct IdFeeder(AtomicU64);
993pub type Id = u64;
994
995impl IdFeeder {
996 pub const fn new(id: Id) -> Self {
997 IdFeeder(AtomicU64::new(id))
998 }
999
1000 pub fn next(&self) -> Id {
1001 self.0.fetch_add(1, Ordering::Relaxed)
1002 }
1003}