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}