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