diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4e031b215..a02e19c46 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2374,6 +2374,17 @@ impl Context { self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id)); } + /// Mark the `child` layer as a sublayer of `parent`. + /// + /// Sublayers are moved directly above the parent layer at the end of the frame. This is mainly + /// intended for adding a new [`Area`] inside a [`Window`]. + /// + /// This currently only supports one level of nesting. If `parent` is a sublayer of another + /// layer, the behavior is unspecified. + pub fn set_sublayer(&self, parent: LayerId, child: LayerId) { + self.memory_mut(|mem| mem.areas_mut().set_sublayer(parent, child)); + } + /// Retrieve the [`LayerId`] of the top level windows. pub fn top_layer_id(&self) -> Option { self.memory(|mem| mem.areas().top_layer_id(Order::Middle)) diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 84925e1cf..e6971b4f8 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -1,6 +1,6 @@ #![warn(missing_docs)] // Let's keep this file well-documented.` to memory.rs -use ahash::HashMap; +use ahash::{HashMap, HashSet}; use epaint::emath::TSTransform; use crate::{ @@ -951,6 +951,11 @@ pub struct Areas { /// So if you close three windows and then reopen them all in one frame, /// they will all be sent to the top, but keep their previous internal order. wants_to_be_on_top: ahash::HashSet, + + /// List of sublayers for each layer + /// + /// When a layer has sublayers, they are moved directly above it in the ordering. + sublayers: ahash::HashMap>, } impl Areas { @@ -1042,20 +1047,38 @@ impl Areas { } } + /// Mark the `child` layer as a sublayer of `parent`. + /// + /// Sublayers are moved directly above the parent layer at the end of the frame. This is mainly + /// intended for adding a new [Area](crate::Area) inside a [Window](crate::Window). + /// + /// This currently only supports one level of nesting. If `parent` is a sublayer of another + /// layer, the behavior is unspecified. + pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) { + self.sublayers.entry(parent).or_default().insert(child); + } + pub fn top_layer_id(&self, order: Order) -> Option { self.order .iter() - .filter(|layer| layer.order == order) + .filter(|layer| layer.order == order && !self.is_sublayer(layer)) .last() .copied() } + pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool { + self.sublayers + .iter() + .any(|(_, children)| children.contains(layer)) + } + pub(crate) fn end_frame(&mut self) { let Self { visible_last_frame, visible_current_frame, order, wants_to_be_on_top, + sublayers, .. } = self; @@ -1063,6 +1086,23 @@ impl Areas { visible_current_frame.clear(); order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer))); wants_to_be_on_top.clear(); + // For all layers with sublayers, put the sublayers directly after the parent layer: + 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); + } } } diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs index b6e8096b0..da1da2c5b 100644 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -110,11 +110,10 @@ impl crate::View for PanZoom { .into_iter() .enumerate() { + let window_layer = ui.layer_id(); let id = egui::Area::new(id.with(("subarea", i))) .default_pos(pos) - // Need to cover up the pan_zoom demo window, - // but may also cover over other windows. - .order(egui::Order::Foreground) + .order(egui::Order::Middle) .show(ui.ctx(), |ui| { ui.set_clip_rect(transform.inverse() * rect); egui::Frame::default() @@ -130,6 +129,7 @@ impl crate::View for PanZoom { .response .layer_id; ui.ctx().set_transform_layer(id, transform); + ui.ctx().set_sublayer(window_layer, id); } } }