#![warn(missing_docs)]
use std::num::NonZeroUsize;
use ahash::{HashMap, HashSet};
use epaint::emath::TSTransform;
use crate::{
EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId,
ViewportIdMap, ViewportIdSet, area, vec2,
};
mod theme;
pub use theme::{Theme, ThemePreference};
#[derive(Clone, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Memory {
pub options: Options,
pub data: crate::util::IdTypeMap,
#[cfg_attr(feature = "persistence", serde(skip))]
pub caches: crate::cache::CacheStorage,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) add_fonts: Vec<epaint::text::FontInsert>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) viewport_id: ViewportId,
#[cfg_attr(feature = "persistence", serde(skip))]
everything_is_visible: bool,
pub to_global: HashMap<LayerId, TSTransform>,
areas: ViewportIdMap<Areas>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interactions: ViewportIdMap<InteractionState>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) focus: ViewportIdMap<Focus>,
#[cfg_attr(feature = "persistence", serde(skip))]
popups: ViewportIdMap<OpenPopup>,
}
impl Default for Memory {
fn default() -> Self {
let mut slf = Self {
options: Default::default(),
data: Default::default(),
caches: Default::default(),
new_font_definitions: Default::default(),
interactions: Default::default(),
focus: Default::default(),
viewport_id: Default::default(),
areas: Default::default(),
to_global: Default::default(),
popups: Default::default(),
everything_is_visible: Default::default(),
add_fonts: Default::default(),
};
slf.interactions.entry(slf.viewport_id).or_default();
slf.areas.entry(slf.viewport_id).or_default();
slf
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum FocusDirection {
Up,
Right,
Down,
Left,
Previous,
Next,
#[default]
None,
}
impl FocusDirection {
fn is_cardinal(&self) -> bool {
match self {
Self::Up | Self::Right | Self::Down | Self::Left => true,
Self::Previous | Self::Next | Self::None => false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Options {
#[cfg_attr(feature = "serde", serde(skip))]
pub dark_style: std::sync::Arc<Style>,
#[cfg_attr(feature = "serde", serde(skip))]
pub light_style: std::sync::Arc<Style>,
pub theme_preference: ThemePreference,
pub fallback_theme: Theme,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) system_theme: Option<Theme>,
pub zoom_factor: f32,
#[cfg_attr(feature = "serde", serde(skip))]
pub zoom_with_keyboard: bool,
pub tessellation_options: epaint::TessellationOptions,
pub repaint_on_widget_change: bool,
pub max_passes: NonZeroUsize,
pub screen_reader: bool,
pub preload_font_glyphs: bool,
pub warn_on_id_clash: bool,
pub input_options: crate::input_state::InputOptions,
pub reduce_texture_memory: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
dark_style: std::sync::Arc::new(Theme::Dark.default_style()),
light_style: std::sync::Arc::new(Theme::Light.default_style()),
theme_preference: Default::default(),
fallback_theme: Theme::Dark,
system_theme: None,
zoom_factor: 1.0,
zoom_with_keyboard: true,
tessellation_options: Default::default(),
repaint_on_widget_change: false,
max_passes: NonZeroUsize::new(2).unwrap(),
screen_reader: false,
preload_font_glyphs: true,
warn_on_id_clash: cfg!(debug_assertions),
input_options: Default::default(),
reduce_texture_memory: false,
}
}
}
impl Options {
pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput) {
self.system_theme = new_raw_input.system_theme;
}
pub(crate) fn theme(&self) -> Theme {
match self.theme_preference {
ThemePreference::Dark => Theme::Dark,
ThemePreference::Light => Theme::Light,
ThemePreference::System => self.system_theme.unwrap_or(self.fallback_theme),
}
}
pub(crate) fn style(&self) -> &std::sync::Arc<Style> {
match self.theme() {
Theme::Dark => &self.dark_style,
Theme::Light => &self.light_style,
}
}
pub(crate) fn style_mut(&mut self) -> &mut std::sync::Arc<Style> {
match self.theme() {
Theme::Dark => &mut self.dark_style,
Theme::Light => &mut self.light_style,
}
}
}
impl Options {
pub fn ui(&mut self, ui: &mut crate::Ui) {
let theme = self.theme();
let Self {
dark_style, light_style,
theme_preference,
fallback_theme: _,
system_theme: _,
zoom_factor: _, zoom_with_keyboard,
tessellation_options,
repaint_on_widget_change,
max_passes,
screen_reader: _, preload_font_glyphs: _,
warn_on_id_clash,
input_options,
reduce_texture_memory,
} = self;
use crate::Widget as _;
use crate::containers::CollapsingHeader;
CollapsingHeader::new("âš™ Options")
.default_open(false)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label("Max passes:");
ui.add(crate::DragValue::new(max_passes).range(0..=10));
});
ui.checkbox(
repaint_on_widget_change,
"Repaint if any widget moves or changes id",
);
ui.checkbox(
zoom_with_keyboard,
"Zoom with keyboard (Cmd +, Cmd -, Cmd 0)",
);
ui.checkbox(warn_on_id_clash, "Warn if two widgets have the same Id");
ui.checkbox(reduce_texture_memory, "Reduce texture memory");
});
CollapsingHeader::new("🎑 Style")
.default_open(true)
.show(ui, |ui| {
theme_preference.radio_buttons(ui);
let style = std::sync::Arc::make_mut(match theme {
Theme::Dark => dark_style,
Theme::Light => light_style,
});
style.ui(ui);
});
CollapsingHeader::new("✒ Painting")
.default_open(false)
.show(ui, |ui| {
tessellation_options.ui(ui);
ui.vertical_centered(|ui| {
crate::reset_button(ui, tessellation_options, "Reset paint settings");
});
});
CollapsingHeader::new("🖱 Input")
.default_open(false)
.show(ui, |ui| {
input_options.ui(ui);
});
ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all"));
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct InteractionState {
pub potential_click_id: Option<Id>,
pub potential_drag_id: Option<Id>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Focus {
focused_widget: Option<FocusWidget>,
id_previous_frame: Option<Id>,
id_next_frame: Option<Id>,
#[cfg(feature = "accesskit")]
id_requested_by_accesskit: Option<accesskit::NodeId>,
give_to_next: bool,
last_interested: Option<Id>,
focus_direction: FocusDirection,
top_modal_layer: Option<LayerId>,
top_modal_layer_current_frame: Option<LayerId>,
focus_widgets_cache: IdMap<Rect>,
}
#[derive(Clone, Copy, Debug)]
struct FocusWidget {
pub id: Id,
pub filter: EventFilter,
}
impl FocusWidget {
pub fn new(id: Id) -> Self {
Self {
id,
filter: Default::default(),
}
}
}
impl InteractionState {
pub fn is_using_pointer(&self) -> bool {
self.potential_click_id.is_some() || self.potential_drag_id.is_some()
}
}
impl Focus {
pub fn focused(&self) -> Option<Id> {
self.focused_widget.as_ref().map(|w| w.id)
}
fn begin_pass(&mut self, new_input: &crate::data::input::RawInput) {
self.id_previous_frame = self.focused();
if let Some(id) = self.id_next_frame.take() {
self.focused_widget = Some(FocusWidget::new(id));
}
let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
#[cfg(feature = "accesskit")]
{
self.id_requested_by_accesskit = None;
}
self.focus_direction = FocusDirection::None;
for event in &new_input.events {
if !event_filter.matches(event) {
if let crate::Event::Key {
key,
pressed: true,
modifiers,
..
} = event
{
if let Some(cardinality) = match key {
crate::Key::ArrowUp => Some(FocusDirection::Up),
crate::Key::ArrowRight => Some(FocusDirection::Right),
crate::Key::ArrowDown => Some(FocusDirection::Down),
crate::Key::ArrowLeft => Some(FocusDirection::Left),
crate::Key::Tab => {
if modifiers.shift {
Some(FocusDirection::Previous)
} else {
Some(FocusDirection::Next)
}
}
crate::Key::Escape => {
self.focused_widget = None;
Some(FocusDirection::None)
}
_ => None,
} {
self.focus_direction = cardinality;
}
}
}
#[cfg(feature = "accesskit")]
{
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
{
self.id_requested_by_accesskit = Some(*target);
}
}
}
}
pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
if self.focus_direction.is_cardinal() {
if let Some(found_widget) = self.find_widget_in_direction(used_ids) {
self.focused_widget = Some(FocusWidget::new(found_widget));
}
}
if let Some(focused_widget) = self.focused_widget {
let recently_gained_focus = self.id_previous_frame != Some(focused_widget.id);
if !recently_gained_focus && !used_ids.contains_key(&focused_widget.id) {
self.focused_widget = None;
}
}
self.top_modal_layer = self.top_modal_layer_current_frame.take();
}
pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
self.id_previous_frame == Some(id)
}
fn interested_in_focus(&mut self, id: Id) {
#[cfg(feature = "accesskit")]
{
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id));
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
}
}
self.focus_widgets_cache
.entry(id)
.or_insert(Rect::EVERYTHING);
if self.give_to_next && !self.had_focus_last_frame(id) {
self.focused_widget = Some(FocusWidget::new(id));
self.give_to_next = false;
} else if self.focused() == Some(id) {
if self.focus_direction == FocusDirection::Next {
self.focused_widget = None;
self.give_to_next = true;
self.reset_focus();
} else if self.focus_direction == FocusDirection::Previous {
self.id_next_frame = self.last_interested; self.reset_focus();
}
} else if self.focus_direction == FocusDirection::Next
&& self.focused_widget.is_none()
&& !self.give_to_next
{
self.focused_widget = Some(FocusWidget::new(id));
self.reset_focus();
} else if self.focus_direction == FocusDirection::Previous
&& self.focused_widget.is_none()
&& !self.give_to_next
{
self.focused_widget = self.last_interested.map(FocusWidget::new);
self.reset_focus();
}
self.last_interested = Some(id);
}
fn set_modal_layer(&mut self, layer_id: LayerId) {
self.top_modal_layer_current_frame = Some(layer_id);
}
pub(crate) fn top_modal_layer(&self) -> Option<LayerId> {
self.top_modal_layer
}
fn reset_focus(&mut self) {
self.focus_direction = FocusDirection::None;
}
fn find_widget_in_direction(&mut self, new_rects: &IdMap<Rect>) -> Option<Id> {
fn range_diff(a: Rangef, b: Rangef) -> f32 {
let has_significant_overlap = a.intersection(b).span() >= 0.5 * b.span().min(a.span());
if has_significant_overlap {
0.0
} else {
a.center() - b.center()
}
}
let current_focused = self.focused_widget?;
let search_direction = match self.focus_direction {
FocusDirection::Up => Vec2::UP,
FocusDirection::Right => Vec2::RIGHT,
FocusDirection::Down => Vec2::DOWN,
FocusDirection::Left => Vec2::LEFT,
_ => {
return None;
}
};
self.focus_widgets_cache.retain(|id, old_rect| {
if let Some(new_rect) = new_rects.get(id) {
*old_rect = *new_rect;
true } else {
false }
});
let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?;
let mut best_score = f32::INFINITY;
let mut best_id = None;
for (candidate_id, candidate_rect) in &self.focus_widgets_cache {
if *candidate_id == current_focused.id {
continue;
}
let to_candidate = vec2(
range_diff(candidate_rect.x_range(), current_rect.x_range()),
range_diff(candidate_rect.y_range(), current_rect.y_range()),
);
let acos_angle = to_candidate.normalized().dot(search_direction);
let is_in_search_cone = 0.5_f32.sqrt() <= acos_angle;
if is_in_search_cone {
let distance = to_candidate.length();
let score = distance / (acos_angle * acos_angle);
if score < best_score {
best_score = score;
best_id = Some(*candidate_id);
}
}
}
best_id
}
}
impl Memory {
pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
profiling::function_scope!();
self.viewport_id = new_raw_input.viewport_id;
self.interactions.retain(|id, _| viewports.contains(id));
self.areas.retain(|id, _| viewports.contains(id));
self.popups.retain(|id, _| viewports.contains(id));
self.areas.entry(self.viewport_id).or_default();
self.options.begin_pass(new_raw_input);
self.focus
.entry(self.viewport_id)
.or_default()
.begin_pass(new_raw_input);
}
pub(crate) fn end_pass(&mut self, used_ids: &IdMap<Rect>) {
self.caches.update();
self.areas_mut().end_pass();
self.focus_mut().end_pass(used_ids);
if let Some(popup) = self.popups.get_mut(&self.viewport_id) {
if popup.open_this_frame {
popup.open_this_frame = false;
} else {
self.popups.remove(&self.viewport_id);
}
}
}
pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
self.viewport_id = viewport_id;
}
pub fn areas(&self) -> &Areas {
self.areas
.get(&self.viewport_id)
.expect("Memory broken: no area for the current viewport")
}
pub fn areas_mut(&mut self) -> &mut Areas {
self.areas.entry(self.viewport_id).or_default()
}
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
self.areas()
.layer_id_at(pos, &self.to_global)
.and_then(|layer_id| {
if self.is_above_modal_layer(layer_id) {
Some(layer_id)
} else {
self.top_modal_layer()
}
})
}
#[deprecated = "Use `Context::layer_transform_to_global` instead"]
pub fn layer_transforms(&self, layer_id: LayerId) -> Option<TSTransform> {
self.to_global.get(&layer_id).copied()
}
pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
self.areas().order().iter().copied()
}
pub fn had_focus_last_frame(&self, id: Id) -> bool {
self.focus().and_then(|f| f.id_previous_frame) == Some(id)
}
pub(crate) fn lost_focus(&self, id: Id) -> bool {
self.had_focus_last_frame(id) && !self.has_focus(id)
}
pub(crate) fn gained_focus(&self, id: Id) -> bool {
!self.had_focus_last_frame(id) && self.has_focus(id)
}
#[inline(always)]
pub fn has_focus(&self, id: Id) -> bool {
self.focused() == Some(id)
}
pub fn focused(&self) -> Option<Id> {
self.focus().and_then(|f| f.focused())
}
pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
if self.had_focus_last_frame(id) && self.has_focus(id) {
if let Some(focused) = &mut self.focus_mut().focused_widget {
if focused.id == id {
focused.filter = event_filter;
}
}
}
}
#[inline(always)]
pub fn request_focus(&mut self, id: Id) {
self.focus_mut().focused_widget = Some(FocusWidget::new(id));
}
#[inline(always)]
pub fn surrender_focus(&mut self, id: Id) {
let focus = self.focus_mut();
if focus.focused() == Some(id) {
focus.focused_widget = None;
}
}
pub fn is_above_modal_layer(&self, layer_id: LayerId) -> bool {
if let Some(modal_layer) = self.focus().and_then(|f| f.top_modal_layer) {
matches!(
self.areas().compare_order(layer_id, modal_layer),
std::cmp::Ordering::Equal | std::cmp::Ordering::Greater
)
} else {
true
}
}
pub fn allows_interaction(&self, layer_id: LayerId) -> bool {
let is_above_modal_layer = self.is_above_modal_layer(layer_id);
let ordering_allows_interaction = layer_id.order.allow_interaction();
is_above_modal_layer && ordering_allows_interaction
}
#[inline(always)]
pub fn interested_in_focus(&mut self, id: Id, layer_id: LayerId) {
if !self.allows_interaction(layer_id) {
return;
}
self.focus_mut().interested_in_focus(id);
}
pub fn set_modal_layer(&mut self, layer_id: LayerId) {
if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame) {
if matches!(
self.areas().compare_order(layer_id, current),
std::cmp::Ordering::Less
) {
return;
}
}
self.focus_mut().set_modal_layer(layer_id);
}
pub fn top_modal_layer(&self) -> Option<LayerId> {
self.focus()?.top_modal_layer()
}
#[inline(always)]
pub fn stop_text_input(&mut self) {
self.focus_mut().focused_widget = None;
}
pub fn reset_areas(&mut self) {
for area in self.areas.values_mut() {
*area = Default::default();
}
}
pub fn area_rect(&self, id: impl Into<Id>) -> Option<Rect> {
self.areas().get(id.into()).map(|state| state.rect())
}
pub(crate) fn interaction(&self) -> &InteractionState {
self.interactions
.get(&self.viewport_id)
.expect("Failed to get interaction")
}
pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
self.interactions.entry(self.viewport_id).or_default()
}
pub(crate) fn focus(&self) -> Option<&Focus> {
self.focus.get(&self.viewport_id)
}
pub(crate) fn focus_mut(&mut self) -> &mut Focus {
self.focus.entry(self.viewport_id).or_default()
}
}
#[derive(Clone, Copy, Debug)]
struct OpenPopup {
id: Id,
pos: Option<Pos2>,
open_this_frame: bool,
}
impl OpenPopup {
fn new(id: Id, pos: Option<Pos2>) -> Self {
Self {
id,
pos,
open_this_frame: true,
}
}
}
impl Memory {
#[deprecated = "Use Popup::is_id_open instead"]
pub fn is_popup_open(&self, popup_id: Id) -> bool {
self.popups
.get(&self.viewport_id)
.is_some_and(|state| state.id == popup_id)
|| self.everything_is_visible()
}
#[deprecated = "Use Popup::is_any_open instead"]
pub fn any_popup_open(&self) -> bool {
self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
}
#[deprecated = "Use Popup::open_id instead"]
pub fn open_popup(&mut self, popup_id: Id) {
self.popups
.insert(self.viewport_id, OpenPopup::new(popup_id, None));
}
#[deprecated = "Use Popup::show instead"]
pub fn keep_popup_open(&mut self, popup_id: Id) {
if let Some(state) = self.popups.get_mut(&self.viewport_id) {
if state.id == popup_id {
state.open_this_frame = true;
}
}
}
#[deprecated = "Use Popup with PopupAnchor::Position instead"]
pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
self.popups
.insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
}
#[deprecated = "Use Popup::position_of_id instead"]
pub fn popup_position(&self, id: Id) -> Option<Pos2> {
self.popups
.get(&self.viewport_id)
.and_then(|state| if state.id == id { state.pos } else { None })
}
#[deprecated = "Use Popup::close_all instead"]
pub fn close_all_popups(&mut self) {
self.popups.clear();
}
#[deprecated = "Use Popup::close_id instead"]
pub fn close_popup(&mut self, popup_id: Id) {
#[expect(deprecated)]
if self.is_popup_open(popup_id) {
self.popups.remove(&self.viewport_id);
}
}
#[deprecated = "Use Popup::toggle_id instead"]
pub fn toggle_popup(&mut self, popup_id: Id) {
#[expect(deprecated)]
if self.is_popup_open(popup_id) {
self.close_popup(popup_id);
} else {
self.open_popup(popup_id);
}
}
}
impl Memory {
#[inline(always)]
pub fn everything_is_visible(&self) -> bool {
self.everything_is_visible
}
pub fn set_everything_is_visible(&mut self, value: bool) {
self.everything_is_visible = value;
}
}
type OrderMap = HashMap<LayerId, usize>;
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Areas {
areas: IdMap<area::AreaState>,
visible_areas_last_frame: ahash::HashSet<LayerId>,
visible_areas_current_frame: ahash::HashSet<LayerId>,
order: Vec<LayerId>,
order_map: OrderMap,
wants_to_be_on_top: ahash::HashSet<LayerId>,
sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
}
impl Areas {
pub(crate) fn count(&self) -> usize {
self.areas.len()
}
pub(crate) fn get(&self, id: Id) -> Option<&area::AreaState> {
self.areas.get(&id)
}
pub(crate) fn order(&self) -> &[LayerId] {
&self.order
}
pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
match a.order.cmp(&b.order) {
std::cmp::Ordering::Equal => self.order_map.get(&a).cmp(&self.order_map.get(&b)),
cmp => cmp,
}
}
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
self.visible_areas_current_frame.insert(layer_id);
self.areas.insert(layer_id.id, state);
if !self.order.contains(&layer_id) {
self.order.push(layer_id);
}
}
pub fn layer_id_at(
&self,
pos: Pos2,
layer_to_global: &HashMap<LayerId, TSTransform>,
) -> Option<LayerId> {
for layer in self.order.iter().rev() {
if self.is_visible(layer) {
if let Some(state) = self.areas.get(&layer.id) {
let mut rect = state.rect();
if state.interactable {
if let Some(to_global) = layer_to_global.get(layer) {
rect = *to_global * rect;
}
if rect.contains(pos) {
return Some(*layer);
}
}
}
}
}
None
}
pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
self.visible_areas_last_frame.contains(layer_id)
}
pub fn is_visible(&self, layer_id: &LayerId) -> bool {
self.visible_areas_last_frame.contains(layer_id)
|| self.visible_areas_current_frame.contains(layer_id)
}
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
self.visible_areas_last_frame
.iter()
.copied()
.chain(self.visible_areas_current_frame.iter().copied())
.collect()
}
pub(crate) fn visible_windows(&self) -> impl Iterator<Item = (LayerId, &area::AreaState)> {
self.visible_layer_ids()
.into_iter()
.filter(|layer| layer.order == crate::Order::Middle)
.filter(|&layer| !self.is_sublayer(&layer))
.filter_map(|layer| Some((layer, self.get(layer.id)?)))
}
pub fn move_to_top(&mut self, layer_id: LayerId) {
self.visible_areas_current_frame.insert(layer_id);
self.wants_to_be_on_top.insert(layer_id);
if !self.order.contains(&layer_id) {
self.order.push(layer_id);
}
}
pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
debug_assert_eq!(
parent.order, child.order,
"DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui",
parent.order, child.order
);
self.sublayers.entry(parent).or_default().insert(child);
if !self.order.contains(&parent) {
self.order.push(parent);
}
if !self.order.contains(&child) {
self.order.push(child);
}
}
pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
self.order
.iter()
.filter(|layer| layer.order == order && !self.is_sublayer(layer))
.next_back()
.copied()
}
pub fn parent_layer(&self, layer_id: LayerId) -> Option<LayerId> {
self.sublayers.iter().find_map(|(parent, children)| {
if children.contains(&layer_id) {
Some(*parent)
} else {
None
}
})
}
pub fn child_layers(&self, layer_id: LayerId) -> impl Iterator<Item = LayerId> + '_ {
self.sublayers.get(&layer_id).into_iter().flatten().copied()
}
pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
self.parent_layer(*layer).is_some()
}
pub(crate) fn end_pass(&mut self) {
let Self {
visible_areas_last_frame,
visible_areas_current_frame,
order,
wants_to_be_on_top,
sublayers,
..
} = self;
std::mem::swap(visible_areas_last_frame, visible_areas_current_frame);
visible_areas_current_frame.clear();
order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
wants_to_be_on_top.clear();
let sublayers = std::mem::take(sublayers);
for (parent, children) in sublayers {
let mut moved_layers = vec![parent];
order.retain(|l| {
if children.contains(l) {
moved_layers.push(*l);
false
} else {
true
}
});
let Some(parent_pos) = order.iter().position(|l| l == &parent) else {
continue;
};
order.splice(parent_pos..=parent_pos, moved_layers);
}
self.order_map = self
.order
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect();
}
}
#[test]
fn memory_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Memory>();
}
#[test]
fn order_map_total_ordering() {
let mut layers = [
LayerId::new(Order::Tooltip, Id::new("a")),
LayerId::new(Order::Background, Id::new("b")),
LayerId::new(Order::Background, Id::new("c")),
LayerId::new(Order::Tooltip, Id::new("d")),
LayerId::new(Order::Background, Id::new("e")),
LayerId::new(Order::Background, Id::new("f")),
LayerId::new(Order::Tooltip, Id::new("g")),
];
let mut areas = Areas::default();
for &layer in &layers[3..] {
areas.set_state(layer, crate::AreaState::default());
}
areas.end_pass();
layers.sort_by(|&a, &b| areas.compare_order(a, b));
let mut equivalence_classes = vec![0];
let mut i = 0;
for l in layers.windows(2) {
assert!(l[0].order <= l[1].order, "does not follow LayerId.order");
if areas.compare_order(l[0], l[1]) != std::cmp::Ordering::Equal {
i += 1;
}
equivalence_classes.push(i);
}
assert_eq!(layers.len(), equivalence_classes.len());
for (&l1, c1) in std::iter::zip(&layers, &equivalence_classes) {
for (&l2, c2) in std::iter::zip(&layers, &equivalence_classes) {
assert_eq!(
c1.cmp(c2),
areas.compare_order(l1, l2),
"not a total ordering",
);
}
}
}