terminal_button.rs

  1use context_menu::{ContextMenu, ContextMenuItem};
  2use gpui::{
  3    elements::*, impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton,
  4    MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakModelHandle,
  5    WeakViewHandle,
  6};
  7use settings::Settings;
  8use std::any::TypeId;
  9use terminal::Terminal;
 10use workspace::{dock::FocusDock, item::ItemHandle, NewTerminal, StatusItemView, Workspace};
 11
 12use crate::TerminalView;
 13
 14#[derive(Clone, PartialEq)]
 15pub struct DeployTerminalMenu;
 16
 17#[derive(Clone, PartialEq)]
 18pub struct FocusTerminal {
 19    terminal_handle: WeakModelHandle<Terminal>,
 20}
 21
 22impl_internal_actions!(terminal, [FocusTerminal, DeployTerminalMenu]);
 23
 24pub fn init(cx: &mut MutableAppContext) {
 25    cx.add_action(TerminalButton::deploy_terminal_menu);
 26    cx.add_action(TerminalButton::focus_terminal);
 27}
 28
 29pub struct TerminalButton {
 30    workspace: WeakViewHandle<Workspace>,
 31    popup_menu: ViewHandle<ContextMenu>,
 32}
 33
 34impl Entity for TerminalButton {
 35    type Event = ();
 36}
 37
 38impl View for TerminalButton {
 39    fn ui_name() -> &'static str {
 40        "TerminalButton"
 41    }
 42
 43    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
 44        let workspace = self.workspace.upgrade(cx);
 45        let project = match workspace {
 46            Some(workspace) => workspace.read(cx).project().read(cx),
 47            None => return Empty::new().boxed(),
 48        };
 49
 50        let focused_view = cx.focused_view_id(cx.window_id());
 51        let active = focused_view
 52            .map(|view_id| {
 53                cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>())
 54            })
 55            .unwrap_or(false);
 56
 57        let has_terminals = !project.local_terminal_handles().is_empty();
 58        let terminal_count = project.local_terminal_handles().len() as i32;
 59        let theme = cx.global::<Settings>().theme.clone();
 60
 61        Stack::new()
 62            .with_child(
 63                MouseEventHandler::<Self>::new(0, cx, {
 64                    let theme = theme.clone();
 65                    move |state, _cx| {
 66                        let style = theme
 67                            .workspace
 68                            .status_bar
 69                            .sidebar_buttons
 70                            .item
 71                            .style_for(state, active);
 72
 73                        Flex::row()
 74                            .with_child(
 75                                Svg::new("icons/terminal_12.svg")
 76                                    .with_color(style.icon_color)
 77                                    .constrained()
 78                                    .with_width(style.icon_size)
 79                                    .aligned()
 80                                    .named("terminals-icon"),
 81                            )
 82                            .with_children(has_terminals.then(|| {
 83                                Label::new(terminal_count.to_string(), style.label.text.clone())
 84                                    .contained()
 85                                    .with_style(style.label.container)
 86                                    .aligned()
 87                                    .boxed()
 88                            }))
 89                            .constrained()
 90                            .with_height(style.icon_size)
 91                            .contained()
 92                            .with_style(style.container)
 93                            .boxed()
 94                    }
 95                })
 96                .with_cursor_style(CursorStyle::PointingHand)
 97                .on_click(MouseButton::Left, move |_, cx| {
 98                    if has_terminals {
 99                        cx.dispatch_action(DeployTerminalMenu);
100                    } else {
101                        if !active {
102                            cx.dispatch_action(FocusDock);
103                        }
104                    };
105                })
106                .with_tooltip::<Self, _>(
107                    0,
108                    "Show Terminal".into(),
109                    Some(Box::new(FocusDock)),
110                    theme.tooltip.clone(),
111                    cx,
112                )
113                .boxed(),
114            )
115            .with_child(
116                ChildView::new(&self.popup_menu, cx)
117                    .aligned()
118                    .top()
119                    .right()
120                    .boxed(),
121            )
122            .boxed()
123    }
124}
125
126impl TerminalButton {
127    pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
128        cx.observe(&workspace, |_, _, cx| cx.notify()).detach();
129        Self {
130            workspace: workspace.downgrade(),
131            popup_menu: cx.add_view(|cx| {
132                let mut menu = ContextMenu::new(cx);
133                menu.set_position_mode(OverlayPositionMode::Local);
134                menu
135            }),
136        }
137    }
138
139    pub fn deploy_terminal_menu(
140        &mut self,
141        _action: &DeployTerminalMenu,
142        cx: &mut ViewContext<Self>,
143    ) {
144        let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)];
145
146        if let Some(workspace) = self.workspace.upgrade(cx) {
147            let project = workspace.read(cx).project().read(cx);
148            let local_terminal_handles = project.local_terminal_handles();
149
150            if !local_terminal_handles.is_empty() {
151                menu_options.push(ContextMenuItem::Separator)
152            }
153
154            for local_terminal_handle in local_terminal_handles {
155                if let Some(terminal) = local_terminal_handle.upgrade(cx) {
156                    menu_options.push(ContextMenuItem::item(
157                        terminal.read(cx).title(),
158                        FocusTerminal {
159                            terminal_handle: local_terminal_handle.clone(),
160                        },
161                    ))
162                }
163            }
164        }
165
166        self.popup_menu.update(cx, |menu, cx| {
167            menu.show(
168                Default::default(),
169                AnchorCorner::BottomRight,
170                menu_options,
171                cx,
172            );
173        });
174    }
175
176    pub fn focus_terminal(&mut self, action: &FocusTerminal, cx: &mut ViewContext<Self>) {
177        if let Some(workspace) = self.workspace.upgrade(cx) {
178            workspace.update(cx, |workspace, cx| {
179                let terminal = workspace
180                    .items_of_type::<TerminalView>(cx)
181                    .find(|terminal| {
182                        terminal.read(cx).model().downgrade() == action.terminal_handle
183                    });
184                if let Some(terminal) = terminal {
185                    workspace.activate_item(&terminal, cx);
186                }
187            });
188        }
189    }
190}
191
192impl StatusItemView for TerminalButton {
193    fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
194        cx.notify();
195    }
196}