terminal_button.rs

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