1#![warn(missing_docs)] use 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#[derive(Clone, Debug)]
28#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
29#[cfg_attr(feature = "persistence", serde(default))]
30pub struct Memory {
31 pub options: Options,
33
34 pub data: crate::util::IdTypeMap,
48
49 #[cfg_attr(feature = "persistence", serde(skip))]
75 pub caches: crate::cache::CacheStorage,
76
77 #[cfg_attr(feature = "persistence", serde(skip))]
80 pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,
81
82 #[cfg_attr(feature = "persistence", serde(skip))]
84 pub(crate) add_fonts: Vec<epaint::text::FontInsert>,
85
86 #[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 pub to_global: HashMap<LayerId, TSTransform>,
100
101 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 #[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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
145pub enum FocusDirection {
146 Up,
148
149 Right,
151
152 Down,
154
155 Left,
157
158 Previous,
160
161 Next,
163
164 #[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#[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 #[cfg_attr(feature = "serde", serde(skip))]
190 pub dark_style: std::sync::Arc<Style>,
191
192 #[cfg_attr(feature = "serde", serde(skip))]
194 pub light_style: std::sync::Arc<Style>,
195
196 pub theme_preference: ThemePreference,
201
202 pub fallback_theme: Theme,
207
208 #[cfg_attr(feature = "serde", serde(skip))]
211 pub(crate) system_theme: Option<Theme>,
212
213 pub zoom_factor: f32,
223
224 #[cfg_attr(feature = "serde", serde(skip))]
235 pub zoom_with_keyboard: bool,
236
237 #[cfg_attr(feature = "serde", serde(skip))]
245 pub quit_shortcuts: Vec<crate::KeyboardShortcut>,
246
247 pub tessellation_options: epaint::TessellationOptions,
249
250 pub repaint_on_widget_change: bool,
256
257 pub max_passes: NonZeroUsize,
273
274 pub screen_reader: bool,
284
285 pub warn_on_id_clash: bool,
289
290 pub input_options: crate::input_state::InputOptions,
292
293 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_options: Default::default(),
331 reduce_texture_memory: false,
332 }
333 }
334}
335
336impl Options {
337 #[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 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 pub fn ui(&mut self, ui: &mut crate::Ui) {
370 let theme = self.theme();
371
372 let Self {
373 dark_style, light_style,
375 theme_preference,
376 fallback_theme: _,
377 system_theme: _,
378 zoom_factor,
379 zoom_with_keyboard,
380 quit_shortcuts: _, tessellation_options,
382 repaint_on_widget_change,
383 max_passes,
384 screen_reader: _, 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#[derive(Clone, Debug, Default)]
465pub(crate) struct InteractionState {
466 pub potential_click_id: Option<Id>,
468
469 pub potential_drag_id: Option<Id>,
476}
477
478#[derive(Clone, Debug, Default)]
480pub(crate) struct Focus {
481 focused_widget: Option<FocusWidget>,
483
484 id_previous_frame: Option<Id>,
486
487 id_next_frame: Option<Id>,
489
490 id_requested_by_accesskit: Option<accesskit::NodeId>,
491
492 give_to_next: bool,
495
496 last_interested: Option<Id>,
498
499 focus_direction: FocusDirection,
501
502 top_modal_layer: Option<LayerId>,
504
505 top_modal_layer_current_frame: Option<LayerId>,
507
508 focus_widgets_cache: IdMap<Rect>,
510}
511
512#[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 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 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 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 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 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; 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 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 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 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 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 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 } else {
707 false }
709 });
710
711 let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?;
712
713 let mut best_score = f32::INFINITY;
714 let mut best_id = None;
715
716 #[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 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 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 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 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.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 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 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 pub fn areas_mut(&mut self) -> &mut Areas {
802 self.areas.entry(self.viewport_id).or_default()
803 }
804
805 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 #[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 pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
823 self.areas().order().iter().copied()
824 }
825
826 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 pub(crate) fn lost_focus(&self, id: Id) -> bool {
835 self.had_focus_last_frame(id) && !self.has_focus(id)
836 }
837
838 pub(crate) fn gained_focus(&self, id: Id) -> bool {
841 !self.had_focus_last_frame(id) && self.has_focus(id)
842 }
843
844 #[inline(always)]
851 pub fn has_focus(&self, id: Id) -> bool {
852 self.focused() == Some(id)
853 }
854
855 pub fn focused(&self) -> Option<Id> {
857 self.focus()?.focused()
858 }
859
860 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 #[inline(always)]
879 pub fn request_focus(&mut self, id: Id) {
880 self.focus_mut().focused_widget = Some(FocusWidget::new(id));
881 }
882
883 #[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 pub fn move_focus(&mut self, direction: FocusDirection) {
895 self.focus_mut().focus_direction = direction;
896 }
897
898 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 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 #[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 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 pub fn top_modal_layer(&self) -> Option<LayerId> {
956 self.focus()?.top_modal_layer()
957 }
958
959 #[inline(always)]
961 pub fn stop_text_input(&mut self) {
962 self.focus_mut().focused_widget = None;
963 }
964
965 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 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#[derive(Clone, Copy, Debug)]
1000struct OpenPopup {
1001 id: Id,
1003
1004 pos: Option<Pos2>,
1006
1007 open_this_frame: bool,
1009}
1010
1011impl OpenPopup {
1012 fn new(id: Id, pos: Option<Pos2>) -> Self {
1014 Self {
1015 id,
1016 pos,
1017 open_this_frame: true,
1018 }
1019 }
1020}
1021
1022impl Memory {
1025 #[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 #[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 #[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 #[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 #[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 #[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 #[deprecated = "Use Popup::close_all instead"]
1079 pub fn close_all_popups(&mut self) {
1080 self.popups.clear();
1081 }
1082
1083 #[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 #[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 #[inline(always)]
1115 pub fn everything_is_visible(&self) -> bool {
1116 self.everything_is_visible
1117 }
1118
1119 pub fn set_everything_is_visible(&mut self, value: bool) {
1125 self.everything_is_visible = value;
1126 }
1127}
1128
1129type OrderMap = HashMap<LayerId, usize>;
1133
1134#[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 order: Vec<LayerId>,
1150
1151 order_map: OrderMap,
1153
1154 wants_to_be_on_top: ahash::HashSet<LayerId>,
1160
1161 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 pub(crate) fn order(&self) -> &[LayerId] {
1178 &self.order
1179 }
1180
1181 pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
1185 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 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 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 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 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 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 #[expect(clippy::iter_over_hash_type)]
1334 for (parent, children) in std::mem::take(sublayers) {
1335 let mut moved_layers = vec![parent]; order.retain(|l| {
1338 if children.contains(l) {
1339 moved_layers.push(*l); 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); }
1350
1351 self.order_map = self
1352 .order
1353 .iter()
1354 .enumerate()
1355 .map(|(i, id)| (*id, i))
1356 .collect();
1357 }
1358}
1359
1360#[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 for &layer in &layers[3..] {
1383 areas.set_state(layer, crate::AreaState::default());
1384 }
1385 areas.end_pass(); layers.sort_by(|&a, &b| areas.compare_order(a, b));
1389
1390 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}