Initial fix of the z-index

Kirill Bulatov , Antonio Scandurra , and Nathan Sobo created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/gpui2/src/elements/div.rs            |  12 +
crates/gpui2/src/scene.rs                   | 165 +++++++++++++++-------
crates/gpui2/src/window.rs                  |  75 ++++++++--
crates/ui2/src/components/list/list_item.rs |   4 
crates/workspace2/src/toolbar.rs            |   2 
5 files changed, 187 insertions(+), 71 deletions(-)

Detailed changes

crates/gpui2/src/elements/div.rs 🔗

@@ -1447,9 +1447,15 @@ impl Interactivity {
                         cx.on_action(action_type, listener)
                     }
 
-                    f(style, scroll_offset.unwrap_or_default(), cx)
-                },
-            );
+                    cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
+                    if style.background.as_ref().is_some_and(|fill| {
+                        fill.color().is_some_and(|color| !color.is_transparent())
+                    }) {
+                        cx.add_opaque_layer(bounds)
+                    }f(style, scroll_offset.unwrap_or_default(), cx)
+                })
+            },
+        );
 
             if let Some(group) = self.group.as_ref() {
                 GroupBounds::pop(group, cx);

crates/gpui2/src/scene.rs 🔗

@@ -857,55 +857,116 @@ impl Bounds<ScaledPixels> {
     }
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use crate::{point, size};
-
-//     use super::*;
-//     use smallvec::smallvec;
-
-//     #[test]
-//     fn test_scene() {
-//         let mut scene = SceneBuilder::new();
-//         assert_eq!(scene.layers_by_order.len(), 0);
-
-//         scene.insert(&smallvec![1].into(), quad());
-//         scene.insert(&smallvec![2].into(), shadow());
-//         scene.insert(&smallvec![3].into(), quad());
-
-//         let mut batches_count = 0;
-//         for _ in scene.build().batches() {
-//             batches_count += 1;
-//         }
-//         assert_eq!(batches_count, 3);
-//     }
-
-//     fn quad() -> Quad {
-//         Quad {
-//             order: 0,
-//             bounds: Bounds {
-//                 origin: point(ScaledPixels(0.), ScaledPixels(0.)),
-//                 size: size(ScaledPixels(100.), ScaledPixels(100.)),
-//             },
-//             content_mask: Default::default(),
-//             background: Default::default(),
-//             border_color: Default::default(),
-//             corner_radii: Default::default(),
-//             border_widths: Default::default(),
-//         }
-//     }
-
-//     fn shadow() -> Shadow {
-//         Shadow {
-//             order: Default::default(),
-//             bounds: Bounds {
-//                 origin: point(ScaledPixels(0.), ScaledPixels(0.)),
-//                 size: size(ScaledPixels(100.), ScaledPixels(100.)),
-//             },
-//             corner_radii: Default::default(),
-//             content_mask: Default::default(),
-//             color: Default::default(),
-//             blur_radius: Default::default(),
-//         }
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{point, px, size, Size};
+    use smallvec::smallvec;
+
+    // todo!()
+    // #[test]
+    // fn test_scene() {
+    //     let mut scene = SceneBuilder::default();
+    //     assert_eq!(scene.layers_by_order.len(), 0);
+
+    //     // div with z_index(1)
+    //     //     glyph with z_index(1)
+    //     // div with z_index(1)
+    //     //      glyph with z_index(1)
+
+    //     scene.insert(
+    //         &smallvec![1].into(),
+    //         quad(
+    //             point(px(0.), px(0.)),
+    //             size(px(100.), px(100.)),
+    //             crate::black(),
+    //         ),
+    //     );
+    //     scene.insert(
+    //         &smallvec![1, 1].into(),
+    //         sprite(
+    //             point(px(0.), px(0.)),
+    //             size(px(10.), px(10.)),
+    //             crate::white(),
+    //         ),
+    //     );
+    //     scene.insert(
+    //         &smallvec![1].into(),
+    //         quad(
+    //             point(px(10.), px(10.)),
+    //             size(px(20.), px(20.)),
+    //             crate::green(),
+    //         ),
+    //     );
+    //     scene.insert(
+    //         &smallvec![1, 1].into(),
+    //         sprite(point(px(15.), px(15.)), size(px(5.), px(5.)), crate::blue()),
+    //     );
+
+    //     assert!(!scene.layers_by_order.is_empty());
+
+    //     for batch in scene.build().batches() {
+    //         println!("new batch");
+    //         match batch {
+    //             PrimitiveBatch::Quads(quads) => {
+    //                 for quad in quads {
+    //                     if quad.background == crate::black() {
+    //                         println!("  black quad");
+    //                     } else if quad.background == crate::green() {
+    //                         println!("  green quad");
+    //                     } else {
+    //                         todo!("  ((( bad quad");
+    //                     }
+    //                 }
+    //             }
+    //             PrimitiveBatch::MonochromeSprites { sprites, .. } => {
+    //                 for sprite in sprites {
+    //                     if sprite.color == crate::white() {
+    //                         println!("  white sprite");
+    //                     } else if sprite.color == crate::blue() {
+    //                         println!("  blue sprite");
+    //                     } else {
+    //                         todo!("  ((( bad sprite")
+    //                     }
+    //                 }
+    //             }
+    //             _ => todo!(),
+    //         }
+    //     }
+    // }
+
+    fn quad(origin: Point<Pixels>, size: Size<Pixels>, background: Hsla) -> Quad {
+        Quad {
+            order: 0,
+            bounds: Bounds { origin, size }.scale(1.),
+            background,
+            content_mask: ContentMask {
+                bounds: Bounds { origin, size },
+            }
+            .scale(1.),
+            border_color: Default::default(),
+            corner_radii: Default::default(),
+            border_widths: Default::default(),
+        }
+    }
+
+    fn sprite(origin: Point<Pixels>, size: Size<Pixels>, color: Hsla) -> MonochromeSprite {
+        MonochromeSprite {
+            order: 0,
+            bounds: Bounds { origin, size }.scale(1.),
+            content_mask: ContentMask {
+                bounds: Bounds { origin, size },
+            }
+            .scale(1.),
+            color,
+            tile: AtlasTile {
+                texture_id: AtlasTextureId {
+                    index: 0,
+                    kind: crate::AtlasTextureKind::Monochrome,
+                },
+                tile_id: crate::TileId(0),
+                bounds: Default::default(),
+            },
+        }
+    }
+}

