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 pub tessellation_options: epaint::TessellationOptions,
239
240 pub repaint_on_widget_change: bool,
246
247 pub max_passes: NonZeroUsize,
263
264 pub screen_reader: bool,
274
275 pub warn_on_id_clash: bool,
279
280 pub input_options: crate::input_state::InputOptions,
282
283 pub reduce_texture_memory: bool,
295}
296
297impl Default for Options {
298 fn default() -> Self {
299 Self {
300 dark_style: std::sync::Arc::new(Theme::Dark.default_style()),
301 light_style: std::sync::Arc::new(Theme::Light.default_style()),
302 theme_preference: Default::default(),
303 fallback_theme: Theme::Dark,
304 system_theme: None,
305 zoom_factor: 1.0,
306 zoom_with_keyboard: true,
307 tessellation_options: Default::default(),
308 repaint_on_widget_change: false,
309 max_passes: NonZeroUsize::new(2).unwrap(),
310 screen_reader: false,
311 warn_on_id_clash: cfg!(debug_assertions),
312
313 input_options: Default::default(),
315 reduce_texture_memory: false,
316 }
317 }
318}
319
320impl Options {
321 #[doc(hidden)]
323 pub fn begin_pass(&mut self, new_raw_input: &RawInput) {
324 self.system_theme = new_raw_input.system_theme;
325 }
326
327 pub(crate) fn theme(&self) -> Theme {
329 match self.theme_preference {
330 ThemePreference::Dark => Theme::Dark,
331 ThemePreference::Light => Theme::Light,
332 ThemePreference::System => self.system_theme.unwrap_or(self.fallback_theme),
333 }
334 }
335
336 pub(crate) fn style(&self) -> &std::sync::Arc<Style> {
337 match self.theme() {
338 Theme::Dark => &self.dark_style,
339 Theme::Light => &self.light_style,
340 }
341 }
342
343 pub(crate) fn style_mut(&mut self) -> &mut std::sync::Arc<Style> {
344 match self.theme() {
345 Theme::Dark => &mut self.dark_style,
346 Theme::Light => &mut self.light_style,
347 }
348 }
349}
350
351impl Options {
352 pub fn ui(&mut self, ui: &mut crate::Ui) {
354 let theme = self.theme();
355
356 let Self {
357 dark_style, light_style,
359 theme_preference,
360 fallback_theme: _,
361 system_theme: _,
362 zoom_factor,
363 zoom_with_keyboard,
364 tessellation_options,
365 repaint_on_widget_change,
366 max_passes,
367 screen_reader: _, warn_on_id_clash,
369 input_options,
370 reduce_texture_memory,
371 } = self;
372
373 use crate::Widget as _;
374 use crate::containers::CollapsingHeader;
375
376 CollapsingHeader::new("⚙ Options")
377 .default_open(false)
378 .show(ui, |ui| {
379 ui.horizontal(|ui| {
380 ui.label("Max passes:");
381 ui.add(crate::DragValue::new(max_passes).range(0..=10));
382 });
383
384 ui.checkbox(
385 repaint_on_widget_change,
386 "Repaint if any widget moves or changes id",
387 );
388
389 ui.horizontal(|ui| {
390 ui.label("Zoom factor:");
391 ui.add(crate::DragValue::new(zoom_factor).range(0.10..=10.0));
392 });
393
394 ui.checkbox(
395 zoom_with_keyboard,
396 "Zoom with keyboard (Cmd +, Cmd -, Cmd 0)",
397 );
398
399 ui.checkbox(warn_on_id_clash, "Warn if two widgets have the same Id");
400
401 ui.checkbox(reduce_texture_memory, "Reduce texture memory");
402 });
403
404 CollapsingHeader::new("🎑 Style")
405 .default_open(true)
406 .show(ui, |ui| {
407 theme_preference.radio_buttons(ui);
408
409 let style = std::sync::Arc::make_mut(match theme {
410 Theme::Dark => dark_style,
411 Theme::Light => light_style,
412 });
413 style.ui(ui);
414 });
415
416 CollapsingHeader::new("✒ Painting")
417 .default_open(false)
418 .show(ui, |ui| {
419 tessellation_options.ui(ui);
420 ui.vertical_centered(|ui| {
421 crate::reset_button(ui, tessellation_options, "Reset paint settings");
422 });
423 });
424
425 CollapsingHeader::new("🖱 Input")
426 .default_open(false)
427 .show(ui, |ui| {
428 input_options.ui(ui);
429 });
430
431 ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all"));
432 }
433}
434
435#[derive(Clone, Debug, Default)]
448pub(crate) struct InteractionState {
449 pub potential_click_id: Option<Id>,
451
452 pub potential_drag_id: Option<Id>,
459}
460
461#[derive(Clone, Debug, Default)]
463pub(crate) struct Focus {
464 focused_widget: Option<FocusWidget>,
466
467 id_previous_frame: Option<Id>,
469
470 id_next_frame: Option<Id>,
472
473 #[cfg(feature = "accesskit")]
474 id_requested_by_accesskit: Option<accesskit::NodeId>,
475
476 give_to_next: bool,
479
480 last_interested: Option<Id>,
482
483 focus_direction: FocusDirection,
485
486 top_modal_layer: Option<LayerId>,
488
489 top_modal_layer_current_frame: Option<LayerId>,
491
492 focus_widgets_cache: IdMap<Rect>,
494}
495
496#[derive(Clone, Copy, Debug)]
498struct FocusWidget {
499 pub id: Id,
500 pub filter: EventFilter,
501}
502
503impl FocusWidget {
504 pub fn new(id: Id) -> Self {
505 Self {
506 id,
507 filter: Default::default(),
508 }
509 }
510}
511
512impl InteractionState {
513 pub fn is_using_pointer(&self) -> bool {
515 self.potential_click_id.is_some() || self.potential_drag_id.is_some()
516 }
517}
518
519impl Focus {
520 pub fn focused(&self) -> Option<Id> {
522 self.focused_widget.as_ref().map(|w| w.id)
523 }
524
525 fn begin_pass(&mut self, new_input: &crate::data::input::RawInput) {
526 self.id_previous_frame = self.focused();
527 if let Some(id) = self.id_next_frame.take() {
528 self.focused_widget = Some(FocusWidget::new(id));
529 }
530 let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
531
532 #[cfg(feature = "accesskit")]
533 {
534 self.id_requested_by_accesskit = None;
535 }
536
537 self.focus_direction = FocusDirection::None;
538
539 for event in &new_input.events {
540 if !event_filter.matches(event)
541 && let crate::Event::Key {
542 key,
543 pressed: true,
544 modifiers,
545 ..
546 } = event
547 && let Some(cardinality) = match key {
548 crate::Key::ArrowUp => Some(FocusDirection::Up),
549 crate::Key::ArrowRight => Some(FocusDirection::Right),
550 crate::Key::ArrowDown => Some(FocusDirection::Down),
551 crate::Key::ArrowLeft => Some(FocusDirection::Left),
552
553 crate::Key::Tab => {
554 if modifiers.shift {
555 Some(FocusDirection::Previous)
556 } else {
557 Some(FocusDirection::Next)
558 }
559 }
560 crate::Key::Escape => {
561 self.focused_widget = None;
562 Some(FocusDirection::None)
563 }
564 _ => None,
565 }
566 {
567 self.focus_direction = cardinality;
568 }
569
570 #[cfg(feature = "accesskit")]
571 {
572 if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
573 action: accesskit::Action::Focus,
574 target,
575 data: None,
576 }) = event
577 {
578 self.id_requested_by_accesskit = Some(*target);
579 }
580 }
581 }
582 }
583
584 pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
585 if self.focus_direction.is_cardinal()
586 && let Some(found_widget) = self.find_widget_in_direction(used_ids)
587 {
588 self.focused_widget = Some(FocusWidget::new(found_widget));
589 }
590
591 if let Some(focused_widget) = self.focused_widget {
592 let recently_gained_focus = self.id_previous_frame != Some(focused_widget.id);
594
595 if !recently_gained_focus && !used_ids.contains_key(&focused_widget.id) {
596 self.focused_widget = None;
598 }
599 }
600
601 self.top_modal_layer = self.top_modal_layer_current_frame.take();
602 }
603
604 pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
605 self.id_previous_frame == Some(id)
606 }
607
608 fn interested_in_focus(&mut self, id: Id) {
609 #[cfg(feature = "accesskit")]
610 {
611 if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
612 self.focused_widget = Some(FocusWidget::new(id));
613 self.id_requested_by_accesskit = None;
614 self.give_to_next = false;
615 self.reset_focus();
616 }
617 }
618
619 self.focus_widgets_cache
621 .entry(id)
622 .or_insert(Rect::EVERYTHING);
623
624 if self.give_to_next && !self.had_focus_last_frame(id) {
625 self.focused_widget = Some(FocusWidget::new(id));
626 self.give_to_next = false;
627 } else if self.focused() == Some(id) {
628 if self.focus_direction == FocusDirection::Next {
629 self.focused_widget = None;
630 self.give_to_next = true;
631 self.reset_focus();
632 } else if self.focus_direction == FocusDirection::Previous {
633 self.id_next_frame = self.last_interested; self.reset_focus();
635 }
636 } else if self.focus_direction == FocusDirection::Next
637 && self.focused_widget.is_none()
638 && !self.give_to_next
639 {
640 self.focused_widget = Some(FocusWidget::new(id));
642 self.reset_focus();
643 } else if self.focus_direction == FocusDirection::Previous
644 && self.focused_widget.is_none()
645 && !self.give_to_next
646 {
647 self.focused_widget = self.last_interested.map(FocusWidget::new);
649 self.reset_focus();
650 }
651
652 self.last_interested = Some(id);
653 }
654
655 fn set_modal_layer(&mut self, layer_id: LayerId) {
656 self.top_modal_layer_current_frame = Some(layer_id);
657 }
658
659 pub(crate) fn top_modal_layer(&self) -> Option<LayerId> {
660 self.top_modal_layer
661 }
662
663 fn reset_focus(&mut self) {
664 self.focus_direction = FocusDirection::None;
665 }
666
667 fn find_widget_in_direction(&mut self, new_rects: &IdMap<Rect>) -> Option<Id> {
668 fn range_diff(a: Rangef, b: Rangef) -> f32 {
674 let has_significant_overlap = a.intersection(b).span() >= 0.5 * b.span().min(a.span());
675 if has_significant_overlap {
676 0.0
677 } else {
678 a.center() - b.center()
679 }
680 }
681
682 let current_focused = self.focused_widget?;
683
684 let search_direction = match self.focus_direction {
686 FocusDirection::Up => Vec2::UP,
687 FocusDirection::Right => Vec2::RIGHT,
688 FocusDirection::Down => Vec2::DOWN,
689 FocusDirection::Left => Vec2::LEFT,
690 _ => {
691 return None;
692 }
693 };
694
695 self.focus_widgets_cache.retain(|id, old_rect| {
697 if let Some(new_rect) = new_rects.get(id) {
698 *old_rect = *new_rect;
699 true } else {
701 false }
703 });
704
705 let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?;
706
707 let mut best_score = f32::INFINITY;
708 let mut best_id = None;
709
710 #[expect(clippy::iter_over_hash_type)]
712 for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
713 if *candidate_id == current_focused.id {
714 continue;
715 }
716
717 let to_candidate = vec2(
719 range_diff(candidate_rect.x_range(), current_rect.x_range()),
720 range_diff(candidate_rect.y_range(), current_rect.y_range()),
721 );
722
723 let acos_angle = to_candidate.normalized().dot(search_direction);
724
725 let is_in_search_cone = 0.5_f32.sqrt() <= acos_angle;
728 if is_in_search_cone {
729 let distance = to_candidate.length();
730
731 let score = distance / (acos_angle * acos_angle);
733
734 if score < best_score {
735 best_score = score;
736 best_id = Some(*candidate_id);
737 }
738 }
739 }
740
741 best_id
742 }
743}
744
745impl Memory {
746 pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
747 profiling::function_scope!();
748
749 self.viewport_id = new_raw_input.viewport_id;
750
751 self.interactions.retain(|id, _| viewports.contains(id));
753 self.areas.retain(|id, _| viewports.contains(id));
754 self.popups.retain(|id, _| viewports.contains(id));
755
756 self.areas.entry(self.viewport_id).or_default();
757
758 self.options.begin_pass(new_raw_input);
761
762 self.focus
763 .entry(self.viewport_id)
764 .or_default()
765 .begin_pass(new_raw_input);
766 }
767
768 pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
769 self.caches.update();
770 self.areas_mut().end_pass();
771 self.focus_mut().end_pass(used_ids);
772
773 if let Some(popup) = self.popups.get_mut(&self.viewport_id) {
775 if popup.open_this_frame {
776 popup.open_this_frame = false;
777 } else {
778 self.popups.remove(&self.viewport_id);
779 }
780 }
781 }
782
783 pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
784 self.viewport_id = viewport_id;
785 }
786
787 pub fn areas(&self) -> &Areas {
789 self.areas
790 .get(&self.viewport_id)
791 .expect("Memory broken: no area for the current viewport")
792 }
793
794 pub fn areas_mut(&mut self) -> &mut Areas {
796 self.areas.entry(self.viewport_id).or_default()
797 }
798
799 pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
801 let layer_id = self.areas().layer_id_at(pos, &self.to_global)?;
802 if self.is_above_modal_layer(layer_id) {
803 Some(layer_id)
804 } else {
805 self.top_modal_layer()
806 }
807 }
808
809 #[deprecated = "Use `Context::layer_transform_to_global` instead"]
811 pub fn layer_transforms(&self, layer_id: LayerId) -> Option<TSTransform> {
812 self.to_global.get(&layer_id).copied()
813 }
814
815 pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
817 self.areas().order().iter().copied()
818 }
819
820 pub fn had_focus_last_frame(&self, id: Id) -> bool {
823 self.focus().and_then(|f| f.id_previous_frame) == Some(id)
824 }
825
826 pub(crate) fn lost_focus(&self, id: Id) -> bool {
829 self.had_focus_last_frame(id) && !self.has_focus(id)
830 }
831
832 pub(crate) fn gained_focus(&self, id: Id) -> bool {
835 !self.had_focus_last_frame(id) && self.has_focus(id)
836 }
837
838 #[inline(always)]
845 pub fn has_focus(&self, id: Id) -> bool {
846 self.focused() == Some(id)
847 }
848
849 pub fn focused(&self) -> Option<Id> {
851 self.focus()?.focused()
852 }
853
854 pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
861 if self.had_focus_last_frame(id)
862 && self.has_focus(id)
863 && let Some(focused) = &mut self.focus_mut().focused_widget
864 && focused.id == id
865 {
866 focused.filter = event_filter;
867 }
868 }
869
870 #[inline(always)]
873 pub fn request_focus(&mut self, id: Id) {
874 self.focus_mut().focused_widget = Some(FocusWidget::new(id));
875 }
876
877 #[inline(always)]
880 pub fn surrender_focus(&mut self, id: Id) {
881 let focus = self.focus_mut();
882 if focus.focused() == Some(id) {
883 focus.focused_widget = None;
884 }
885 }
886
887 pub fn move_focus(&mut self, direction: FocusDirection) {
889 self.focus_mut().focus_direction = direction;
890 }
891
892 pub fn is_above_modal_layer(&self, layer_id: LayerId) -> bool {
896 if let Some(modal_layer) = self.focus().and_then(|f| f.top_modal_layer) {
897 matches!(
898 self.areas().compare_order(layer_id, modal_layer),
899 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater
900 )
901 } else {
902 true
903 }
904 }
905
906 pub fn allows_interaction(&self, layer_id: LayerId) -> bool {
911 let is_above_modal_layer = self.is_above_modal_layer(layer_id);
912 let ordering_allows_interaction = layer_id.order.allow_interaction();
913 is_above_modal_layer && ordering_allows_interaction
914 }
915
916 #[inline(always)]
926 pub fn interested_in_focus(&mut self, id: Id, layer_id: LayerId) {
927 if !self.allows_interaction(layer_id) {
928 return;
929 }
930 self.focus_mut().interested_in_focus(id);
931 }
932
933 pub fn set_modal_layer(&mut self, layer_id: LayerId) {
936 if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame)
937 && matches!(
938 self.areas().compare_order(layer_id, current),
939 std::cmp::Ordering::Less
940 )
941 {
942 return;
943 }
944
945 self.focus_mut().set_modal_layer(layer_id);
946 }
947
948 pub fn top_modal_layer(&self) -> Option<LayerId> {
950 self.focus()?.top_modal_layer()
951 }
952
953 #[inline(always)]
955 pub fn stop_text_input(&mut self) {
956 self.focus_mut().focused_widget = None;
957 }
958
959 pub fn reset_areas(&mut self) {
962 #[expect(clippy::iter_over_hash_type)]
963 for area in self.areas.values_mut() {
964 *area = Default::default();
965 }
966 }
967
968 pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
970 self.areas().get(id.into()).map(|state| state.rect())
971 }
972
973 pub(crate) fn interaction(&self) -> &InteractionState {
974 self.interactions
975 .get(&self.viewport_id)
976 .expect("Failed to get interaction")
977 }
978
979 pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
980 self.interactions.entry(self.viewport_id).or_default()
981 }
982
983 pub(crate) fn focus(&self) -> Option<&Focus> {
984 self.focus.get(&self.viewport_id)
985 }
986
987 pub(crate) fn focus_mut(&mut self) -> &mut Focus {
988 self.focus.entry(self.viewport_id).or_default()
989 }
990}
991
992#[derive(Clone, Copy, Debug)]
994struct OpenPopup {
995 id: Id,
997
998 pos: Option<Pos2>,
1000
1001 open_this_frame: bool,
1003}
1004
1005impl OpenPopup {
1006 fn new(id: Id, pos: Option<Pos2>) -> Self {
1008 Self {
1009 id,
1010 pos,
1011 open_this_frame: true,
1012 }
1013 }
1014}
1015
1016impl Memory {
1019 #[deprecated = "Use Popup::is_id_open instead"]
1021 pub fn is_popup_open(&self, popup_id: Id) -> bool {
1022 self.popups
1023 .get(&self.viewport_id)
1024 .is_some_and(|state| state.id == popup_id)
1025 || self.everything_is_visible()
1026 }
1027
1028 #[deprecated = "Use Popup::is_any_open instead"]
1030 pub fn any_popup_open(&self) -> bool {
1031 self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
1032 }
1033
1034 #[deprecated = "Use Popup::open_id instead"]
1038 pub fn open_popup(&mut self, popup_id: Id) {
1039 self.popups
1040 .insert(self.viewport_id, OpenPopup::new(popup_id, None));
1041 }
1042
1043 #[deprecated = "Use Popup::show instead"]
1049 pub fn keep_popup_open(&mut self, popup_id: Id) {
1050 if let Some(state) = self.popups.get_mut(&self.viewport_id)
1051 && state.id == popup_id
1052 {
1053 state.open_this_frame = true;
1054 }
1055 }
1056
1057 #[deprecated = "Use Popup with PopupAnchor::Position instead"]
1059 pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
1060 self.popups
1061 .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
1062 }
1063
1064 #[deprecated = "Use Popup::position_of_id instead"]
1066 pub fn popup_position(&self, id: Id) -> Option<Pos2> {
1067 let state = self.popups.get(&self.viewport_id)?;
1068 if state.id == id { state.pos } else { None }
1069 }
1070
1071 #[deprecated = "Use Popup::close_all instead"]
1073 pub fn close_all_popups(&mut self) {
1074 self.popups.clear();
1075 }
1076
1077 #[deprecated = "Use Popup::close_id instead"]
1081 pub fn close_popup(&mut self, popup_id: Id) {
1082 #[expect(deprecated)]
1083 if self.is_popup_open(popup_id) {
1084 self.popups.remove(&self.viewport_id);
1085 }
1086 }
1087
1088 #[deprecated = "Use Popup::toggle_id instead"]
1092 pub fn toggle_popup(&mut self, popup_id: Id) {
1093 #[expect(deprecated)]
1094 if self.is_popup_open(popup_id) {
1095 self.close_popup(popup_id);
1096 } else {
1097 self.open_popup(popup_id);
1098 }
1099 }
1100}
1101
1102impl Memory {
1103 #[inline(always)]
1109 pub fn everything_is_visible(&self) -> bool {
1110 self.everything_is_visible
1111 }
1112
1113 pub fn set_everything_is_visible(&mut self, value: bool) {
1119 self.everything_is_visible = value;
1120 }
1121}
1122
1123type OrderMap = HashMap<LayerId, usize>;
1127
1128#[derive(Clone, Debug, Default)]
1131#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1132#[cfg_attr(feature = "serde", serde(default))]
1133pub struct Areas {
1134 areas: IdMap<area::AreaState>,
1135
1136 visible_areas_last_frame: ahash::HashSet<LayerId>,
1137 visible_areas_current_frame: ahash::HashSet<LayerId>,
1138
1139 order: Vec<LayerId>,
1144
1145 order_map: OrderMap,
1147
1148 wants_to_be_on_top: ahash::HashSet<LayerId>,
1154
1155 sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
1159}
1160
1161impl Areas {
1162 pub(crate) fn count(&self) -> usize {
1163 self.areas.len()
1164 }
1165
1166 pub(crate) fn get(&self, id: Id) -> Option<&area::AreaState> {
1167 self.areas.get(&id)
1168 }
1169
1170 pub(crate) fn order(&self) -> &[LayerId] {
1172 &self.order
1173 }
1174
1175 pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
1179 match a.order.cmp(&b.order) {
1183 std::cmp::Ordering::Equal => self.order_map.get(&a).cmp(&self.order_map.get(&b)),
1184 cmp => cmp,
1185 }
1186 }
1187
1188 pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
1189 self.visible_areas_current_frame.insert(layer_id);
1190 self.areas.insert(layer_id.id, state);
1191 if !self.order.contains(&layer_id) {
1192 self.order.push(layer_id);
1193 }
1194 }
1195
1196 pub fn layer_id_at(
1198 &self,
1199 pos: Pos2,
1200 layer_to_global: &HashMap<LayerId, TSTransform>,
1201 ) -> Option<LayerId> {
1202 for layer in self.order.iter().rev() {
1203 if self.is_visible(layer)
1204 && let Some(state) = self.areas.get(&layer.id)
1205 {
1206 let mut rect = state.rect();
1207 if state.interactable {
1208 if let Some(to_global) = layer_to_global.get(layer) {
1209 rect = *to_global * rect;
1210 }
1211
1212 if rect.contains(pos) {
1213 return Some(*layer);
1214 }
1215 }
1216 }
1217 }
1218 None
1219 }
1220
1221 pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
1222 self.visible_areas_last_frame.contains(layer_id)
1223 }
1224
1225 pub fn is_visible(&self, layer_id: &LayerId) -> bool {
1226 self.visible_areas_last_frame.contains(layer_id)
1227 || self.visible_areas_current_frame.contains(layer_id)
1228 }
1229
1230 pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
1231 self.visible_areas_last_frame
1232 .iter()
1233 .copied()
1234 .chain(self.visible_areas_current_frame.iter().copied())
1235 .collect()
1236 }
1237
1238 pub(crate) fn visible_windows(&self) -> impl Iterator<Item = (LayerId, &area::AreaState)> {
1239 self.visible_layer_ids()
1240 .into_iter()
1241 .filter(|layer| layer.order == crate::Order::Middle)
1242 .filter(|&layer| !self.is_sublayer(&layer))
1243 .filter_map(|layer| Some((layer, self.get(layer.id)?)))
1244 }
1245
1246 pub fn move_to_top(&mut self, layer_id: LayerId) {
1247 self.visible_areas_current_frame.insert(layer_id);
1248 self.wants_to_be_on_top.insert(layer_id);
1249
1250 if !self.order.contains(&layer_id) {
1251 self.order.push(layer_id);
1252 }
1253 }
1254
1255 pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
1265 debug_assert_eq!(
1266 parent.order, child.order,
1267 "DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui",
1268 parent.order, child.order
1269 );
1270
1271 self.sublayers.entry(parent).or_default().insert(child);
1272
1273 if !self.order.contains(&parent) {
1275 self.order.push(parent);
1276 }
1277 if !self.order.contains(&child) {
1278 self.order.push(child);
1279 }
1280 }
1281
1282 pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
1283 self.order
1284 .iter()
1285 .filter(|layer| layer.order == order && !self.is_sublayer(layer))
1286 .next_back()
1287 .copied()
1288 }
1289
1290 pub fn parent_layer(&self, layer_id: LayerId) -> Option<LayerId> {
1292 self.sublayers.iter().find_map(|(parent, children)| {
1293 if children.contains(&layer_id) {
1294 Some(*parent)
1295 } else {
1296 None
1297 }
1298 })
1299 }
1300
1301 pub fn child_layers(&self, layer_id: LayerId) -> impl Iterator<Item = LayerId> + '_ {
1303 self.sublayers.get(&layer_id).into_iter().flatten().copied()
1304 }
1305
1306 pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
1307 self.parent_layer(*layer).is_some()
1308 }
1309
1310 pub(crate) fn end_pass(&mut self) {
1311 let Self {
1312 visible_areas_last_frame,
1313 visible_areas_current_frame,
1314 order,
1315 wants_to_be_on_top,
1316 sublayers,
1317 ..
1318 } = self;
1319
1320 std::mem::swap(visible_areas_last_frame, visible_areas_current_frame);
1321 visible_areas_current_frame.clear();
1322
1323 order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
1324 wants_to_be_on_top.clear();
1325
1326 #[expect(clippy::iter_over_hash_type)]
1329 for (parent, children) in std::mem::take(sublayers) {
1330 let mut moved_layers = vec![parent]; order.retain(|l| {
1333 if children.contains(l) {
1334 moved_layers.push(*l); false
1336 } else {
1337 true
1338 }
1339 });
1340 let Some(parent_pos) = order.iter().position(|l| l == &parent) else {
1341 continue;
1342 };
1343 order.splice(parent_pos..=parent_pos, moved_layers); }
1345
1346 self.order_map = self
1347 .order
1348 .iter()
1349 .enumerate()
1350 .map(|(i, id)| (*id, i))
1351 .collect();
1352 }
1353}
1354
1355#[test]
1358fn memory_impl_send_sync() {
1359 fn assert_send_sync<T: Send + Sync>() {}
1360 assert_send_sync::<Memory>();
1361}
1362
1363#[test]
1364fn order_map_total_ordering() {
1365 let mut layers = [
1366 LayerId::new(Order::Tooltip, Id::new("a")),
1367 LayerId::new(Order::Background, Id::new("b")),
1368 LayerId::new(Order::Background, Id::new("c")),
1369 LayerId::new(Order::Tooltip, Id::new("d")),
1370 LayerId::new(Order::Background, Id::new("e")),
1371 LayerId::new(Order::Background, Id::new("f")),
1372 LayerId::new(Order::Tooltip, Id::new("g")),
1373 ];
1374 let mut areas = Areas::default();
1375
1376 for &layer in &layers[3..] {
1378 areas.set_state(layer, crate::AreaState::default());
1379 }
1380 areas.end_pass(); layers.sort_by(|&a, &b| areas.compare_order(a, b));
1384
1385 let mut equivalence_classes = vec![0];
1387 let mut i = 0;
1388 for l in layers.windows(2) {
1389 assert!(l[0].order <= l[1].order, "does not follow LayerId.order");
1390 if areas.compare_order(l[0], l[1]) != std::cmp::Ordering::Equal {
1391 i += 1;
1392 }
1393 equivalence_classes.push(i);
1394 }
1395 assert_eq!(layers.len(), equivalence_classes.len());
1396 for (&l1, c1) in std::iter::zip(&layers, &equivalence_classes) {
1397 for (&l2, c2) in std::iter::zip(&layers, &equivalence_classes) {
1398 assert_eq!(
1399 c1.cmp(c2),
1400 areas.compare_order(l1, l2),
1401 "not a total ordering",
1402 );
1403 }
1404 }
1405}