Skip to main content

egui/memory/
mod.rs

1#![warn(missing_docs)] // Let's keep this file well-documented.` to memory.rs
2
3use std::num::NonZeroUsize;
4
5use ahash::{HashMap, HashSet};
6use epaint::emath::TSTransform;
7
8use crate::{
9    EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId,
10    ViewportIdMap, ViewportIdSet, area, vec2,
11};
12
13mod theme;
14pub use theme::{Theme, ThemePreference};
15
16// ----------------------------------------------------------------------------
17
18/// The data that egui persists between frames.
19///
20/// This includes window positions and sizes,
21/// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc.
22///
23/// If you want this to persist when closing your app, you should serialize [`Memory`] and store it.
24/// For this you need to enable the `persistence` feature.
25///
26/// If you want to store data for your widgets, you should look at [`Memory::data`]
27#[derive(Clone, Debug)]
28#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
29#[cfg_attr(feature = "persistence", serde(default))]
30pub struct Memory {
31    /// Global egui options.
32    pub options: Options,
33
34    /// This map stores some superficial state for all widgets with custom [`Id`]s.
35    ///
36    /// This includes storing whether a [`crate::CollapsingHeader`] is open, how far scrolled a
37    /// [`crate::ScrollArea`] is, where the cursor in a [`crate::TextEdit`] is, etc.
38    ///
39    /// This is NOT meant to store any important data. Store that in your own structures!
40    ///
41    /// Each read clones the data, so keep your values cheap to clone.
42    /// If you want to store a lot of data, you should wrap it in `Arc<Mutex<…>>` so it is cheap to clone.
43    ///
44    /// This will be saved between different program runs if you use the `persistence` feature.
45    ///
46    /// To store a state common for all your widgets (a singleton), use [`Id::NULL`] as the key.
47    pub data: crate::util::IdTypeMap,
48
49    // ------------------------------------------
50    /// Can be used to cache computations from one frame to another.
51    ///
52    /// This is for saving CPU time when you have something that may take 1-100ms to compute.
53    /// Very slow operations (>100ms) should instead be done async (i.e. in another thread)
54    /// so as not to lock the UI thread.
55    ///
56    /// ```
57    /// use egui::cache::{ComputerMut, FrameCache};
58    ///
59    /// #[derive(Default)]
60    /// struct CharCounter {}
61    /// impl ComputerMut<&str, usize> for CharCounter {
62    ///     fn compute(&mut self, s: &str) -> usize {
63    ///         s.chars().count() // you probably want to cache something more expensive than this
64    ///     }
65    /// }
66    /// type CharCountCache<'a> = FrameCache<usize, CharCounter>;
67    ///
68    /// # let mut ctx = egui::Context::default();
69    /// ctx.memory_mut(|mem| {
70    ///     let cache = mem.caches.cache::<CharCountCache<'_>>();
71    ///     assert_eq!(*cache.get("hello"), 5);
72    /// });
73    /// ```
74    #[cfg_attr(feature = "persistence", serde(skip))]
75    pub caches: crate::cache::CacheStorage,
76
77    // ------------------------------------------
78    /// new fonts that will be applied at the start of the next frame
79    #[cfg_attr(feature = "persistence", serde(skip))]
80    pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,
81
82    /// add new font that will be applied at the start of the next frame
83    #[cfg_attr(feature = "persistence", serde(skip))]
84    pub(crate) add_fonts: Vec<epaint::text::FontInsert>,
85
86    // Current active viewport
87    #[cfg_attr(feature = "persistence", serde(skip))]
88    pub(crate) viewport_id: ViewportId,
89
90    #[cfg_attr(feature = "persistence", serde(skip))]
91    everything_is_visible: bool,
92
93    /// Transforms per layer.
94    ///
95    /// Instead of using this directly, use:
96    /// * [`crate::Context::set_transform_layer`]
97    /// * [`crate::Context::layer_transform_to_global`]
98    /// * [`crate::Context::layer_transform_from_global`]
99    pub to_global: HashMap<LayerId, TSTransform>,
100
101    // -------------------------------------------------
102    // Per-viewport:
103    areas: ViewportIdMap<Areas>,
104
105    #[cfg_attr(feature = "persistence", serde(skip))]
106    pub(crate) interactions: ViewportIdMap<InteractionState>,
107
108    #[cfg_attr(feature = "persistence", serde(skip))]
109    pub(crate) focus: ViewportIdMap<Focus>,
110
111    /// Which popup-window is open on a viewport (if any)?
112    /// Could be a combo box, color picker, menu, etc.
113    /// Optionally stores the position of the popup (usually this would be the position where
114    /// the user clicked).
115    /// If position is [`None`], the popup position will be calculated based on some configuration
116    /// (e.g. relative to some other widget).
117    #[cfg_attr(feature = "persistence", serde(skip))]
118    popups: ViewportIdMap<OpenPopup>,
119}
120
121impl Default for Memory {
122    fn default() -> Self {
123        let mut slf = Self {
124            options: Default::default(),
125            data: Default::default(),
126            caches: Default::default(),
127            new_font_definitions: Default::default(),
128            interactions: Default::default(),
129            focus: Default::default(),
130            viewport_id: Default::default(),
131            areas: Default::default(),
132            to_global: Default::default(),
133            popups: Default::default(),
134            everything_is_visible: Default::default(),
135            add_fonts: Default::default(),
136        };
137        slf.interactions.entry(slf.viewport_id).or_default();
138        slf.areas.entry(slf.viewport_id).or_default();
139        slf
140    }
141}
142
143/// A direction in which to move the keyboard focus.
144#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
145pub enum FocusDirection {
146    /// Select the widget closest above the current focused widget.
147    Up,
148
149    /// Select the widget to the right of the current focused widget.
150    Right,
151
152    /// Select the widget below the current focused widget.
153    Down,
154
155    /// Select the widget to the left of the current focused widget.
156    Left,
157
158    /// Select the previous widget that had focus.
159    Previous,
160
161    /// Select the next widget that wants focus.
162    Next,
163
164    /// Don't change focus.
165    #[default]
166    None,
167}
168
169impl FocusDirection {
170    fn is_cardinal(&self) -> bool {
171        match self {
172            Self::Up | Self::Right | Self::Down | Self::Left => true,
173
174            Self::Previous | Self::Next | Self::None => false,
175        }
176    }
177}
178
179// ----------------------------------------------------------------------------
180
181/// Some global options that you can read and write.
182///
183/// See also [`crate::style::DebugOptions`].
184#[derive(Clone, Debug, PartialEq)]
185#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
186#[cfg_attr(feature = "serde", serde(default))]
187pub struct Options {
188    /// The default style for new [`Ui`](crate::Ui):s in dark mode.
189    #[cfg_attr(feature = "serde", serde(skip))]
190    pub dark_style: std::sync::Arc<Style>,
191
192    /// The default style for new [`Ui`](crate::Ui):s in light mode.
193    #[cfg_attr(feature = "serde", serde(skip))]
194    pub light_style: std::sync::Arc<Style>,
195
196    /// Preference for selection between dark and light [`crate::Context::style`]
197    /// as the active style used by all subsequent windows, panels, etc.
198    ///
199    /// Default: `ThemePreference::System`.
200    pub theme_preference: ThemePreference,
201
202    /// Which theme to use in case [`Self::theme_preference`] is [`ThemePreference::System`]
203    /// and egui fails to detect the system theme.
204    ///
205    /// Default: [`crate::Theme::Dark`].
206    pub fallback_theme: Theme,
207
208    /// The current system theme, used to choose between
209    /// dark and light style in case [`Self::theme_preference`] is [`ThemePreference::System`].
210    #[cfg_attr(feature = "serde", serde(skip))]
211    pub(crate) system_theme: Option<Theme>,
212
213    /// Global zoom factor of the UI.
214    ///
215    /// This is used to calculate the `pixels_per_point`
216    /// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`.
217    ///
218    /// The default is 1.0. Increase it to make all UI elements larger.
219    ///
220    /// You should call [`crate::Context::set_zoom_factor`]
221    /// instead of modifying this directly!
222    pub zoom_factor: f32,
223
224    /// If `true`, egui will change the scale of the ui ([`crate::Context::zoom_factor`]) when the user
225    /// presses Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
226    ///
227    /// This is `true` by default.
228    ///
229    /// On the web-backend of `eframe` this is set to false by default,
230    /// so that the zoom shortcuts are handled exclusively by the browser,
231    /// which will change the `native_pixels_per_point` (`devicePixelRatio`).
232    /// You can still zoom egui independently by calling [`crate::Context::set_zoom_factor`],
233    /// which will be applied on top of the browsers global zoom.
234    #[cfg_attr(feature = "serde", serde(skip))]
235    pub zoom_with_keyboard: bool,
236
237    /// Keyboard shortcuts to close the application.
238    ///
239    /// Pressing any of these will send [`crate::ViewportCommand::Close`]
240    /// to the root viewport.
241    ///
242    /// Defaults to `Cmd-Q` (which is Ctrl-Q on Linux/Windows, Cmd-Q on Mac).
243    /// Set to empty to disable.
244    #[cfg_attr(feature = "serde", serde(skip))]
245    pub quit_shortcuts: Vec<crate::KeyboardShortcut>,
246
247    /// Controls the tessellator.
248    pub tessellation_options: epaint::TessellationOptions,
249
250    /// If any widget moves or changes id, repaint everything.
251    ///
252    /// It is recommended you keep this OFF, as it may
253    /// lead to endless repaints for an unknown reason. See
254    /// (<https://github.com/rerun-io/rerun/issues/5018>).
255    pub repaint_on_widget_change: bool,
256
257    /// Maximum number of passes to run in one frame.
258    ///
259    /// Set to `1` for pure single-pass immediate mode.
260    /// Set to something larger than `1` to allow multi-pass when needed.
261    ///
262    /// Default is `2`. This means sometimes a frame will cost twice as much,
263    /// but usually only rarely (e.g. when showing a new panel for the first time).
264    ///
265    /// egui will usually only ever run one pass, even if `max_passes` is large.
266    ///
267    /// If this is `1`, [`crate::Context::request_discard`] will be ignored.
268    ///
269    /// Multi-pass is supported by [`crate::Context::run`].
270    ///
271    /// See [`crate::Context::request_discard`] for more.
272    pub max_passes: NonZeroUsize,
273
274    /// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud.
275    ///
276    /// The only change to egui is that labels can be focused by pressing tab.
277    ///
278    /// Screen readers are an experimental feature of egui, and not supported on all platforms.
279    /// `eframe` only supports it on web.
280    ///
281    /// Consider using [AccessKit](https://github.com/AccessKit/accesskit) instead,
282    /// which is supported by `eframe`.
283    pub screen_reader: bool,
284
285    /// Check reusing of [`Id`]s, and show a visual warning on screen when one is found.
286    ///
287    /// By default this is `true` in debug builds.
288    pub warn_on_id_clash: bool,
289
290    /// Options related to input state handling.
291    pub input_options: crate::input_state::InputOptions,
292
293    /// If `true`, `egui` will discard the loaded image data after
294    /// the texture is loaded onto the GPU to reduce memory usage.
295    ///
296    /// In modern GPU rendering, the texture data is not required after the texture is loaded.
297    ///
298    /// This is beneficial when using a large number or resolution of images and there is no need to
299    /// retain the image data, potentially saving a significant amount of memory.
300    ///
301    /// The drawback is that it becomes impossible to serialize the loaded images or render in non-GPU systems.
302    ///
303    /// Default is `false`.
304    pub reduce_texture_memory: bool,
305}
306
307impl Default for Options {
308    fn default() -> Self {
309        Self {
310            dark_style: std::sync::Arc::new(Theme::Dark.default_style()),
311            light_style: std::sync::Arc::new(Theme::Light.default_style()),
312            theme_preference: Default::default(),
313            fallback_theme: Theme::Dark,
314            system_theme: None,
315            zoom_factor: 1.0,
316            zoom_with_keyboard: true,
317            quit_shortcuts: vec![crate::KeyboardShortcut::new(
318                crate::Modifiers::COMMAND,
319                crate::Key::Q,
320            )],
321            tessellation_options: Default::default(),
322            repaint_on_widget_change: false,
323
324            #[expect(clippy::unwrap_used)]
325            max_passes: NonZeroUsize::new(2).unwrap(),
326            screen_reader: false,
327            warn_on_id_clash: cfg!(debug_assertions),
328
329            // Input:
330            input_options: Default::default(),
331            reduce_texture_memory: false,
332        }
333    }
334}
335
336impl Options {
337    // Needs to be pub because we need to set the system_theme early in the eframe glow renderer.
338    #[doc(hidden)]
339    pub fn begin_pass(&mut self, new_raw_input: &RawInput) {
340        self.system_theme = new_raw_input.system_theme;
341    }
342
343    /// The currently active theme (may depend on the system theme).
344    pub(crate) fn theme(&self) -> Theme {
345        match self.theme_preference {
346            ThemePreference::Dark => Theme::Dark,
347            ThemePreference::Light => Theme::Light,
348            ThemePreference::System => self.system_theme.unwrap_or(self.fallback_theme),
349        }
350    }
351
352    pub(crate) fn style(&self) -> &std::sync::Arc<Style> {
353        match self.theme() {
354            Theme::Dark => &self.dark_style,
355            Theme::Light => &self.light_style,
356        }
357    }
358
359    pub(crate) fn style_mut(&mut self) -> &mut std::sync::Arc<Style> {
360        match self.theme() {
361            Theme::Dark => &mut self.dark_style,
362            Theme::Light => &mut self.light_style,
363        }
364    }
365}
366
367impl Options {
368    /// Show the options in the ui.
369    pub fn ui(&mut self, ui: &mut crate::Ui) {
370        let theme = self.theme();
371
372        let Self {
373            dark_style, // covered above
374            light_style,
375            theme_preference,
376            fallback_theme: _,
377            system_theme: _,
378            zoom_factor,
379            zoom_with_keyboard,
380            quit_shortcuts: _, // not shown in ui
381            tessellation_options,
382            repaint_on_widget_change,
383            max_passes,
384            screen_reader: _, // needs to come from the integration
385            warn_on_id_clash,
386            input_options,
387            reduce_texture_memory,
388        } = self;
389
390        use crate::Widget as _;
391        use crate::containers::CollapsingHeader;
392
393        CollapsingHeader::new("⚙ Options")
394            .default_open(false)
395            .show(ui, |ui| {
396                ui.horizontal(|ui| {
397                    ui.label("Max passes:");
398                    ui.add(crate::DragValue::new(max_passes).range(0..=10));
399                });
400
401                ui.checkbox(
402                    repaint_on_widget_change,
403                    "Repaint if any widget moves or changes id",
404                );
405
406                ui.horizontal(|ui| {
407                    ui.label("Zoom factor:");
408                    ui.add(crate::DragValue::new(zoom_factor).range(0.10..=10.0));
409                });
410
411                ui.checkbox(
412                    zoom_with_keyboard,
413                    "Zoom with keyboard (Cmd +, Cmd -, Cmd 0)",
414                );
415
416                ui.checkbox(warn_on_id_clash, "Warn if two widgets have the same Id");
417
418                ui.checkbox(reduce_texture_memory, "Reduce texture memory");
419            });
420
421        CollapsingHeader::new("🎑 Style")
422            .default_open(true)
423            .show(ui, |ui| {
424                theme_preference.radio_buttons(ui);
425
426                let style = std::sync::Arc::make_mut(match theme {
427                    Theme::Dark => dark_style,
428                    Theme::Light => light_style,
429                });
430                style.ui(ui);
431            });
432
433        CollapsingHeader::new("✒ Painting")
434            .default_open(false)
435            .show(ui, |ui| {
436                tessellation_options.ui(ui);
437                ui.vertical_centered(|ui| {
438                    crate::reset_button(ui, tessellation_options, "Reset paint settings");
439                });
440            });
441
442        CollapsingHeader::new("🖱 Input")
443            .default_open(false)
444            .show(ui, |ui| {
445                input_options.ui(ui);
446            });
447
448        ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all"));
449    }
450}
451
452// ----------------------------------------------------------------------------
453
454/// The state of the interaction in egui,
455/// i.e. what is being dragged.
456///
457/// Say there is a button in a scroll area.
458/// If the user clicks the button, the button should click.
459/// If the user drags the button we should scroll the scroll area.
460/// Therefore, when the mouse is pressed, we register both the button
461/// and the scroll area (as `click_id`/`drag_id`).
462/// If the user releases the button without moving the mouse, we register it as a click on `click_id`.
463/// If the cursor moves too much, we clear the `click_id` and start passing move events to `drag_id`.
464#[derive(Clone, Debug, Default)]
465pub(crate) struct InteractionState {
466    /// A widget interested in clicks that has a mouse press on it.
467    pub potential_click_id: Option<Id>,
468
469    /// A widget interested in drags that has a mouse press on it.
470    ///
471    /// Note that this is set as soon as the mouse is pressed,
472    /// so the widget may not yet be marked as "dragged"
473    /// as that can only happen after the mouse has moved a bit
474    /// (at least if the widget is interesated in both clicks and drags).
475    pub potential_drag_id: Option<Id>,
476}
477
478/// Keeps tracks of what widget has keyboard focus
479#[derive(Clone, Debug, Default)]
480pub(crate) struct Focus {
481    /// The widget with keyboard focus (i.e. a text input field).
482    focused_widget: Option<FocusWidget>,
483
484    /// The ID of a widget that had keyboard focus during the previous frame.
485    id_previous_frame: Option<Id>,
486
487    /// The ID of a widget to give the focus to in the next frame.
488    id_next_frame: Option<Id>,
489
490    id_requested_by_accesskit: Option<accesskit::NodeId>,
491
492    /// If set, the next widget that is interested in focus will automatically get it.
493    /// Probably because the user pressed Tab.
494    give_to_next: bool,
495
496    /// The last widget interested in focus.
497    last_interested: Option<Id>,
498
499    /// Set when looking for widget with navigational keys like arrows, tab, shift+tab.
500    focus_direction: FocusDirection,
501
502    /// The top-most modal layer from the previous frame.
503    top_modal_layer: Option<LayerId>,
504
505    /// The top-most modal layer from the current frame.
506    top_modal_layer_current_frame: Option<LayerId>,
507
508    /// A cache of widget IDs that are interested in focus with their corresponding rectangles.
509    focus_widgets_cache: IdMap<Rect>,
510}
511
512/// The widget with focus.
513#[derive(Clone, Copy, Debug)]
514struct FocusWidget {
515    pub id: Id,
516    pub filter: EventFilter,
517}
518
519impl FocusWidget {
520    pub fn new(id: Id) -> Self {
521        Self {
522            id,
523            filter: Default::default(),
524        }
525    }
526}
527
528impl InteractionState {
529    /// Are we currently clicking or dragging an egui widget?
530    pub fn is_using_pointer(&self) -> bool {
531        self.potential_click_id.is_some() || self.potential_drag_id.is_some()
532    }
533}
534
535impl Focus {
536    /// Which widget currently has keyboard focus?
537    pub fn focused(&self) -> Option<Id> {
538        self.focused_widget.as_ref().map(|w| w.id)
539    }
540
541    fn begin_pass(&mut self, new_input: &crate::data::input::RawInput) {
542        self.id_previous_frame = self.focused();
543        if let Some(id) = self.id_next_frame.take() {
544            self.focused_widget = Some(FocusWidget::new(id));
545        }
546        let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
547
548        self.id_requested_by_accesskit = None;
549
550        self.focus_direction = FocusDirection::None;
551
552        for event in &new_input.events {
553            if !event_filter.matches(event)
554                && let crate::Event::Key {
555                    key,
556                    pressed: true,
557                    modifiers,
558                    ..
559                } = event
560                && let Some(cardinality) = match key {
561                    crate::Key::ArrowUp if !modifiers.any() => Some(FocusDirection::Up),
562                    crate::Key::ArrowRight if !modifiers.any() => Some(FocusDirection::Right),
563                    crate::Key::ArrowDown if !modifiers.any() => Some(FocusDirection::Down),
564                    crate::Key::ArrowLeft if !modifiers.any() => Some(FocusDirection::Left),
565
566                    crate::Key::Tab if !modifiers.any() => Some(FocusDirection::Next),
567                    crate::Key::Tab if modifiers.shift_only() => Some(FocusDirection::Previous),
568
569                    crate::Key::Escape if !modifiers.any() => {
570                        self.focused_widget = None;
571                        Some(FocusDirection::None)
572                    }
573
574                    _ => None,
575                }
576            {
577                self.focus_direction = cardinality;
578            }
579
580            if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
581                action: accesskit::Action::Focus,
582                target_node,
583                target_tree,
584                data: None,
585            }) = event
586                && *target_tree == accesskit::TreeId::ROOT
587            {
588                self.id_requested_by_accesskit = Some(*target_node);
589            }
590        }
591    }
592
593    pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
594        if self.focus_direction.is_cardinal()
595            && let Some(found_widget) = self.find_widget_in_direction(used_ids)
596        {
597            self.focused_widget = Some(FocusWidget::new(found_widget));
598        }
599
600        if let Some(focused_widget) = self.focused_widget {
601            // Allow calling `request_focus` one frame and not using it until next frame
602            let recently_gained_focus = self.id_previous_frame != Some(focused_widget.id);
603
604            if !recently_gained_focus && !used_ids.contains_key(&focused_widget.id) {
605                // Dead-mans-switch: the widget with focus has disappeared!
606                self.focused_widget = None;
607            }
608        }
609
610        self.top_modal_layer = self.top_modal_layer_current_frame.take();
611    }
612
613    pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
614        self.id_previous_frame == Some(id)
615    }
616
617    fn interested_in_focus(&mut self, id: Id) {
618        if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
619            self.focused_widget = Some(FocusWidget::new(id));
620            self.id_requested_by_accesskit = None;
621            self.give_to_next = false;
622            self.reset_focus();
623        }
624
625        // The rect is updated at the end of the frame.
626        self.focus_widgets_cache
627            .entry(id)
628            .or_insert(Rect::EVERYTHING);
629
630        if self.give_to_next && !self.had_focus_last_frame(id) {
631            self.focused_widget = Some(FocusWidget::new(id));
632            self.give_to_next = false;
633        } else if self.focused() == Some(id) {
634            if self.focus_direction == FocusDirection::Next {
635                self.focused_widget = None;
636                self.give_to_next = true;
637                self.reset_focus();
638            } else if self.focus_direction == FocusDirection::Previous {
639                self.id_next_frame = self.last_interested; // frame-delay so gained_focus works
640                self.reset_focus();
641            }
642        } else if self.focus_direction == FocusDirection::Next
643            && self.focused_widget.is_none()
644            && !self.give_to_next
645        {
646            // nothing has focus and the user pressed tab - give focus to the first widgets that wants it:
647            self.focused_widget = Some(FocusWidget::new(id));
648            self.reset_focus();
649        } else if self.focus_direction == FocusDirection::Previous
650            && self.focused_widget.is_none()
651            && !self.give_to_next
652        {
653            // nothing has focus and the user pressed Shift+Tab - give focus to the last widgets that wants it:
654            self.focused_widget = self.last_interested.map(FocusWidget::new);
655            self.reset_focus();
656        }
657
658        self.last_interested = Some(id);
659    }
660
661    fn set_modal_layer(&mut self, layer_id: LayerId) {
662        self.top_modal_layer_current_frame = Some(layer_id);
663    }
664
665    pub(crate) fn top_modal_layer(&self) -> Option<LayerId> {
666        self.top_modal_layer
667    }
668
669    fn reset_focus(&mut self) {
670        self.focus_direction = FocusDirection::None;
671    }
672
673    fn find_widget_in_direction(&mut self, new_rects: &IdMap<Rect>) -> Option<Id> {
674        // NOTE: `new_rects` here include some widgets _not_ interested in focus.
675
676        /// * negative if `a` is left of `b`
677        /// * positive if `a` is right of `b`
678        /// * zero if the ranges overlap significantly
679        fn range_diff(a: Rangef, b: Rangef) -> f32 {
680            let has_significant_overlap = a.intersection(b).span() >= 0.5 * b.span().min(a.span());
681            if has_significant_overlap {
682                0.0
683            } else {
684                a.center() - b.center()
685            }
686        }
687
688        let current_focused = self.focused_widget?;
689
690        // In what direction we are looking for the next widget.
691        let search_direction = match self.focus_direction {
692            FocusDirection::Up => Vec2::UP,
693            FocusDirection::Right => Vec2::RIGHT,
694            FocusDirection::Down => Vec2::DOWN,
695            FocusDirection::Left => Vec2::LEFT,
696            _ => {
697                return None;
698            }
699        };
700
701        // Update cache with new rects
702        self.focus_widgets_cache.retain(|id, old_rect| {
703            if let Some(new_rect) = new_rects.get(id) {
704                *old_rect = *new_rect;
705                true // Keep the item
706            } else {
707                false // Remove the item
708            }
709        });
710
711        let current_rect = self.focus_widgets_cache.get(&current_focused.id)?;
712
713        let mut best_score = f32::INFINITY;
714        let mut best_id = None;
715
716        // iteration order should only matter in case of a tie, and that should be very rare
717        #[expect(clippy::iter_over_hash_type)]
718        for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
719            if *candidate_id == current_focused.id {
720                continue;
721            }
722
723            // There is a lot of room for improvement here.
724            let to_candidate = vec2(
725                range_diff(candidate_rect.x_range(), current_rect.x_range()),
726                range_diff(candidate_rect.y_range(), current_rect.y_range()),
727            );
728
729            let acos_angle = to_candidate.normalized().dot(search_direction);
730
731            // Only interested in widgets that fall in a 90° cone (±45°)
732            // of the search direction.
733            let is_in_search_cone = 0.5_f32.sqrt() <= acos_angle;
734            if is_in_search_cone {
735                let distance = to_candidate.length();
736
737                // There is a lot of room for improvement here.
738                let score = distance / (acos_angle * acos_angle);
739
740                if score < best_score {
741                    best_score = score;
742                    best_id = Some(*candidate_id);
743                }
744            }
745        }
746
747        best_id
748    }
749}
750
751impl Memory {
752    pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
753        profiling::function_scope!();
754
755        self.viewport_id = new_raw_input.viewport_id;
756
757        // Cleanup
758        self.interactions.retain(|id, _| viewports.contains(id));
759        self.areas.retain(|id, _| viewports.contains(id));
760        self.popups.retain(|id, _| viewports.contains(id));
761
762        self.areas.entry(self.viewport_id).or_default();
763
764        // self.interactions  is handled elsewhere
765
766        self.options.begin_pass(new_raw_input);
767
768        self.focus
769            .entry(self.viewport_id)
770            .or_default()
771            .begin_pass(new_raw_input);
772    }
773
774    pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
775        self.caches.update();
776        self.areas_mut().end_pass();
777        self.focus_mut().end_pass(used_ids);
778
779        // Clean up abandoned popups.
780        if let Some(popup) = self.popups.get_mut(&self.viewport_id) {
781            if popup.open_this_frame {
782                popup.open_this_frame = false;
783            } else {
784                self.popups.remove(&self.viewport_id);
785            }
786        }
787    }
788
789    pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
790        self.viewport_id = viewport_id;
791    }
792
793    /// Access memory of the [`Area`](crate::containers::area::Area)s, such as `Window`s.
794    pub fn areas(&self) -> &Areas {
795        self.areas
796            .get(&self.viewport_id)
797            .expect("Memory broken: no area for the current viewport")
798    }
799
800    /// Access memory of the [`Area`](crate::containers::area::Area)s, such as `Window`s.
801    pub fn areas_mut(&mut self) -> &mut Areas {
802        self.areas.entry(self.viewport_id).or_default()
803    }
804
805    /// Top-most layer at the given position.
806    pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
807        let layer_id = self.areas().layer_id_at(pos, &self.to_global)?;
808        if self.is_above_modal_layer(layer_id) {
809            Some(layer_id)
810        } else {
811            self.top_modal_layer()
812        }
813    }
814
815    /// The currently set transform of a layer.
816    #[deprecated = "Use `Context::layer_transform_to_global` instead"]
817    pub fn layer_transforms(&self, layer_id: LayerId) -> Option<TSTransform> {
818        self.to_global.get(&layer_id).copied()
819    }
820
821    /// An iterator over all layers. Back-to-front, top is last.
822    pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
823        self.areas().order().iter().copied()
824    }
825
826    /// Check if the layer had focus last frame.
827    /// returns `true` if the layer had focus last frame, but not this one.
828    pub fn had_focus_last_frame(&self, id: Id) -> bool {
829        self.focus().and_then(|f| f.id_previous_frame) == Some(id)
830    }
831
832    /// Check if the layer lost focus last frame.
833    /// returns `true` if the layer lost focus last frame, but not this one.
834    pub(crate) fn lost_focus(&self, id: Id) -> bool {
835        self.had_focus_last_frame(id) && !self.has_focus(id)
836    }
837
838    /// Check if the layer gained focus this frame.
839    /// returns `true` if the layer gained focus this frame, but not last one.
840    pub(crate) fn gained_focus(&self, id: Id) -> bool {
841        !self.had_focus_last_frame(id) && self.has_focus(id)
842    }
843
844    /// Does this widget have keyboard focus?
845    ///
846    /// This function does not consider whether the UI as a whole (e.g. window)
847    /// has the keyboard focus. That makes this function suitable for deciding
848    /// widget state that should not be disrupted if the user moves away from
849    /// the window and back.
850    #[inline(always)]
851    pub fn has_focus(&self, id: Id) -> bool {
852        self.focused() == Some(id)
853    }
854
855    /// Which widget has keyboard focus?
856    pub fn focused(&self) -> Option<Id> {
857        self.focus()?.focused()
858    }
859
860    /// Set an event filter for a widget.
861    ///
862    /// This allows you to control whether the widget will loose focus
863    /// when the user presses tab, arrow keys, or escape.
864    ///
865    /// You must first give focus to the widget before calling this.
866    pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
867        if self.had_focus_last_frame(id)
868            && self.has_focus(id)
869            && let Some(focused) = &mut self.focus_mut().focused_widget
870            && focused.id == id
871        {
872            focused.filter = event_filter;
873        }
874    }
875
876    /// Give keyboard focus to a specific widget.
877    /// See also [`crate::Response::request_focus`].
878    #[inline(always)]
879    pub fn request_focus(&mut self, id: Id) {
880        self.focus_mut().focused_widget = Some(FocusWidget::new(id));
881    }
882
883    /// Surrender keyboard focus for a specific widget.
884    /// See also [`crate::Response::surrender_focus`].
885    #[inline(always)]
886    pub fn surrender_focus(&mut self, id: Id) {
887        let focus = self.focus_mut();
888        if focus.focused() == Some(id) {
889            focus.focused_widget = None;
890        }
891    }
892
893    /// Move keyboard focus in a specific direction.
894    pub fn move_focus(&mut self, direction: FocusDirection) {
895        self.focus_mut().focus_direction = direction;
896    }
897
898    /// Returns true if
899    /// - this layer is the top-most modal layer or above it
900    /// - there is no modal layer
901    pub fn is_above_modal_layer(&self, layer_id: LayerId) -> bool {
902        if let Some(modal_layer) = self.focus().and_then(|f| f.top_modal_layer) {
903            matches!(
904                self.areas().compare_order(layer_id, modal_layer),
905                std::cmp::Ordering::Equal | std::cmp::Ordering::Greater
906            )
907        } else {
908            true
909        }
910    }
911
912    /// Does this layer allow interaction?
913    /// Returns true if
914    ///  - the layer is not behind a modal layer
915    ///  - the [`Order`] allows interaction
916    pub fn allows_interaction(&self, layer_id: LayerId) -> bool {
917        let is_above_modal_layer = self.is_above_modal_layer(layer_id);
918        let ordering_allows_interaction = layer_id.order.allow_interaction();
919        is_above_modal_layer && ordering_allows_interaction
920    }
921
922    /// Register this widget as being interested in getting keyboard focus.
923    /// This will allow the user to select it with tab and shift-tab.
924    /// This is normally done automatically when handling interactions,
925    /// but it is sometimes useful to pre-register interest in focus,
926    /// e.g. before deciding which type of underlying widget to use,
927    /// as in the [`crate::DragValue`] widget, so a widget can be focused
928    /// and rendered correctly in a single frame.
929    ///
930    /// Pass in the `layer_id` of the layer that the widget is in.
931    #[inline(always)]
932    pub fn interested_in_focus(&mut self, id: Id, layer_id: LayerId) {
933        if !self.allows_interaction(layer_id) {
934            return;
935        }
936        self.focus_mut().interested_in_focus(id);
937    }
938
939    /// Limit focus to widgets on the given layer and above.
940    /// If this is called multiple times per frame, the top layer wins.
941    pub fn set_modal_layer(&mut self, layer_id: LayerId) {
942        if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame)
943            && matches!(
944                self.areas().compare_order(layer_id, current),
945                std::cmp::Ordering::Less
946            )
947        {
948            return;
949        }
950
951        self.focus_mut().set_modal_layer(layer_id);
952    }
953
954    /// Get the top modal layer (from the previous frame).
955    pub fn top_modal_layer(&self) -> Option<LayerId> {
956        self.focus()?.top_modal_layer()
957    }
958
959    /// Stop editing the active [`TextEdit`](crate::TextEdit) (if any).
960    #[inline(always)]
961    pub fn stop_text_input(&mut self) {
962        self.focus_mut().focused_widget = None;
963    }
964
965    /// Forget window positions, sizes etc.
966    /// Can be used to auto-layout windows.
967    pub fn reset_areas(&mut self) {
968        #[expect(clippy::iter_over_hash_type)]
969        for area in self.areas.values_mut() {
970            *area = Default::default();
971        }
972    }
973
974    /// Obtain the previous rectangle of an area.
975    pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
976        self.areas().get(id.into()).map(|state| state.rect())
977    }
978
979    pub(crate) fn interaction(&self) -> &InteractionState {
980        self.interactions
981            .get(&self.viewport_id)
982            .expect("Failed to get interaction")
983    }
984
985    pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
986        self.interactions.entry(self.viewport_id).or_default()
987    }
988
989    pub(crate) fn focus(&self) -> Option<&Focus> {
990        self.focus.get(&self.viewport_id)
991    }
992
993    pub(crate) fn focus_mut(&mut self) -> &mut Focus {
994        self.focus.entry(self.viewport_id).or_default()
995    }
996}
997
998/// State of an open popup.
999#[derive(Clone, Copy, Debug)]
1000struct OpenPopup {
1001    /// Id of the popup.
1002    id: Id,
1003
1004    /// Optional position of the popup.
1005    pos: Option<Pos2>,
1006
1007    /// Whether this popup was still open this frame. Otherwise it's considered abandoned and `Memory::popup` will be cleared.
1008    open_this_frame: bool,
1009}
1010
1011impl OpenPopup {
1012    /// Create a new `OpenPopup`.
1013    fn new(id: Id, pos: Option<Pos2>) -> Self {
1014        Self {
1015            id,
1016            pos,
1017            open_this_frame: true,
1018        }
1019    }
1020}
1021
1022/// ## Deprecated popup API
1023/// Use [`crate::Popup`] instead.
1024impl Memory {
1025    /// Is the given popup open?
1026    #[deprecated = "Use Popup::is_id_open instead"]
1027    pub fn is_popup_open(&self, popup_id: Id) -> bool {
1028        self.popups
1029            .get(&self.viewport_id)
1030            .is_some_and(|state| state.id == popup_id)
1031            || self.everything_is_visible()
1032    }
1033
1034    /// Is any popup open?
1035    #[deprecated = "Use Popup::is_any_open instead"]
1036    pub fn any_popup_open(&self) -> bool {
1037        self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
1038    }
1039
1040    /// Open the given popup and close all others.
1041    ///
1042    /// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open.
1043    #[deprecated = "Use Popup::open_id instead"]
1044    pub fn open_popup(&mut self, popup_id: Id) {
1045        self.popups
1046            .insert(self.viewport_id, OpenPopup::new(popup_id, None));
1047    }
1048
1049    /// Popups must call this every frame while open.
1050    ///
1051    /// This is needed because in some cases popups can go away without `close_popup` being
1052    /// called. For example, when a context menu is open and the underlying widget stops
1053    /// being rendered.
1054    #[deprecated = "Use Popup::show instead"]
1055    pub fn keep_popup_open(&mut self, popup_id: Id) {
1056        if let Some(state) = self.popups.get_mut(&self.viewport_id)
1057            && state.id == popup_id
1058        {
1059            state.open_this_frame = true;
1060        }
1061    }
1062
1063    /// Open the popup and remember its position.
1064    #[deprecated = "Use Popup with PopupAnchor::Position instead"]
1065    pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
1066        self.popups
1067            .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
1068    }
1069
1070    /// Get the position for this popup.
1071    #[deprecated = "Use Popup::position_of_id instead"]
1072    pub fn popup_position(&self, id: Id) -> Option<Pos2> {
1073        let state = self.popups.get(&self.viewport_id)?;
1074        if state.id == id { state.pos } else { None }
1075    }
1076
1077    /// Close any currently open popup.
1078    #[deprecated = "Use Popup::close_all instead"]
1079    pub fn close_all_popups(&mut self) {
1080        self.popups.clear();
1081    }
1082
1083    /// Close the given popup, if it is open.
1084    ///
1085    /// See also [`Self::close_all_popups`] if you want to close any / all currently open popups.
1086    #[deprecated = "Use Popup::close_id instead"]
1087    pub fn close_popup(&mut self, popup_id: Id) {
1088        #[expect(deprecated)]
1089        if self.is_popup_open(popup_id) {
1090            self.popups.remove(&self.viewport_id);
1091        }
1092    }
1093
1094    /// Toggle the given popup between closed and open.
1095    ///
1096    /// Note: At most, only one popup can be open at a time.
1097    #[deprecated = "Use Popup::toggle_id instead"]
1098    pub fn toggle_popup(&mut self, popup_id: Id) {
1099        #[expect(deprecated)]
1100        if self.is_popup_open(popup_id) {
1101            self.close_popup(popup_id);
1102        } else {
1103            self.open_popup(popup_id);
1104        }
1105    }
1106}
1107
1108impl Memory {
1109    /// If true, all windows, menus, tooltips, etc., will be visible at once.
1110    ///
1111    /// This is useful for testing, benchmarking, pre-caching, etc.
1112    ///
1113    /// Experimental feature!
1114    #[inline(always)]
1115    pub fn everything_is_visible(&self) -> bool {
1116        self.everything_is_visible
1117    }
1118
1119    /// If true, all windows, menus, tooltips etc are to be visible at once.
1120    ///
1121    /// This is useful for testing, benchmarking, pre-caching, etc.
1122    ///
1123    /// Experimental feature!
1124    pub fn set_everything_is_visible(&mut self, value: bool) {
1125        self.everything_is_visible = value;
1126    }
1127}
1128
1129// ----------------------------------------------------------------------------
1130
1131/// Map containing the index of each layer in the order list, for quick lookups.
1132type OrderMap = HashMap<LayerId, usize>;
1133
1134/// Keeps track of [`Area`](crate::containers::area::Area)s, which are free-floating [`Ui`](crate::Ui)s.
1135/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`].
1136#[derive(Clone, Debug, Default)]
1137#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1138#[cfg_attr(feature = "serde", serde(default))]
1139pub struct Areas {
1140    areas: IdMap<area::AreaState>,
1141
1142    visible_areas_last_frame: ahash::HashSet<LayerId>,
1143    visible_areas_current_frame: ahash::HashSet<LayerId>,
1144
1145    // ----------------------------
1146    // Everything below this is general to all layers, not just areas.
1147    // TODO(emilk): move this to a separate struct.
1148    /// Back-to-front,  top is last.
1149    order: Vec<LayerId>,
1150
1151    /// Inverse of [`Self::order`], calculated at the end of the frame.
1152    order_map: OrderMap,
1153
1154    /// When an area wants to be on top, it is assigned here.
1155    /// This is used to reorder the layers at the end of the frame.
1156    /// If several layers want to be on top, they will keep their relative order.
1157    /// This means closing three windows and then reopening them all in one frame
1158    /// results in them being sent to the top and keeping their previous internal order.
1159    wants_to_be_on_top: ahash::HashSet<LayerId>,
1160
1161    /// The sublayers that each layer has.
1162    ///
1163    /// The parent sublayer is moved directly above the child sublayers in the ordering.
1164    sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
1165}
1166
1167impl Areas {
1168    pub(crate) fn count(&self) -> usize {
1169        self.areas.len()
1170    }
1171
1172    pub(crate) fn get(&self, id: Id) -> Option<&area::AreaState> {
1173        self.areas.get(&id)
1174    }
1175
1176    /// All layers back-to-front, top is last.
1177    pub(crate) fn order(&self) -> &[LayerId] {
1178        &self.order
1179    }
1180
1181    /// Compare the order of two layers, based on the order list from last frame.
1182    ///
1183    /// May return [`std::cmp::Ordering::Equal`] if the layers are not in the order list.
1184    pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
1185        // Sort by layer `order` first and use `order_map` to resolve disputes.
1186        // If `order_map` only contains one layer ID, then the other one will be
1187        // lower because `None < Some(x)`.
1188        match a.order.cmp(&b.order) {
1189            std::cmp::Ordering::Equal => self.order_map.get(&a).cmp(&self.order_map.get(&b)),
1190            cmp => cmp,
1191        }
1192    }
1193
1194    pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
1195        self.visible_areas_current_frame.insert(layer_id);
1196        self.areas.insert(layer_id.id, state);
1197        if !self.order.contains(&layer_id) {
1198            self.order.push(layer_id);
1199        }
1200    }
1201
1202    /// Top-most layer at the given position.
1203    pub fn layer_id_at(
1204        &self,
1205        pos: Pos2,
1206        layer_to_global: &HashMap<LayerId, TSTransform>,
1207    ) -> Option<LayerId> {
1208        for layer in self.order.iter().rev() {
1209            if self.is_visible(layer)
1210                && let Some(state) = self.areas.get(&layer.id)
1211            {
1212                let mut rect = state.rect();
1213                if state.interactable {
1214                    if let Some(to_global) = layer_to_global.get(layer) {
1215                        rect = *to_global * rect;
1216                    }
1217
1218                    if rect.contains(pos) {
1219                        return Some(*layer);
1220                    }
1221                }
1222            }
1223        }
1224        None
1225    }
1226
1227    pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
1228        self.visible_areas_last_frame.contains(layer_id)
1229    }
1230
1231    pub fn is_visible(&self, layer_id: &LayerId) -> bool {
1232        self.visible_areas_last_frame.contains(layer_id)
1233            || self.visible_areas_current_frame.contains(layer_id)
1234    }
1235
1236    pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
1237        self.visible_areas_last_frame
1238            .iter()
1239            .copied()
1240            .chain(self.visible_areas_current_frame.iter().copied())
1241            .collect()
1242    }
1243
1244    pub(crate) fn visible_windows(&self) -> impl Iterator<Item = (LayerId, &area::AreaState)> {
1245        self.visible_layer_ids()
1246            .into_iter()
1247            .filter(|layer| layer.order == crate::Order::Middle)
1248            .filter(|&layer| !self.is_sublayer(&layer))
1249            .filter_map(|layer| Some((layer, self.get(layer.id)?)))
1250    }
1251
1252    pub fn move_to_top(&mut self, layer_id: LayerId) {
1253        self.visible_areas_current_frame.insert(layer_id);
1254        self.wants_to_be_on_top.insert(layer_id);
1255
1256        if !self.order.contains(&layer_id) {
1257            self.order.push(layer_id);
1258        }
1259    }
1260
1261    /// Mark the `child` layer as a sublayer of `parent`.
1262    ///
1263    /// Sublayers are moved directly above the parent layer at the end of the frame. This is mainly
1264    /// intended for adding a new [Area](crate::Area) inside a [Window](crate::Window).
1265    ///
1266    /// This currently only supports one level of nesting. If `parent` is a sublayer of another
1267    /// layer, the behavior is unspecified.
1268    ///
1269    /// The two layers must have the same [`LayerId::order`].
1270    pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
1271        debug_assert_eq!(
1272            parent.order, child.order,
1273            "DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui",
1274            parent.order, child.order
1275        );
1276
1277        self.sublayers.entry(parent).or_default().insert(child);
1278
1279        // Make sure the layers are in the order list:
1280        if !self.order.contains(&parent) {
1281            self.order.push(parent);
1282        }
1283        if !self.order.contains(&child) {
1284            self.order.push(child);
1285        }
1286    }
1287
1288    pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
1289        self.order
1290            .iter()
1291            .rfind(|layer| layer.order == order && !self.is_sublayer(layer))
1292            .copied()
1293    }
1294
1295    /// If this layer is the sublayer of another layer, return the parent.
1296    pub fn parent_layer(&self, layer_id: LayerId) -> Option<LayerId> {
1297        self.sublayers.iter().find_map(|(parent, children)| {
1298            if children.contains(&layer_id) {
1299                Some(*parent)
1300            } else {
1301                None
1302            }
1303        })
1304    }
1305
1306    /// All the child layers of this layer.
1307    pub fn child_layers(&self, layer_id: LayerId) -> impl Iterator<Item = LayerId> + '_ {
1308        self.sublayers.get(&layer_id).into_iter().flatten().copied()
1309    }
1310
1311    pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
1312        self.parent_layer(*layer).is_some()
1313    }
1314
1315    pub(crate) fn end_pass(&mut self) {
1316        let Self {
1317            visible_areas_last_frame,
1318            visible_areas_current_frame,
1319            order,
1320            wants_to_be_on_top,
1321            sublayers,
1322            ..
1323        } = self;
1324
1325        std::mem::swap(visible_areas_last_frame, visible_areas_current_frame);
1326        visible_areas_current_frame.clear();
1327
1328        order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
1329        wants_to_be_on_top.clear();
1330
1331        // For all layers with sublayers, put the sublayers directly after the parent layer:
1332        // (it doesn't matter in which order we replace parents with their children)
1333        #[expect(clippy::iter_over_hash_type)]
1334        for (parent, children) in std::mem::take(sublayers) {
1335            let mut moved_layers = vec![parent]; // parent first…
1336
1337            order.retain(|l| {
1338                if children.contains(l) {
1339                    moved_layers.push(*l); // …followed by children
1340                    false
1341                } else {
1342                    true
1343                }
1344            });
1345            let Some(parent_pos) = order.iter().position(|l| l == &parent) else {
1346                continue;
1347            };
1348            order.splice(parent_pos..=parent_pos, moved_layers); // replace the parent with itself and its children
1349        }
1350
1351        self.order_map = self
1352            .order
1353            .iter()
1354            .enumerate()
1355            .map(|(i, id)| (*id, i))
1356            .collect();
1357    }
1358}
1359
1360// ----------------------------------------------------------------------------
1361
1362#[test]
1363fn memory_impl_send_sync() {
1364    fn assert_send_sync<T: Send + Sync>() {}
1365    assert_send_sync::<Memory>();
1366}
1367
1368#[test]
1369fn order_map_total_ordering() {
1370    let mut layers = [
1371        LayerId::new(Order::Tooltip, Id::new("a")),
1372        LayerId::new(Order::Background, Id::new("b")),
1373        LayerId::new(Order::Background, Id::new("c")),
1374        LayerId::new(Order::Tooltip, Id::new("d")),
1375        LayerId::new(Order::Background, Id::new("e")),
1376        LayerId::new(Order::Background, Id::new("f")),
1377        LayerId::new(Order::Tooltip, Id::new("g")),
1378    ];
1379    let mut areas = Areas::default();
1380
1381    // skip some of the layers
1382    for &layer in &layers[3..] {
1383        areas.set_state(layer, crate::AreaState::default());
1384    }
1385    areas.end_pass(); // sort layers
1386
1387    // Sort layers
1388    layers.sort_by(|&a, &b| areas.compare_order(a, b));
1389
1390    // Assert that `areas.compare_order()` forms a total ordering
1391    let mut equivalence_classes = vec![0];
1392    let mut i = 0;
1393    for l in layers.windows(2) {
1394        assert!(l[0].order <= l[1].order, "does not follow LayerId.order");
1395        if areas.compare_order(l[0], l[1]) != std::cmp::Ordering::Equal {
1396            i += 1;
1397        }
1398        equivalence_classes.push(i);
1399    }
1400    assert_eq!(layers.len(), equivalence_classes.len());
1401    for (&l1, c1) in std::iter::zip(&layers, &equivalence_classes) {
1402        for (&l2, c2) in std::iter::zip(&layers, &equivalence_classes) {
1403            assert_eq!(
1404                c1.cmp(c2),
1405                areas.compare_order(l1, l2),
1406                "not a total ordering",
1407            );
1408        }
1409    }
1410}