Start on rendering context menu in editor2

Antonio Scandurra , Nathan , and Mikayla created

Co-Authored-By: Nathan <nathan@zed.dev>
Co-Authored-By: Mikayla <mikayla@zed.dev>

Change summary

crates/editor2/src/editor.rs  |  59 ++++++++---------
crates/editor2/src/element.rs | 118 ++++++++++++++++++------------------
crates/gpui2/src/window.rs    |  22 ++++--
3 files changed, 100 insertions(+), 99 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -920,15 +920,14 @@ impl ContextMenu {
     fn render(
         &self,
         cursor_position: DisplayPoint,
-        style: EditorStyle,
+        style: &EditorStyle,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
     ) -> (DisplayPoint, AnyElement<Editor>) {
-        todo!()
-        // match self {
-        //     ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
-        //     ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
-        // }
+        match self {
+            ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
+            ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
+        }
     }
 }
 
@@ -1253,13 +1252,13 @@ impl CompletionsMenu {
 
     fn render(
         &self,
-        style: EditorStyle,
+        style: &EditorStyle,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,
-    ) {
+    ) -> AnyElement<Editor> {
         todo!("old implementation below")
     }
-    // ) -> AnyElement<Editor> {
+
     //     enum CompletionTag {}
 
     //     let settings = EditorSettings>(cx);
@@ -1572,7 +1571,7 @@ impl CodeActionsMenu {
     fn render(
         &self,
         mut cursor_position: DisplayPoint,
-        style: EditorStyle,
+        style: &EditorStyle,
         cx: &mut ViewContext<Editor>,
     ) -> (DisplayPoint, AnyElement<Editor>) {
         todo!("old version below")
@@ -4480,29 +4479,27 @@ impl Editor {
     //     }
 
     pub fn context_menu_visible(&self) -> bool {
-        false
-        // todo!("context menu")
-        // self.context_menu
-        //     .read()
-        //     .as_ref()
-        //     .map_or(false, |menu| menu.visible())
+        self.context_menu
+            .read()
+            .as_ref()
+            .map_or(false, |menu| menu.visible())
     }
 
-    //     pub fn render_context_menu(
-    //         &self,
-    //         cursor_position: DisplayPoint,
-    //         style: EditorStyle,
-    //         cx: &mut ViewContext<Editor>,
-    //     ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
-    //         self.context_menu.read().as_ref().map(|menu| {
-    //             menu.render(
-    //                 cursor_position,
-    //                 style,
-    //                 self.workspace.as_ref().map(|(w, _)| w.clone()),
-    //                 cx,
-    //             )
-    //         })
-    //     }
+    pub fn render_context_menu(
+        &self,
+        cursor_position: DisplayPoint,
+        style: &EditorStyle,
+        cx: &mut ViewContext<Editor>,
+    ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
+        self.context_menu.read().as_ref().map(|menu| {
+            menu.render(
+                cursor_position,
+                style,
+                self.workspace.as_ref().map(|(w, _)| w.clone()),
+                cx,
+            )
+        })
+    }
 
     fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
         cx.notify();

crates/editor2/src/element.rs 🔗

@@ -603,7 +603,7 @@ impl EditorElement {
     fn paint_text(
         &mut self,
         bounds: Bounds<Pixels>,
-        layout: &LayoutState,
+        layout: &mut LayoutState,
         editor: &mut Editor,
         cx: &mut ViewContext<Editor>,
     ) {
@@ -794,48 +794,46 @@ impl EditorElement {
                 )
             }
 
-            cx.stack(0, |cx| {
+            cx.with_z_index(0, |cx| {
                 for cursor in cursors {
                     cursor.paint(content_origin, cx);
                 }
             });
-            // cx.scene().push_layer(Some(bounds));
-
-            // cx.scene().pop_layer();
-
-            // if let Some((position, context_menu)) = layout.context_menu.as_mut() {
-            //     cx.scene().push_stacking_context(None, None);
-            //     let cursor_row_layout =
-            //         &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-            //     let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
-            //     let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
-            //     let mut list_origin = content_origin + point(x, y);
-            //     let list_width = context_menu.size().x;
-            //     let list_height = context_menu.size().y;
-
-            //     // Snap the right edge of the list to the right edge of the window if
-            //     // its horizontal bounds overflow.
-            //     if list_origin.x + list_width > cx.window_size().x {
-            //         list_origin.set_x((cx.window_size().x - list_width).max(0.));
-            //     }
 
-            //     if list_origin.y + list_height > bounds.max_y {
-            //         list_origin
-            //             .set_y(list_origin.y - layout.position_map.line_height - list_height);
-            //     }
+            if let Some((position, context_menu)) = layout.context_menu.as_mut() {
+                cx.with_z_index(1, |cx| {
+                    let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
+                    let available_space = size(
+                        AvailableSpace::Definite(cx.viewport_size().width * 0.7),
+                        AvailableSpace::Definite(
+                            (12. * line_height).min((bounds.size.height - line_height) / 2.),
+                        ),
+                    );
+                    let context_menu_size = context_menu.measure(available_space, editor, cx);
+
+                    let cursor_row_layout = &layout.position_map.line_layouts
+                        [(position.row() - start_row) as usize]
+                        .line;
+                    let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
+                    let y =
+                        (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
+                    let mut list_origin = content_origin + point(x, y);
+                    let list_width = context_menu_size.width;
+                    let list_height = context_menu_size.height;
+
+                    // Snap the right edge of the list to the right edge of the window if
+                    // its horizontal bounds overflow.
+                    if list_origin.x + list_width > cx.viewport_size().width {
+                        list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
+                    }
 
-            //     context_menu.paint(
-            //         list_origin,
-            //         Bounds::<Pixels>::from_points(
-            //             gpui::Point::<Pixels>::zero(),
-            //             point(f32::MAX, f32::MAX),
-            //         ), // Let content bleed outside of editor
-            //         editor,
-            //         cx,
-            //     );
+                    if list_origin.y + list_height > bounds.lower_right().y {
+                        list_origin.y -= layout.position_map.line_height - list_height;
+                    }
 
-            //     cx.scene().pop_stacking_context();
-            // }
+                    context_menu.draw(list_origin, available_space, editor, cx);
+                })
+            }
 
             // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
             //     cx.scene().push_stacking_context(None, None);
@@ -1781,15 +1779,14 @@ impl EditorElement {
             snapshot = editor.snapshot(cx);
         }
 
-        // todo!("context menu")
-        // let mut context_menu = None;
+        let mut context_menu = None;
         let mut code_actions_indicator = None;
         if let Some(newest_selection_head) = newest_selection_head {
             if (start_row..end_row).contains(&newest_selection_head.row()) {
-                //         if editor.context_menu_visible() {
-                //             context_menu =
-                //                 editor.render_context_menu(newest_selection_head, style.clone(), cx);
-                //         }
+                if editor.context_menu_visible() {
+                    context_menu =
+                        editor.render_context_menu(newest_selection_head, &self.style, cx);
+                }
 
                 let active = matches!(
                     editor.context_menu.read().as_ref(),
@@ -1939,7 +1936,7 @@ impl EditorElement {
             display_hunks,
             // blocks,
             selections,
-            // context_menu,
+            context_menu,
             code_actions_indicator,
             // fold_indicators,
             tab_invisible,
@@ -2501,21 +2498,24 @@ impl Element<Editor> for EditorElement {
             size: layout.text_size,
         };
 
-        cx.with_content_mask(ContentMask { bounds }, |cx| {
-            self.paint_mouse_listeners(
-                bounds,
-                gutter_bounds,
-                text_bounds,
-                &layout.position_map,
-                cx,
-            );
-            self.paint_background(gutter_bounds, text_bounds, &layout, cx);
-            if layout.gutter_size.width > Pixels::ZERO {
-                self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
-            }
-            self.paint_text(text_bounds, &layout, editor, cx);
-            let input_handler = ElementInputHandler::new(bounds, cx);
-            cx.handle_input(&editor.focus_handle, input_handler);
+        // We call with_z_index to establish a new stacking context.
+        cx.with_z_index(0, |cx| {
+            cx.with_content_mask(ContentMask { bounds }, |cx| {
+                self.paint_mouse_listeners(
+                    bounds,
+                    gutter_bounds,
+                    text_bounds,
+                    &layout.position_map,
+                    cx,
+                );
+                self.paint_background(gutter_bounds, text_bounds, &layout, cx);
+                if layout.gutter_size.width > Pixels::ZERO {
+                    self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
+                }
+                self.paint_text(text_bounds, &mut layout, editor, cx);
+                let input_handler = ElementInputHandler::new(bounds, cx);
+                cx.handle_input(&editor.focus_handle, input_handler);
+            });
         });
     }
 }
@@ -3141,7 +3141,7 @@ pub struct LayoutState {
     show_scrollbars: bool,
     is_singleton: bool,
     max_row: u32,
-    // context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
+    context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
     code_actions_indicator: Option<CodeActionsIndicator>,
     // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
     // fold_indicators: Vec<Option<AnyElement<Editor>>>,

crates/gpui2/src/window.rs 🔗

@@ -200,7 +200,7 @@ pub struct Window {
     display_id: DisplayId,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
-    content_size: Size<Pixels>,
+    viewport_size: Size<Pixels>,
     pub(crate) layout_engine: TaffyLayoutEngine,
     pub(crate) root_view: Option<AnyView>,
     pub(crate) element_id_stack: GlobalElementId,
@@ -299,7 +299,7 @@ impl Window {
             display_id,
             sprite_atlas,
             rem_size: px(16.),
-            content_size,
+            viewport_size: content_size,
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             element_id_stack: GlobalElementId::default(),
@@ -609,7 +609,7 @@ impl<'a> WindowContext<'a> {
 
     fn window_bounds_changed(&mut self) {
         self.window.scale_factor = self.window.platform_window.scale_factor();
-        self.window.content_size = self.window.platform_window.content_size();
+        self.window.viewport_size = self.window.platform_window.content_size();
         self.window.bounds = self.window.platform_window.bounds();
         self.window.display_id = self.window.platform_window.display().id();
         self.window.dirty = true;
@@ -624,6 +624,10 @@ impl<'a> WindowContext<'a> {
         self.window.bounds
     }
 
+    pub fn viewport_size(&self) -> Size<Pixels> {
+        self.window.viewport_size
+    }
+
     pub fn is_window_active(&self) -> bool {
         self.window.active
     }
@@ -717,7 +721,7 @@ 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 stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+    pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
         self.window.current_frame.z_index_stack.push(z_index);
         let result = f(self);
         self.window.current_frame.z_index_stack.pop();
@@ -1015,13 +1019,13 @@ impl<'a> WindowContext<'a> {
 
         self.start_frame();
 
-        self.stack(0, |cx| {
-            let available_space = cx.window.content_size.map(Into::into);
+        self.with_z_index(0, |cx| {
+            let available_space = cx.window.viewport_size.map(Into::into);
             root_view.draw(available_space, cx);
         });
 
         if let Some(active_drag) = self.app.active_drag.take() {
-            self.stack(1, |cx| {
+            self.with_z_index(1, |cx| {
                 let offset = cx.mouse_position() - active_drag.cursor_offset;
                 cx.with_element_offset(Some(offset), |cx| {
                     let available_space =
@@ -1031,7 +1035,7 @@ impl<'a> WindowContext<'a> {
                 });
             });
         } else if let Some(active_tooltip) = self.app.active_tooltip.take() {
-            self.stack(1, |cx| {
+            self.with_z_index(1, |cx| {
                 cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
                     let available_space =
                         size(AvailableSpace::MinContent, AvailableSpace::MinContent);
@@ -1686,7 +1690,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
             .unwrap_or_else(|| ContentMask {
                 bounds: Bounds {
                     origin: Point::default(),
-                    size: self.window().content_size,
+                    size: self.window().viewport_size,
                 },
             })
     }