Close tabs when X is clicked

Nathan Sobo and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

gpui/src/elements/mouse_event_handler.rs | 16 +++++++++++++++-
zed/src/workspace/pane.rs                | 19 ++++++++++++++-----
2 files changed, 29 insertions(+), 6 deletions(-)

Detailed changes

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -8,6 +8,7 @@ use serde_json::json;
 pub struct MouseEventHandler {
     state: ValueHandle<MouseState>,
     child: ElementBox,
+    click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
 }
 
 #[derive(Clone, Copy, Debug, Default)]
@@ -28,8 +29,14 @@ impl MouseEventHandler {
         Self {
             state: state_handle,
             child,
+            click_handler: None,
         }
     }
+
+    pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
+        self.click_handler = Some(Box::new(handler));
+        self
+    }
 }
 
 impl Element for MouseEventHandler {
@@ -70,6 +77,8 @@ impl Element for MouseEventHandler {
         _: &mut Self::PaintState,
         ctx: &mut EventContext,
     ) -> bool {
+        let click_handler = self.click_handler.as_mut();
+
         let handled_in_child = self.child.dispatch_event(event, ctx);
 
         self.state.update(ctx.app, |state| match event {
@@ -92,10 +101,15 @@ impl Element for MouseEventHandler {
                     handled_in_child
                 }
             }
-            Event::LeftMouseUp { .. } => {
+            Event::LeftMouseUp { position, .. } => {
                 if !handled_in_child && state.clicked {
                     state.clicked = false;
                     ctx.notify();
+                    if let Some(handler) = click_handler {
+                        if bounds.contains_point(*position) {
+                            handler(ctx);
+                        }
+                    }
                     true
                 } else {
                     handled_in_child

zed/src/workspace/pane.rs 🔗

@@ -25,6 +25,12 @@ pub fn init(app: &mut MutableAppContext) {
     app.add_action("pane:close_active_item", |pane: &mut Pane, _: &(), ctx| {
         pane.close_active_item(ctx);
     });
+    app.add_action(
+        "pane:close_item",
+        |pane: &mut Pane, item_id: &usize, ctx| {
+            pane.close_item(*item_id, ctx);
+        },
+    );
     app.add_action("pane:split_up", |pane: &mut Pane, _: &(), ctx| {
         pane.split(SplitDirection::Up, ctx);
     });
@@ -155,15 +161,17 @@ impl Pane {
 
     pub fn close_active_item(&mut self, ctx: &mut ViewContext<Self>) {
         if !self.items.is_empty() {
-            self.items.remove(self.active_item);
-            if self.active_item >= self.items.len() {
-                self.active_item = self.items.len().saturating_sub(1);
-            }
-            ctx.notify();
+            self.close_item(self.items[self.active_item].id(), ctx)
         }
+    }
+
+    pub fn close_item(&mut self, item_id: usize, ctx: &mut ViewContext<Self>) {
+        self.items.retain(|item| item.id() != item_id);
+        self.active_item = cmp::min(self.active_item, self.items.len().saturating_sub(1));
         if self.items.is_empty() {
             ctx.emit(Event::Remove);
         }
+        ctx.notify();
     }
 
     fn focus_active_item(&mut self, ctx: &mut ViewContext<Self>) {
@@ -316,6 +324,7 @@ impl Pane {
                     icon.boxed()
                 }
             })
+            .on_click(move |ctx| ctx.dispatch_action("pane:close_item", item_id))
             .named("close-tab-icon")
         } else {
             let diameter = 8.;