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