cadmus_core/view/
mod.rs

1//! Views are organized as a tree. A view might receive / send events and render itself.
2//!
3//! The z-level of the n-th child of a view is less or equal to the z-level of its n+1-th child.
4//!
5//! Events travel from the root to the leaves, only the leaf views will handle the root events, but
6//! any view can send events to its parent. From the events it receives from its children, a view
7//! resends the ones it doesn't handle to its own parent. Hence an event sent from a child might
8//! bubble up to the root. If it reaches the root without being captured by any view, then it will
9//! be written to the main event channel and will be sent to every leaf in one of the next loop
10//! iterations.
11
12pub 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
81// Border thicknesses in pixels, at 300 DPI.
82pub const THICKNESS_SMALL: f32 = 1.0;
83pub const THICKNESS_MEDIUM: f32 = 2.0;
84pub const THICKNESS_LARGE: f32 = 3.0;
85
86// Border radii in pixels, at 300 DPI.
87pub const BORDER_RADIUS_SMALL: f32 = 6.0;
88pub const BORDER_RADIUS_MEDIUM: f32 = 9.0;
89pub const BORDER_RADIUS_LARGE: f32 = 12.0;
90
91// Big and small bar heights in pixels, at 300 DPI.
92// On the *Aura ONE*, the height is exactly `2 * sb + 10 * bb`.
93pub 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// We start delivering events from the highest z-level to prevent views from capturing
169// gestures that occurred in higher views.
170// The consistency must also be ensured by the views: popups, for example, need to
171// capture any tap gesture with a touch point inside their rectangle.
172// A child can send events to the main channel through the *hub* or communicate with its parent through the *bus*.
173// A view that wants to render can write to the rendering queue.
174#[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// We render from bottom to top. For a view to render it has to either appear in `ids` or intersect
214// one of the rectangles in `bgs`. When we're about to render a view, if `wait` is true, we'll wait
215// for all the updates in `updating` that intersect with the view.
216#[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            // Most views can't render a subrectangle of themselves.
258            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    // Merge the contiguous zones to avoid having to schedule lots of small frambuffer updates.
267    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    /// Manages input focus state for focusable views like [`InputField`](input_field::InputField).
395    ///
396    /// This event controls which view currently receives keyboard input.
397    /// It is **not** a navigation event — use [`Event::Show`] to transition
398    /// between screens or display new UI components.
399    ///
400    /// # Variants
401    ///
402    /// - `Focus(Some(view_id))` — Grants focus to the view matching `view_id`.
403    ///   The focused view will receive [`Event::Keyboard`] events. Parent views
404    ///   typically use this to show the on-screen keyboard.
405    /// - `Focus(None)` — Clears focus from all views. Parent views typically
406    ///   use this to hide the on-screen keyboard.
407    ///
408    /// # Dispatch behavior
409    ///
410    /// Focus events are **broadcast**: [`InputField`](input_field::InputField)
411    /// returns `false` after handling this event so that all input fields in
412    /// the hierarchy can update their focused/unfocused state.
413    ///
414    /// # Sending
415    ///
416    /// Typically sent through the hub (`hub.send(...)`) by:
417    /// - An [`InputField`](input_field::InputField) when tapped while unfocused
418    /// - A parent view after building a screen that contains an input field
419    /// - A keyboard's hide method to clear focus
420    ///
421    /// # Example
422    ///
423    /// ```no_run
424    /// use cadmus_core::view::{Event, ViewId};
425    /// use cadmus_core::view::ota::OtaViewId;
426    ///
427    /// // Focus the PR input field (e.g. after building the PR input screen).
428    /// // Note: `hub` is provided by the application's event loop.
429    /// # let (hub, _) = std::sync::mpsc::channel();
430    /// hub.send(Event::Focus(Some(ViewId::Ota(OtaViewId::PrInput)))).ok();
431    ///
432    /// // Clear focus from all views.
433    /// hub.send(Event::Focus(None)).ok();
434    /// ```
435    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    // TODO(ogkevin): remove Toggle variant above and rename this to Toggle
459    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    /// The file chooser was closed.
499    ///  The `Option<PathBuf>` contains the selected path, if any.
500    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    // TODO(ogkevin): merge all these settings editor view IDs into one
544    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    // TODO(ogkevin): Make one entryId for settings editor
713    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}