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}