Allow customization of `Pane` tab bar buttons

Antonio Scandurra created

Change summary

crates/terminal_view/src/terminal_panel.rs |  72 ++++++++----
crates/workspace/src/pane.rs               | 128 ++++++++++++-----------
2 files changed, 114 insertions(+), 86 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -21,6 +21,7 @@ pub struct TerminalPanel {
 
 impl TerminalPanel {
     pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
+        let this = cx.weak_handle();
         let pane = cx.add_view(|cx| {
             let window_id = cx.window_id();
             let mut pane = Pane::new(
@@ -36,6 +37,23 @@ impl TerminalPanel {
                         item.handle.act_as::<TerminalView>(cx).is_some()
                     })
             });
+            pane.set_render_tab_bar_buttons(cx, move |_, cx| {
+                let this = this.clone();
+                Pane::render_tab_bar_button(
+                    0,
+                    "icons/plus_12.svg",
+                    cx,
+                    move |_, cx| {
+                        let this = this.clone();
+                        cx.window_context().defer(move |cx| {
+                            if let Some(this) = this.upgrade(cx) {
+                                this.update(cx, |this, cx| this.add_terminal(cx));
+                            }
+                        })
+                    },
+                    None,
+                )
+            });
             pane
         });
         let subscriptions = vec![
@@ -61,6 +79,33 @@ impl TerminalPanel {
             _ => {}
         }
     }
+
+    fn add_terminal(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(workspace) = self.workspace.upgrade(cx) {
+            let working_directory_strategy = cx
+                .global::<Settings>()
+                .terminal_overrides
+                .working_directory
+                .clone()
+                .unwrap_or(WorkingDirectory::CurrentProjectDirectory);
+            let working_directory =
+                crate::get_working_directory(workspace.read(cx), cx, working_directory_strategy);
+            let window_id = cx.window_id();
+            if let Some(terminal) = self.project.update(cx, |project, cx| {
+                project
+                    .create_terminal(working_directory, window_id, cx)
+                    .log_err()
+            }) {
+                workspace.update(cx, |workspace, cx| {
+                    let terminal =
+                        Box::new(cx.add_view(|cx| {
+                            TerminalView::new(terminal, workspace.database_id(), cx)
+                        }));
+                    Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx);
+                });
+            }
+        }
+    }
 }
 
 impl Entity for TerminalPanel {
@@ -78,32 +123,7 @@ impl View for TerminalPanel {
 
     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
         if self.pane.read(cx).items_len() == 0 {
-            if let Some(workspace) = self.workspace.upgrade(cx) {
-                let working_directory_strategy = cx
-                    .global::<Settings>()
-                    .terminal_overrides
-                    .working_directory
-                    .clone()
-                    .unwrap_or(WorkingDirectory::CurrentProjectDirectory);
-                let working_directory = crate::get_working_directory(
-                    workspace.read(cx),
-                    cx,
-                    working_directory_strategy,
-                );
-                let window_id = cx.window_id();
-                if let Some(terminal) = self.project.update(cx, |project, cx| {
-                    project
-                        .create_terminal(working_directory, window_id, cx)
-                        .log_err()
-                }) {
-                    workspace.update(cx, |workspace, cx| {
-                        let terminal = Box::new(cx.add_view(|cx| {
-                            TerminalView::new(terminal, workspace.database_id(), cx)
-                        }));
-                        Pane::add_item(workspace, &self.pane, terminal, true, true, None, cx);
-                    });
-                }
-            }
+            self.add_terminal(cx)
         }
     }
 }

crates/workspace/src/pane.rs 🔗

@@ -150,6 +150,7 @@ pub struct Pane {
     has_focus: bool,
     can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
     can_split: bool,
+    render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
 }
 
 pub struct ItemNavHistory {
@@ -257,6 +258,27 @@ impl Pane {
             has_focus: false,
             can_drop: Rc::new(|_, _| true),
             can_split: true,
+            render_tab_bar_buttons: Rc::new(|pane, cx| {
+                Flex::row()
+                    // New menu
+                    .with_child(Self::render_tab_bar_button(
+                        0,
+                        "icons/plus_12.svg",
+                        cx,
+                        |pane, cx| pane.deploy_new_menu(cx),
+                        pane.tab_bar_context_menu
+                            .handle_if_kind(TabBarContextMenuKind::New),
+                    ))
+                    .with_child(Self::render_tab_bar_button(
+                        2,
+                        "icons/split_12.svg",
+                        cx,
+                        |pane, cx| pane.deploy_split_menu(cx),
+                        pane.tab_bar_context_menu
+                            .handle_if_kind(TabBarContextMenuKind::Split),
+                    ))
+                    .into_any()
+            }),
         }
     }
 
