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_node,
575 target_tree,
576 data: None,
577 }) = event
578 && *target_tree == accesskit::TreeId::ROOT
579 {
580 self.id_requested_by_accesskit = Some(*target_node);
581 }
582 }
583 }
584 }
585
586 pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
587 if self.focus_direction.is_cardinal()
588 && let Some(found_widget) = self.find_widget_in_direction(used_ids)
589 {
590 self.focused_widget = Some(FocusWidget::new(found_widget));
591 }
592
593 if let Some(focused_widget) = self.focused_widget {
594 let recently_gained_focus = self.id_previous_frame != Some(focused_widget.id);
596
597 if !recently_gained_focus && !used_ids.contains_key(&focused_widget.id) {
598 self.focused_widget = None;
600 }
601 }
602
603 self.top_modal_layer = self.top_modal_layer_current_frame.take();
604 }
605
606 pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
607 self.id_previous_frame == Some(id)
608 }
609
610 fn interested_in_focus(&mut self, id: Id) {
611 #[cfg(feature = "accesskit")]
612 {
613 if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
614 self.focused_widget = Some(FocusWidget::new(id));
615 self.id_requested_by_accesskit = None;
616 self.give_to_next = false;
617 self.reset_focus();
618 }
619 }
620
621 self.focus_widgets_cache
623 .entry(id)
624 .or_insert(Rect::EVERYTHING);
625
626 if self.give_to_next && !self.had_focus_last_frame(id) {
627 self.focused_widget = Some(FocusWidget::new(id));
628 self.give_to_next = false;
629 } else if self.focused() == Some(id) {
630 if self.focus_direction == FocusDirection::Next {
631 self.focused_widget = None;
632 self.give_to_next = true;
633 self.reset_focus();
634 } else if self.focus_direction == FocusDirection::Previous {
635 self.id_next_frame = self.last_interested; self.reset_focus();
637 }
638 } else if self.focus_direction == FocusDirection::Next
639 && self.focused_widget.is_none()
640 && !self.give_to_next
641 {
642 self.focused_widget = Some(FocusWidget::new(id));
644 self.reset_focus();
645 } else if self.focus_direction == FocusDirection::Previous
646 && self.focused_widget.is_none()
647 && !self.give_to_next
648 {
649 self.focused_widget = self.last_interested.map(FocusWidget::new);
651 self.reset_focus();
652 }
653
654 self.last_interested = Some(id);
655 }
656
657 fn set_modal_layer(&mut self, layer_id: LayerId) {
658 self.top_modal_layer_current_frame = Some(layer_id);
659 }
660
661 pub(crate) fn top_modal_layer(&self) -> Option<LayerId> {
662 self.top_modal_layer
663 }
664
665 fn reset_focus(&mut self) {
666 self.focus_direction = FocusDirection::None;
667 }
668
669 fn find_widget_in_direction(&mut self, new_rects: &IdMap<Rect>) -> Option<Id> {
670 fn range_diff(a: Rangef, b: Rangef) -> f32 {
676 let has_significant_overlap = a.intersection(b).span() >= 0.5 * b.span().min(a.span());
677 if has_significant_overlap {
678 0.0
679 } else {
680 a.center() - b.center()
681 }
682 }
683
684 let current_focused = self.focused_widget?;
685
686 let search_direction = match self.focus_direction {
688 FocusDirection::Up => Vec2::UP,
689 FocusDirection::Right => Vec2::RIGHT,
690 FocusDirection::Down => Vec2::DOWN,
691 FocusDirection::Left => Vec2::LEFT,
692 _ => {
693 return None;
694 }
695 };
696
697 self.focus_widgets_cache.retain(|id, old_rect| {
699 if let Some(new_rect) = new_rects.get(id) {
700 *old_rect = *new_rect;
701 true } else {
703 false }
705 });
706
707 let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?;
708
709 let mut best_score = f32::INFINITY;
710 let mut best_id = None;
711
712 #[expect(clippy::iter_over_hash_type)]
714 for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
715 if *candidate_id == current_focused.id {
716 continue;
717 }
718
719 let to_candidate = vec2(
721 range_diff(candidate_rect.x_range(), current_rect.x_range()),
722 range_diff(candidate_rect.y_range(), current_rect.y_range()),
723 );
724
725 let acos_angle = to_candidate.normalized().dot(search_direction);
726
727 let is_in_search_cone = 0.5_f32.sqrt() <= acos_angle;
730 if is_in_search_cone {
731 let distance = to_candidate.length();
732
733 let score = distance / (acos_angle * acos_angle);
735
736 if score < best_score {
737 best_score = score;
738 best_id = Some(*candidate_id);
739 }
740 }
741 }
742
743 best_id
744 }
745}
746
747impl Memory {
748 pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
749 profiling::function_scope!();
750
751 self.viewport_id = new_raw_input.viewport_id;
752
753 self.interactions.retain(|id, _| viewports.contains(id));
755 self.areas.retain(|id, _| viewports.contains(id));
756 self.popups.retain(|id, _| viewports.contains(id));
757
758 self.areas.entry(self.viewport_id).or_default();
759
760 self.options.begin_pass(new_raw_input);
763
764 self.focus
765 .entry(self.viewport_id)
766 .or_default()
767 .begin_pass(new_raw_input);
768 }
769
770 pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
771 self.caches.update();
772 self.areas_mut().end_pass();
773 self.focus_mut().end_pass(used_ids);
774
775 if let Some(popup) = self.popups.get_mut(&self.viewport_id) {
777 if popup.open_this_frame {
778 popup.open_this_frame = false;
779 } else {
780 self.popups.remove(&self.viewport_id);
781 }
782 }
783 }
784
785 pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
786 self.viewport_id = viewport_id;
787 }
788
789 pub fn areas(&self) -> &Areas {
791 self.areas
792 .get(&self.viewport_id)
793 .expect("Memory broken: no area for the current viewport")
794 }
795
796 pub fn areas_mut(&mut self) -> &mut Areas {
798 self.areas.entry(self.viewport_id).or_default()
799 }
800
801 pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
803 let layer_id = self.areas().layer_id_at(pos, &self.to_global)?;
804 if self.is_above_modal_layer(layer_id) {
805 Some(layer_id)
806 } else {
807 self.top_modal_layer()
808 }
809 }
810
811 #[deprecated = "Use `Context::layer_transform_to_global` instead"]
813 pub fn layer_transforms(&self, layer_id: LayerId) -> Option<TSTransform> {
814 self.to_global.get(&layer_id).copied()
815 }
816
817 pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
819 self.areas().order().iter().copied()
820 }
821
822 pub fn had_focus_last_frame(&self, id: Id) -> bool {
825 self.focus().and_then(|f| f.id_previous_frame) == Some(id)
826 }
827
828 pub(crate) fn lost_focus(&self, id: Id) -> bool {
831 self.had_focus_last_frame(id) && !self.has_focus(id)
832 }
833
834 pub(crate) fn gained_focus(&self, id: Id) -> bool {
837 !self.had_focus_last_frame(id) && self.has_focus(id)
838 }
839
840 #[inline(always)]
847 pub fn has_focus(&self, id: Id) -> bool {
848 self.focused() == Some(id)
849 }
850
851 pub fn focused(&self) -> Option<Id> {
853 self.focus()?.focused()
854 }
855
856 pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
863 if self.had_focus_last_frame(id)
864 && self.has_focus(id)
865 && let Some(focused) = &mut self.focus_mut().focused_widget
866 && focused.id == id
867 {
868 focused.filter = event_filter;
869 }
870 }
871
872 #[inline(always)]
875 pub fn request_focus(&mut self, id: Id) {
876 self.focus_mut().focused_widget = Some(FocusWidget::new(id));
877 }
878
879 #[inline(always)]
882 pub fn surrender_focus(&mut self, id: Id) {
883 let focus = self.focus_mut();
884 if focus.focused() == Some(id) {
885 focus.focused_widget = None;
886 }
887 }
888
889 pub fn move_focus(&mut self, direction: FocusDirection) {
891 self.focus_mut().focus_direction = direction;
892 }
893
894 pub fn is_above_modal_layer(&self, layer_id: LayerId) -> bool {
898 if let Some(modal_layer) = self.focus().and_then(|f| f.top_modal_layer) {
899 matches!(
900 self.areas().compare_order(layer_id, modal_layer),
901 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater
902 )
903 } else {
904 true
905 }
906 }
907
908 pub fn allows_interaction(&self, layer_id: LayerId) -> bool {
913 let is_above_modal_layer = self.is_above_modal_layer(layer_id);
914 let ordering_allows_interaction = layer_id.order.allow_interaction();
915 is_above_modal_layer && ordering_allows_interaction
916 }
917
918 #[inline(always)]
928 pub fn interested_in_focus(&mut self, id: Id, layer_id: LayerId) {
929 if !self.allows_interaction(layer_id) {
930 return;
931 }
932 self.focus_mut().interested_in_focus(id);
933 }
934
935 pub fn set_modal_layer(&mut self, layer_id: LayerId) {
938 if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame)
939 && matches!(
940 self.areas().compare_order(layer_id, current),
941 std::cmp::Ordering::Less
942 )
943 {
944 return;
945 }
946
947 self.focus_mut().set_modal_layer(layer_id);
948 }
949
950 pub fn top_modal_layer(&self) -> Option<LayerId> {
952 self.focus()?.top_modal_layer()
953 }
954
955 #[inline(always)]
957 pub fn stop_text_input(&mut self) {
958 self.focus_mut().focused_widget = None;
959 }
960
961 pub fn reset_areas(&mut self) {
964 #[expect(clippy::iter_over_hash_type)]
965 for area in self.areas.values_mut() {
966 *area = Default::default();
967 }
968 }
969
970 pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
972 self.areas().get(id.into()).map(|state| state.rect())
973 }
974
975 pub(crate) fn interaction(&self) -> &InteractionState {
976 self.interactions
977 .get(&self.viewport_id)
978 .expect("Failed to get interaction")
979 }
980
981 pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
982 self.interactions.entry(self.viewport_id).or_default()
983 }
984
985 pub(crate) fn focus(&self) -> Option<&Focus> {
986 self.focus.get(&self.viewport_id)
987 }
988
989 pub(crate) fn focus_mut(&mut self) -> &mut Focus {
990 self.focus.entry(self.viewport_id).or_default()
991 }
992}
993
994#[derive(Clone, Copy, Debug)]
996struct OpenPopup {
997 id: Id,
999
1000 pos: Option<Pos2>,
1002
1003 open_this_frame: bool,
1005}
1006
1007impl OpenPopup {
1008 fn new(id: Id, pos: Option<Pos2>) -> Self {
1010 Self {
1011 id,
1012 pos,
1013 open_this_frame: true,
1014 }
1015 }
1016}
1017
1018impl Memory {
1021 #[deprecated = "Use Popup::is_id_open instead"]
1023 pub fn is_popup_open(&self, popup_id: Id) -> bool {
1024 self.popups
1025 .get(&self.viewport_id)
1026 .is_some_and(|state| state.id == popup_id)
1027 || self.everything_is_visible()
1028 }
1029
1030 #[deprecated = "Use Popup::is_any_open instead"]
1032 pub fn any_popup_open(&self) -> bool {
1033 self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
1034 }
1035
1036 #[deprecated = "Use Popup::open_id instead"]
1040 pub fn open_popup(&mut self, popup_id: Id) {
1041 self.popups
1042 .insert(self.viewport_id, OpenPopup::new(popup_id, None));
1043 }
1044
1045 #[deprecated = "Use Popup::show instead"]
1051 pub fn keep_popup_open(&mut self, popup_id: Id) {
1052 if let Some(state) = self.popups.get_mut(&self.viewport_id)
1053 && state.id == popup_id
1054 {
1055 state.open_this_frame = true;
1056 }
1057 }
1058
1059 #[deprecated = "Use Popup with PopupAnchor::Position instead"]
1061 pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
1062 self.popups
1063 .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
1064 }
1065
1066 #[deprecated = "Use Popup::position_of_id instead"]
1068 pub fn popup_position(&self, id: Id) -> Option<Pos2> {
1069 let state = self.popups.get(&self.viewport_id)?;
1070 if state.id == id { state.pos } else { None }
1071 }
1072
1073 #[deprecated = "Use Popup::close_all instead"]
1075 pub fn close_all_popups(&mut self) {
1076 self.popups.clear();
1077 }
1078
1079 #[deprecated = "Use Popup::close_id instead"]
1083 pub fn close_popup(&mut self, popup_id: Id) {
1084 #[expect(deprecated)]
1085 if self.is_popup_open(popup_id) {
1086 self.popups.remove(&self.viewport_id);
1087 }
1088 }
1089
1090 #[deprecated = "Use Popup::toggle_id instead"]
1094 pub fn toggle_popup(&mut self, popup_id: Id) {
1095 #[expect(deprecated)]
1096 if self.is_popup_open(popup_id) {
1097 self.close_popup(popup_id);
1098 } else {
1099 self.open_popup(popup_id);
1100 }
1101 }
1102}
1103
1104impl Memory {
1105 #[inline(always)]
1111 pub fn everything_is_visible(&self) -> bool {
1112 self.everything_is_visible
1113 }
1114
1115 pub fn set_everything_is_visible(&mut self, value: bool) {
1121 self.everything_is_visible = value;
1122 }
1123}
1124
1125type OrderMap = HashMap<LayerId, usize>;
1129
1130#[derive(Clone, Debug, Default)]
1133#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1134#[cfg_attr(feature = "serde", serde(default))]
1135pub struct Areas {
1136 areas: IdMap<area::AreaState>,
1137
1138 visible_areas_last_frame: ahash::HashSet<LayerId>,
1139 visible_areas_current_frame: ahash::HashSet<LayerId>,
1140
1141 order: Vec<LayerId>,
1146
1147 order_map: OrderMap,
1149
1150 wants_to_be_on_top: ahash::HashSet<LayerId>,
1156
1157 sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
1161}
1162
1163impl Areas {
1164 pub(crate) fn count(&self) -> usize {
1165 self.areas.len()
1166 }
1167
1168 pub(crate) fn get(&self, id: Id) -> Option<&area::AreaState> {
1169 self.areas.get(&id)
1170 }
1171
1172 pub(crate) fn order(&self) -> &[LayerId] {
1174 &self.order
1175 }
1176
1177 pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
1181 match a.order.cmp(&b.order) {
1185 std::cmp::Ordering::Equal => self.order_map.get(&a).cmp(&self.order_map.get(&b)),
1186 cmp => cmp,
1187 }
1188 }
1189
1190 pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
1191 self.visible_areas_current_frame.insert(layer_id);
1192 self.areas.insert(layer_id.id, state);
1193 if !self.order.contains(&layer_id) {
1194 self.order.push(layer_id);
1195 }
1196 }
1197
1198 pub fn layer_id_at(
1200 &self,
1201 pos: Pos2,
1202 layer_to_global: &HashMap<LayerId, TSTransform>,
1203 ) -> Option<LayerId> {
1204 for layer in self.order.iter().rev() {
1205 if self.is_visible(layer)
1206 && let Some(state) = self.areas.get(&layer.id)
1207 {
1208 let mut rect = state.rect();
1209 if state.interactable {
1210 if let Some(to_global) = layer_to_global.get(layer) {
1211 rect = *to_global * rect;
1212 }
1213
1214 if rect.contains(pos) {
1215 return Some(*layer);
1216 }
1217 }
1218 }
1219 }
1220 None
1221 }
1222
1223 pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
1224 self.visible_areas_last_frame.contains(layer_id)
1225 }
1226
1227 pub fn is_visible(&self, layer_id: &LayerId) -> bool {
1228 self.visible_areas_last_frame.contains(layer_id)
1229 || self.visible_areas_current_frame.contains(layer_id)
1230 }
1231
1232 pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
1233 self.visible_areas_last_frame
1234 .iter()
1235 .copied()
1236 .chain(self.visible_areas_current_frame.iter().copied())
1237 .collect()
1238 }
1239
1240 pub(crate) fn visible_windows(&self) -> impl Iterator<Item = (LayerId, &area::AreaState)> {
1241 self.visible_layer_ids()
1242 .into_iter()
1243 .filter(|layer| layer.order == crate::Order::Middle)
1244 .filter(|&layer| !self.is_sublayer(&layer))
1245 .filter_map(|layer| Some((layer, self.get(layer.id)?)))
1246 }
1247
1248 pub fn move_to_top(&mut self, layer_id: LayerId) {
1249 self.visible_areas_current_frame.insert(layer_id);
1250 self.wants_to_be_on_top.insert(layer_id);
1251
1252 if !self.order.contains(&layer_id) {
1253 self.order.push(layer_id);
1254 }
1255 }
1256
1257 pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
1267 debug_assert_eq!(
1268 parent.order, child.order,
1269 "DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui",
1270 parent.order, child.order
1271 );
1272
1273 self.sublayers.entry(parent).or_default().insert(child);
1274
1275 if !self.order.contains(&parent) {
1277 self.order.push(parent);
1278 }
1279 if !self.order.contains(&child) {
1280 self.order.push(child);
1281 }
1282 }
1283
1284 pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
1285 self.order
1286 .iter()
1287 .filter(|layer| layer.order == order && !self.is_sublayer(layer))
1288 .next_back()
1289 .copied()
1290 }
1291
1292 pub fn parent_layer(&self, layer_id: LayerId) -> Option<LayerId> {
1294 self.sublayers.iter().find_map(|(parent, children)| {
1295 if children.contains(&layer_id) {
1296 Some(*parent)
1297 } else {
1298 None
1299 }
1300 })
1301 }
1302
1303 pub fn child_layers(&self, layer_id: LayerId) -> impl Iterator<Item = LayerId> + '_ {
1305 self.sublayers.get(&layer_id).into_iter().flatten().copied()
1306 }
1307
1308 pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
1309 self.parent_layer(*layer).is_some()
1310 }
1311
1312 pub(crate) fn end_pass(&mut self) {
1313 let Self {
1314 visible_areas_last_frame,
1315 visible_areas_current_frame,
1316 order,
1317 wants_to_be_on_top,
1318 sublayers,
1319 ..
1320 } = self;
1321
1322 std::mem::swap(visible_areas_last_frame, visible_areas_current_frame);
1323 visible_areas_current_frame.clear();
1324
1325 order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
1326 wants_to_be_on_top.clear();
1327
1328 #[expect(clippy::iter_over_hash_type)]
1331 for (parent, children) in std::mem::take(sublayers) {
1332 let mut moved_layers = vec![parent]; order.retain(|l| {
1335 if children.contains(l) {
1336 moved_layers.push(*l); false
1338 } else {
1339 true
1340 }
1341 });
1342 let Some(parent_pos) = order.iter().position(|l| l == &parent) else {
1343 continue;
1344 };
1345 order.splice(parent_pos..=parent_pos, moved_layers); }
1347
1348 self.order_map = self
1349 .order
1350 .iter()
1351 .enumerate()
1352 .map(|(i, id)| (*id, i))
1353 .collect();
1354 }
1355}
1356
1357#[test]
1360fn memory_impl_send_sync() {
1361 fn assert_send_sync<T: Send + Sync>() {}
1362 assert_send_sync::<Memory>();
1363}
1364
1365#[test]
1366fn order_map_total_ordering() {
1367 let mut layers = [
1368 LayerId::new(Order::Tooltip, Id::new("a")),
1369 LayerId::new(Order::Background, Id::new("b")),
1370 LayerId::new(Order::Background, Id::new("c")),
1371 LayerId::new(Order::Tooltip, Id::new("d")),
1372 LayerId::new(Order::Background, Id::new("e")),
1373 LayerId::new(Order::Background, Id::new("f")),
1374 LayerId::new(Order::Tooltip, Id::new("g")),
1375 ];
1376 let mut areas = Areas::default();
1377
1378 for &layer in &layers[3..] {
1380 areas.set_state(layer, crate::AreaState::default());
1381 }
1382 areas.end_pass(); layers.sort_by(|&a, &b| areas.compare_order(a, b));
1386
1387 let mut equivalence_classes = vec![0];
1389 let mut i = 0;
1390 for l in layers.windows(2) {
1391 assert!(l[0].order <= l[1].order, "does not follow LayerId.order");
1392 if areas.compare_order(l[0], l[1]) != std::cmp::Ordering::Equal {
1393 i += 1;
1394 }
1395 equivalence_classes.push(i);
1396 }
1397 assert_eq!(layers.len(), equivalence_classes.len());
1398 for (&l1, c1) in std::iter::zip(&layers, &equivalence_classes) {
1399 for (&l2, c2) in std::iter::zip(&layers, &equivalence_classes) {
1400 assert_eq!(
1401 c1.cmp(c2),
1402 areas.compare_order(l1, l2),
1403 "not a total ordering",
1404 );
1405 }
1406 }
1407}