diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c4cb687d..785da03c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -604,9 +604,9 @@ impl Context { } /// Get a full-screen painter for a new or existing layer - pub fn layer_painter(&self, layer_id: AreaLayerId) -> Painter { + pub fn layer_painter(&self, layer: AreaLayerId) -> Painter { let screen_rect = self.input().screen_rect(); - Painter::new(self.clone(), layer_id, screen_rect) + Painter::new(self.clone(), layer, screen_rect) } /// Paint on top of everything else diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index e619f88f..584361a6 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -98,6 +98,11 @@ impl AreaLayerId { self.order.allow_interaction() } + #[must_use] + pub fn with_z(self, z: ZOrder) -> ZLayer { + ZLayer::from_area_layer_z(self, z) + } + /// Short and readable summary pub fn short_debug_format(&self) -> String { format!( @@ -108,13 +113,72 @@ impl AreaLayerId { } } +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ZOrder(pub i32); + +impl ZOrder { + const DEFAULT: ZOrder = ZOrder(0); +} + +impl Default for ZOrder { + fn default() -> Self { + Self::DEFAULT + } +} + +/// An identifier for a paint layer which supports Z-indexing +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ZLayer { + pub area_layer: AreaLayerId, + pub z: ZOrder, +} + +impl ZLayer { + pub fn new(order: Order, id: Id, z: ZOrder) -> Self { + Self { + area_layer: AreaLayerId { order, id }, + z, + } + } + + pub fn from_area_layer_z(area_layer: AreaLayerId, z: ZOrder) -> Self { + Self { area_layer, z } + } + + pub fn from_area_layer(area_layer: AreaLayerId) -> Self { + Self::from_area_layer_z(area_layer, ZOrder::default()) + } + + pub fn debug() -> Self { + Self::from_area_layer(AreaLayerId::debug()) + } + + pub fn background() -> Self { + Self::from_area_layer(AreaLayerId::background()) + } +} + /// A unique identifier of a specific [`Shape`] in a [`PaintList`]. #[derive(Clone, Copy, PartialEq, Eq)] pub struct ShapeIdx(usize); +#[derive(Clone, PartialEq)] +struct PaintedShape { + shape: ClippedShape, + z: ZOrder, +} + +impl PaintedShape { + pub fn new(shape: ClippedShape, z: ZOrder) -> Self { + Self { shape, z } + } +} + /// A list of [`Shape`]s paired with a clip rectangle. #[derive(Clone, Default)] -pub struct PaintList(Vec); +pub struct PaintList(Vec); impl PaintList { #[inline(always)] @@ -124,17 +188,18 @@ impl PaintList { /// Returns the index of the new [`Shape`] that can be used with `PaintList::set`. #[inline(always)] - pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx { + pub fn add(&mut self, clip_rect: Rect, shape: Shape, z: ZOrder) -> ShapeIdx { let idx = ShapeIdx(self.0.len()); - self.0.push(ClippedShape(clip_rect, shape)); + self.0 + .push(PaintedShape::new(ClippedShape(clip_rect, shape), z)); idx } - pub fn extend>(&mut self, clip_rect: Rect, shapes: I) { + pub fn extend>(&mut self, clip_rect: Rect, shapes: I, z: ZOrder) { self.0.extend( shapes .into_iter() - .map(|shape| ClippedShape(clip_rect, shape)), + .map(|shape| PaintedShape::new(ClippedShape(clip_rect, shape), z)), ); } @@ -147,12 +212,16 @@ impl PaintList { /// and then later setting it using `paint_list.set(idx, cr, frame);`. #[inline(always)] pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) { - self.0[idx.0] = ClippedShape(clip_rect, shape); + self.0[idx.0].shape = ClippedShape(clip_rect, shape); } /// Translate each [`Shape`] and clip rectangle by this much, in-place pub fn translate(&mut self, delta: Vec2) { - for ClippedShape(clip_rect, shape) in &mut self.0 { + for PaintedShape { + shape: ClippedShape(clip_rect, shape), + .. + } in &mut self.0 + { *clip_rect = clip_rect.translate(delta); shape.translate(delta); } @@ -178,6 +247,11 @@ impl GraphicLayers { for &order in &Order::ALL { let order_map = &mut self.0[order as usize]; + // Sort by z-order + for list in order_map.values_mut() { + list.0.sort_by_key(|PaintedShape { z, .. }| *z); + } + // If a layer is empty at the start of the frame // then nobody has added to it, and it is old and defunct. // Free it to save memory: @@ -198,6 +272,8 @@ impl GraphicLayers { } } - all_shapes.into_iter() + all_shapes + .into_iter() + .map(|PaintedShape { shape, .. }| shape) } } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 5097a14e..91f9de0e 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use crate::{ emath::{Align2, Pos2, Rect, Vec2}, - layers::{AreaLayerId, PaintList, ShapeIdx}, - Color32, Context, FontId, + layers::{PaintList, ShapeIdx, ZLayer, ZOrder}, + AreaLayerId, Color32, Context, FontId, }; use epaint::{ mutex::{RwLockReadGuard, RwLockWriteGuard}, @@ -21,7 +21,7 @@ pub struct Painter { ctx: Context, /// Where we paint - layer_id: AreaLayerId, + layer: ZLayer, /// Everything painted in this [`Painter`] will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. @@ -34,10 +34,10 @@ pub struct Painter { impl Painter { /// Create a painter to a specific layer within a certain clip rectangle. - pub fn new(ctx: Context, layer_id: AreaLayerId, clip_rect: Rect) -> Self { + pub fn new(ctx: Context, area_layer: AreaLayerId, clip_rect: Rect) -> Self { Self { ctx, - layer_id, + layer: ZLayer::from_area_layer(area_layer), clip_rect, fade_to_color: None, } @@ -45,15 +45,28 @@ impl Painter { /// Redirect where you are painting. #[must_use] - pub fn with_layer_id(self, layer_id: AreaLayerId) -> Self { + pub fn with_layer(self, layer: ZLayer) -> Self { Self { ctx: self.ctx, - layer_id, + layer, clip_rect: self.clip_rect, fade_to_color: None, } } + /// Redirect where you are painting with default z-index + #[must_use] + pub fn with_layer_id(self, layer: AreaLayerId) -> Self { + self.with_layer(ZLayer::from_area_layer(layer)) + } + + /// Redirect z-index + #[must_use] + pub fn with_z(self, z: ZOrder) -> Self { + let layer = self.layer.area_layer.with_z(z); + self.with_layer(layer) + } + /// Create a painter for a sub-region of this [`Painter`]. /// /// The clip-rect of the returned [`Painter`] will be the intersection @@ -61,15 +74,25 @@ impl Painter { pub fn with_clip_rect(&self, rect: Rect) -> Self { Self { ctx: self.ctx.clone(), - layer_id: self.layer_id, + layer: self.layer, clip_rect: rect.intersect(self.clip_rect), fade_to_color: self.fade_to_color, } } - /// Redirect where you are painting. - pub fn set_layer_id(&mut self, layer_id: AreaLayerId) { - self.layer_id = layer_id; + /// Redirect what area layer you are painting. + pub fn set_layer_id(&mut self, area_layer: AreaLayerId) { + self.layer.area_layer = area_layer; + } + + /// Redirect at what z order you are drawing + pub fn set_z(&mut self, z: ZOrder) { + self.layer.z = z; + } + + /// Redirect where you are drawing + pub fn set_layer(&mut self, layer: ZLayer) { + self.layer = layer; } /// If set, colors will be modified to look like this @@ -90,7 +113,7 @@ impl Painter { pub fn sub_region(&self, rect: Rect) -> Self { Self { ctx: self.ctx.clone(), - layer_id: self.layer_id, + layer: self.layer, clip_rect: rect.intersect(self.clip_rect), fade_to_color: self.fade_to_color, } @@ -113,8 +136,13 @@ impl Painter { /// Where we paint #[inline(always)] - pub fn layer_id(&self) -> AreaLayerId { - self.layer_id + pub fn layer(&self) -> AreaLayerId { + self.layer.area_layer + } + + #[inline(always)] + pub fn z(&self) -> ZOrder { + self.layer.z } /// Everything painted in this [`Painter`] will be clipped against this. @@ -153,7 +181,7 @@ impl Painter { /// ## Low level impl Painter { fn paint_list(&self) -> RwLockWriteGuard<'_, PaintList> { - RwLockWriteGuard::map(self.ctx.graphics(), |g| g.list(self.layer_id)) + RwLockWriteGuard::map(self.ctx.graphics(), |g| g.list(self.layer.area_layer)) } fn transform_shape(&self, shape: &mut Shape) { @@ -162,16 +190,29 @@ impl Painter { } } + fn add_to_paint_list(&self, shape: Shape) -> ShapeIdx { + self.paint_list().add(self.clip_rect, shape, self.layer.z) + } + + fn extend_paint_list(&self, shapes: impl IntoIterator) { + self.paint_list() + .extend(self.clip_rect, shapes, self.layer.z); + } + + fn set_shape_in_paint_list(&self, idx: ShapeIdx, shape: Shape) { + self.paint_list().set(idx, self.clip_rect, shape); + } + /// It is up to the caller to make sure there is room for this. /// Can be used for free painting. /// NOTE: all coordinates are screen coordinates! pub fn add(&self, shape: impl Into) -> ShapeIdx { if self.fade_to_color == Some(Color32::TRANSPARENT) { - self.paint_list().add(self.clip_rect, Shape::Noop) + self.add_to_paint_list(Shape::Noop) } else { let mut shape = shape.into(); self.transform_shape(&mut shape); - self.paint_list().add(self.clip_rect, shape) + self.add_to_paint_list(shape) } } @@ -187,9 +228,9 @@ impl Painter { self.transform_shape(&mut shape); shape }); - self.paint_list().extend(self.clip_rect, shapes); + self.extend_paint_list(shapes); } else { - self.paint_list().extend(self.clip_rect, shapes); + self.extend_paint_list(shapes); }; } @@ -200,7 +241,7 @@ impl Painter { } let mut shape = shape.into(); self.transform_shape(&mut shape); - self.paint_list().set(idx, self.clip_rect, shape); + self.set_shape_in_paint_list(idx, shape); } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d09a32a0..899b6d56 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -6,8 +6,15 @@ use std::sync::Arc; use epaint::mutex::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::{ - containers::*, ecolor::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer, - widgets::*, *, + containers::*, + ecolor::*, + epaint::text::Fonts, + layers::{ZLayer, ZOrder}, + layout::*, + menu::MenuState, + placer::Placer, + widgets::*, + *, }; // ---------------------------------------------------------------------------- @@ -317,7 +324,7 @@ impl Ui { /// Use this to paint stuff within this [`Ui`]. #[inline] pub fn layer_id(&self) -> AreaLayerId { - self.painter().layer_id() + self.painter().layer() } /// The [`InputState`] of the [`Context`] associated with this [`Ui`]. @@ -1731,7 +1738,7 @@ impl Ui { InnerResponse::new(ret, response) } - /// Redirect shapes to another paint layer. + /// Redirect shapes to another area layer. pub fn with_layer_id( &mut self, layer_id: AreaLayerId, @@ -1743,6 +1750,28 @@ impl Ui { }) } + pub fn with_layer( + &mut self, + layer: ZLayer, + add_contents: impl FnOnce(&mut Self) -> R, + ) -> InnerResponse { + self.scope(|ui| { + ui.painter.set_layer(layer); + add_contents(ui) + }) + } + + pub fn with_z( + &mut self, + z: ZOrder, + add_contents: impl FnOnce(&mut Self) -> R, + ) -> InnerResponse { + self.scope(|ui| { + ui.painter.set_z(z); + add_contents(ui) + }) + } + /// A [`CollapsingHeader`] that starts out collapsed. pub fn collapsing( &mut self,