@@ -289,6 +311,14 @@ impl Pane {
         cx.notify();
     }
 
+    pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
+    where
+        F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
+    {
+        self.render_tab_bar_buttons = Rc::new(render);
+        cx.notify();
+    }
+
     pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
         ItemNavHistory {
             history: self.nav_history.clone(),
@@ -1475,33 +1505,37 @@ impl Pane {
             .into_any()
     }
 
-    fn render_tab_bar_buttons(
-        &mut self,
-        theme: &Theme,
-        cx: &mut ViewContext<Self>,
-    ) -> AnyElement<Self> {
-        Flex::row()
-            // New menu
-            .with_child(render_tab_bar_button(
-                0,
-                "icons/plus_12.svg",
-                cx,
-                |pane, cx| pane.deploy_new_menu(cx),
-                self.tab_bar_context_menu
-                    .handle_if_kind(TabBarContextMenuKind::New),
-            ))
-            .with_child(render_tab_bar_button(
-                2,
-                "icons/split_12.svg",
-                cx,
-                |pane, cx| pane.deploy_split_menu(cx),
-                self.tab_bar_context_menu
-                    .handle_if_kind(TabBarContextMenuKind::Split),
-            ))
-            .contained()
-            .with_style(theme.workspace.tab_bar.pane_button_container)
+    pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
+        index: usize,
+        icon: &'static str,
+        cx: &mut ViewContext<Pane>,
+        on_click: F,
+        context_menu: Option<ViewHandle<ContextMenu>>,
+    ) -> AnyElement<Pane> {
+        enum TabBarButton {}
+
+        Stack::new()
+            .with_child(
+                MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
+                    let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
+                    let style = theme.pane_button.style_for(mouse_state, false);
+                    Svg::new(icon)
+                        .with_color(style.color)
+                        .constrained()
+                        .with_width(style.icon_width)
+                        .aligned()
+                        .constrained()
+                        .with_width(style.button_width)
+                        .with_height(style.button_width)
+                })
+                .with_cursor_style(CursorStyle::PointingHand)
+                .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)),
+            )
+            .with_children(
+                context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
+            )
             .flex(1., false)
-            .into_any()
+            .into_any_named("tab bar button")
     }
 
     fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
@@ -1554,7 +1588,14 @@ impl View for Pane {
                             .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
 
                         if self.is_active {
-                            tab_row.add_child(self.render_tab_bar_buttons(&theme, cx))
+                            let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
+                            tab_row.add_child(
+                                (render_tab_bar_buttons)(self, cx)
+                                    .contained()
+                                    .with_style(theme.workspace.tab_bar.pane_button_container)
+                                    .flex(1., false)
+                                    .into_any(),
+                            )
                         }
 
                         stack.add_child(tab_row);
@@ -1676,39 +1717,6 @@ impl View for Pane {
     }
 }
 
-fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
-    index: usize,
-    icon: &'static str,
-    cx: &mut ViewContext<Pane>,
-    on_click: F,
-    context_menu: Option<ViewHandle<ContextMenu>>,
-) -> AnyElement<Pane> {
-    enum TabBarButton {}
-
-    Stack::new()
-        .with_child(
-            MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
-                let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
-                let style = theme.pane_button.style_for(mouse_state, false);
-                Svg::new(icon)
-                    .with_color(style.color)
-                    .constrained()
-                    .with_width(style.icon_width)
-                    .aligned()
-                    .constrained()
-                    .with_width(style.button_width)
-                    .with_height(style.button_width)
-            })
-            .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)),
-        )
-        .with_children(
-            context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
-        )
-        .flex(1., false)
-        .into_any_named("tab bar button")
-}
-
 impl ItemNavHistory {
     pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut WindowContext) {
         self.history.borrow_mut().push(data, self.item.clone(), cx);