crates/gpui2/src/window.rs 🔗

@@ -39,24 +39,36 @@ use std::{
         Arc,
     },
 };
-use util::ResultExt;
+use util::{post_inc, ResultExt};
 
 const ACTIVE_DRAG_Z_INDEX: u8 = 1;
 
 /// A global stacking order, which is created by stacking successive z-index values.
 /// Each z-index will always be interpreted in the context of its parent z-index.
-#[derive(Deref, DerefMut, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
+#[derive(Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
 pub struct StackingOrder {
     #[deref]
     #[deref_mut]
-    z_indices: SmallVec<[u8; 64]>,
+    context_stack: SmallVec<[StackingContext; 64]>,
 }
 
-impl Default for StackingOrder {
-    fn default() -> Self {
-        StackingOrder {
-            z_indices: SmallVec::new(),
+#[derive(Clone, Ord, PartialOrd, PartialEq, Eq)]
+pub struct StackingContext {
+    // TODO kb use u16 and/or try to push the `id` above into the stacking order
+    z_index: u8,
+    id: u16,
+}
+
+impl std::fmt::Debug for StackingOrder {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let mut stacks = self.context_stack.iter().peekable();
+        while let Some(z_index) = stacks.next() {
+            write!(f, "{}.{}", z_index.z_index, z_index.id)?;
+            if stacks.peek().is_some() {
+                write!(f, "->")?;
+            }
         }
+        Ok(())
     }
 }
 
@@ -284,6 +296,7 @@ pub(crate) struct Frame {
     pub(crate) scene_builder: SceneBuilder,
     pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
     pub(crate) z_index_stack: StackingOrder,
+    pub(crate) stacking_context_id_stack: Vec<u16>,
     content_mask_stack: Vec<ContentMask<Pixels>>,
     element_offset_stack: Vec<Point<Pixels>>,
 }
@@ -297,6 +310,7 @@ impl Frame {
             dispatch_tree,
             scene_builder: SceneBuilder::default(),
             z_index_stack: StackingOrder::default(),
+            stacking_context_id_stack: vec![0],
             depth_map: Default::default(),
             content_mask_stack: Vec::new(),
             element_offset_stack: Vec::new(),
@@ -308,6 +322,8 @@ impl Frame {
         self.mouse_listeners.values_mut().for_each(Vec::clear);
         self.dispatch_tree.clear();
         self.depth_map.clear();
+        self.stacking_context_id_stack.clear();
+        self.stacking_context_id_stack.push(0);
     }
 
     fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
@@ -931,8 +947,20 @@ impl<'a> WindowContext<'a> {
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
     pub fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.next_frame.z_index_stack.push(z_index);
+        let id = post_inc(
+            self.window
+                .next_frame
+                .stacking_context_id_stack
+                .last_mut()
+                .unwrap(),
+        );
+        self.window.next_frame.stacking_context_id_stack.push(0);
+        self.window
+            .next_frame
+            .z_index_stack
+            .push(StackingContext { z_index, id });
         let result = f(self);
+        self.window.next_frame.stacking_context_id_stack.pop();
         self.window.next_frame.z_index_stack.pop();
         result
     }
@@ -2046,6 +2074,30 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
         result
     }
 
+    /// Called during painting to invoke the given closure in a new stacking context. The given
+    /// z-index is interpreted relative to the previous call to `stack`.
+    fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
+        let id = post_inc(
+            self.window_mut()
+                .next_frame
+                .stacking_context_id_stack
+                .last_mut()
+                .unwrap(),
+        );
+        self.window_mut()
+            .next_frame
+            .stacking_context_id_stack
+            .push(0);
+        self.window_mut()
+            .next_frame
+            .z_index_stack
+            .push(StackingContext { z_index, id });
+        let result = f(self);
+        self.window_mut().next_frame.stacking_context_id_stack.pop();
+        self.window_mut().next_frame.z_index_stack.pop();
+        result
+    }
+
     /// Update the global element offset relative to the current offset. This is used to implement
     /// scrolling.
     fn with_element_offset<R>(
@@ -2269,13 +2321,6 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         &mut self.window_cx
     }
 
-    pub fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.next_frame.z_index_stack.push(z_index);
-        let result = f(self);
-        self.window.next_frame.z_index_stack.pop();
-        result
-    }
-
     pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
     where
         V: 'static,

crates/ui2/src/components/list/list_item.rs 🔗

@@ -129,6 +129,7 @@ impl RenderOnce for ListItem {
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
         h_stack()
             .id(self.id)
+            .bg(gpui::green())
             .w_full()
             .relative()
             // When an item is inset draw the indent spacing outside of the item
@@ -171,7 +172,8 @@ impl RenderOnce for ListItem {
                             })
                     })
                     .when_some(self.on_click, |this, on_click| {
-                        this.cursor_pointer().on_click(on_click)
+                        this.cursor_copy()
+                            .on_click(move |event, cx| on_click(dbg!(event), cx))
                     })
                     .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
                         this.on_mouse_down(MouseButton::Right, move |event, cx| {

crates/workspace2/src/toolbar.rs 🔗

@@ -105,6 +105,8 @@ impl Render for Toolbar {
         v_stack()
             .p_1()
             .gap_2()
+            // todo!() use a proper constant here (ask Marshall & Nate)
+            .z_index(80)
             .border_b()
             .border_color(cx.theme().colors().border_variant)
             .bg(cx.theme().colors().toolbar_background)