1use gpui::{
2 actions,
3 elements::{ChildView, Container, FlexItem, Margin, MouseEventHandler, Svg},
4 impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton,
5 MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
6};
7use serde::Deserialize;
8use settings::{DockAnchor, Settings};
9use theme::Theme;
10
11use crate::{ItemHandle, Pane, StatusItemView, Workspace};
12
13#[derive(PartialEq, Clone, Deserialize)]
14pub struct MoveDock(pub DockAnchor);
15
16#[derive(PartialEq, Clone)]
17pub struct AddDefaultItemToDock;
18
19actions!(workspace, [ToggleDock]);
20impl_internal_actions!(workspace, [MoveDock, AddDefaultItemToDock]);
21
22pub fn init(cx: &mut MutableAppContext) {
23 cx.add_action(Dock::toggle);
24 cx.add_action(Dock::move_dock);
25}
26
27#[derive(Copy, Clone)]
28pub enum DockPosition {
29 Shown(DockAnchor),
30 Hidden(DockAnchor),
31}
32
33impl Default for DockPosition {
34 fn default() -> Self {
35 DockPosition::Hidden(Default::default())
36 }
37}
38
39impl DockPosition {
40 fn toggle(self) -> Self {
41 match self {
42 DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
43 DockPosition::Hidden(anchor) => DockPosition::Shown(anchor),
44 }
45 }
46
47 fn visible(&self) -> Option<DockAnchor> {
48 match self {
49 DockPosition::Shown(anchor) => Some(*anchor),
50 DockPosition::Hidden(_) => None,
51 }
52 }
53
54 fn hide(self) -> Self {
55 match self {
56 DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
57 DockPosition::Hidden(_) => self,
58 }
59 }
60}
61
62pub type DefaultItemFactory =
63 fn(&mut Workspace, &mut ViewContext<Workspace>) -> Box<dyn ItemHandle>;
64
65pub struct Dock {
66 position: DockPosition,
67 pane: ViewHandle<Pane>,
68 default_item_factory: DefaultItemFactory,
69}
70
71impl Dock {
72 pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
73 let pane = cx.add_view(|cx| Pane::new(true, cx));
74 let pane_id = pane.id();
75 cx.subscribe(&pane, move |workspace, _, event, cx| {
76 workspace.handle_pane_event(pane_id, event, cx);
77 })
78 .detach();
79
80 Self {
81 pane,
82 position: DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor),
83 default_item_factory,
84 }
85 }
86
87 pub fn pane(&self) -> &ViewHandle<Pane> {
88 &self.pane
89 }
90
91 pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
92 self.position.visible().map(|_| self.pane())
93 }
94
95 fn set_dock_position(
96 workspace: &mut Workspace,
97 new_position: DockPosition,
98 cx: &mut ViewContext<Workspace>,
99 ) {
100 workspace.dock.position = new_position;
101 let now_visible = workspace.dock.visible_pane().is_some();
102 if now_visible {
103 // Ensure that the pane has at least one item or construct a default item to put in it
104 let pane = workspace.dock.pane.clone();
105 if pane.read(cx).items().next().is_none() {
106 let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
107 Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
108 }
109 cx.focus(pane);
110 } else {
111 if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() {
112 cx.focus(last_active_center_pane);
113 }
114 }
115 cx.notify();
116 }
117
118 pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
119 Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
120 }
121
122 fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext<Workspace>) {
123 Self::set_dock_position(workspace, workspace.dock.position.toggle(), cx);
124 }
125
126 fn move_dock(
127 workspace: &mut Workspace,
128 &MoveDock(new_anchor): &MoveDock,
129 cx: &mut ViewContext<Workspace>,
130 ) {
131 Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
132 }
133
134 pub fn render(
135 &self,
136 theme: &Theme,
137 anchor: DockAnchor,
138 cx: &mut RenderContext<Workspace>,
139 ) -> Option<ElementBox> {
140 let style = &theme.workspace.dock;
141 self.position
142 .visible()
143 .filter(|current_anchor| *current_anchor == anchor)
144 .map(|anchor| match anchor {
145 DockAnchor::Bottom | DockAnchor::Right => {
146 let mut panel_style = style.panel.clone();
147 if anchor == DockAnchor::Bottom {
148 panel_style.margin = Margin {
149 top: panel_style.margin.top,
150 ..Default::default()
151 };
152 } else {
153 panel_style.margin = Margin {
154 left: panel_style.margin.left,
155 ..Default::default()
156 };
157 }
158 FlexItem::new(
159 Container::new(ChildView::new(self.pane.clone()).boxed())
160 .with_style(style.panel)
161 .boxed(),
162 )
163 .flex(style.flex, true)
164 .boxed()
165 }
166 DockAnchor::Expanded => Container::new(
167 MouseEventHandler::new::<Dock, _, _>(0, cx, |_state, _cx| {
168 Container::new(ChildView::new(self.pane.clone()).boxed())
169 .with_style(style.maximized)
170 .boxed()
171 })
172 .capture_all()
173 .with_cursor_style(CursorStyle::Arrow)
174 .boxed(),
175 )
176 .with_background_color(style.wash_color)
177 .boxed(),
178 })
179 }
180}
181
182pub struct ToggleDockButton {
183 workspace: WeakViewHandle<Workspace>,
184}
185
186impl ToggleDockButton {
187 pub fn new(workspace: WeakViewHandle<Workspace>, _cx: &mut ViewContext<Self>) -> Self {
188 Self { workspace }
189 }
190}
191
192impl Entity for ToggleDockButton {
193 type Event = ();
194}
195
196impl View for ToggleDockButton {
197 fn ui_name() -> &'static str {
198 "Dock Toggle"
199 }
200
201 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
202 let dock_is_open = self
203 .workspace
204 .upgrade(cx)
205 .map(|workspace| workspace.read(cx).dock.position.visible().is_some())
206 .unwrap_or(false);
207
208 MouseEventHandler::new::<Self, _, _>(0, cx, |state, cx| {
209 let theme = &cx
210 .global::<Settings>()
211 .theme
212 .workspace
213 .status_bar
214 .sidebar_buttons;
215 let style = theme.item.style_for(state, dock_is_open);
216
217 Svg::new("icons/terminal_16.svg")
218 .with_color(style.icon_color)
219 .constrained()
220 .with_width(style.icon_size)
221 .with_height(style.icon_size)
222 .contained()
223 .with_style(style.container)
224 .boxed()
225 })
226 .with_cursor_style(CursorStyle::PointingHand)
227 .on_click(MouseButton::Left, |_, cx| {
228 cx.dispatch_action(ToggleDock);
229 })
230 // TODO: Add tooltip
231 .boxed()
232 }
233}
234
235impl StatusItemView for ToggleDockButton {
236 fn set_active_pane_item(
237 &mut self,
238 _active_pane_item: Option<&dyn crate::ItemHandle>,
239 _cx: &mut ViewContext<Self>,
240 ) {
241 //Not applicable
242 }
243}