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 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(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}