Better display items from different sources with different z-indices in the same place (#3723)

Kirill Bulatov created

Change summary

crates/collab_ui2/src/collab_titlebar_item.rs |  1 
crates/gpui2/src/elements/div.rs              |  9 +++
crates/gpui2/src/elements/img.rs              |  2 
crates/gpui2/src/scene.rs                     | 53 ---------------------
crates/gpui2/src/window.rs                    | 53 +++++++++++---------
crates/terminal_view2/src/terminal_element.rs | 10 +-
crates/ui2/src/components/tab_bar.rs          |  1 
crates/workspace2/src/toolbar.rs              |  1 
8 files changed, 47 insertions(+), 83 deletions(-)

Detailed changes

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

@@ -1447,7 +1447,14 @@ 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)
+                    })
                 },
             );
 

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

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use crate::{
-    point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
+    point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
     InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size,
     StyleRefinement, Styled, WindowContext,
 };

crates/gpui2/src/scene.rs 🔗

@@ -856,56 +856,3 @@ impl Bounds<ScaledPixels> {
         .expect("Polygon should not be empty")
     }
 }
-
-// #[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(),
-//         }
-//     }
-// }

crates/gpui2/src/window.rs 🔗

@@ -39,24 +39,32 @@ 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<[u8; 64]>,
+    id: u32,
 }
 
-impl Default for StackingOrder {
-    fn default() -> Self {
-        StackingOrder {
-            z_indices: SmallVec::new(),
+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();
+        write!(f, "[({}): ", self.id)?;
+        while let Some(z_index) = stacks.next() {
+            write!(f, "{z_index}")?;
+            if stacks.peek().is_some() {
+                write!(f, "->")?;
+            }
         }
+        write!(f, "]")?;
+        Ok(())
     }
 }
 
@@ -284,6 +292,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) next_stacking_order_id: u32,
     content_mask_stack: Vec<ContentMask<Pixels>>,
     element_offset_stack: Vec<Point<Pixels>>,
 }
@@ -297,6 +306,7 @@ impl Frame {
             dispatch_tree,
             scene_builder: SceneBuilder::default(),
             z_index_stack: StackingOrder::default(),
+            next_stacking_order_id: 0,
             depth_map: Default::default(),
             content_mask_stack: Vec::new(),
             element_offset_stack: Vec::new(),
@@ -308,6 +318,7 @@ impl Frame {
         self.mouse_listeners.values_mut().for_each(Vec::clear);
         self.dispatch_tree.clear();
         self.depth_map.clear();
+        self.next_stacking_order_id = 0;
     }
 
     fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
@@ -928,15 +939,6 @@ impl<'a> WindowContext<'a> {
         self.window.requested_cursor_style = Some(style)
     }
 
-    /// 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 result = f(self);
-        self.window.next_frame.z_index_stack.pop();
-        result
-    }
-
     /// Called during painting to track which z-index is on top at each pixel position
     pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
         let stacking_order = self.window.next_frame.z_index_stack.clone();
@@ -2046,6 +2048,18 @@ 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 new_stacking_order_id =
+            post_inc(&mut self.window_mut().next_frame.next_stacking_order_id);
+        self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id;
+        self.window_mut().next_frame.z_index_stack.push(z_index);
+        let result = f(self);
+        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 +2283,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/terminal_view2/src/terminal_element.rs 🔗

@@ -1,11 +1,11 @@
 use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
 use gpui::{
     black, div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
-    Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, FontStyle,
-    FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, Interactivity,
-    IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
-    PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
-    TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
+    BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
+    FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
+    Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton,
+    Pixels, PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement,
+    Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
 };
 use itertools::Itertools;
 use language::CursorShape;

crates/workspace2/src/toolbar.rs 🔗

@@ -105,6 +105,7 @@ impl Render for Toolbar {
         v_stack()
             .p_1()
             .gap_2()
+            .z_index(80) // todo!("z-index")
             .border_b()
             .border_color(cx.theme().colors().border_variant)
             .bg(cx.theme().colors().toolbar_background)