1use gpui::{
2 actions,
3 elements::{ChildView, Container, Empty, FlexItem, Margin, MouseEventHandler, Side, 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
39pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str {
40 match anchor {
41 DockAnchor::Right => "icons/dock_right_12.svg",
42 DockAnchor::Bottom => "icons/dock_bottom_12.svg",
43 DockAnchor::Expanded => "icons/dock_modal_12.svg",
44 }
45}
46
47impl DockPosition {
48 fn is_visible(&self) -> bool {
49 match self {
50 DockPosition::Shown(_) => true,
51 DockPosition::Hidden(_) => false,
52 }
53 }
54
55 fn anchor(&self) -> DockAnchor {
56 match self {
57 DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
58 }
59 }
60
61 fn toggle(self) -> Self {
62 match self {
63 DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
64 DockPosition::Hidden(anchor) => DockPosition::Shown(anchor),
65 }
66 }
67
68 fn hide(self) -> Self {
69 match self {
70 DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
71 DockPosition::Hidden(_) => self,
72 }
73 }
74}
75
76pub type DefaultItemFactory =
77 fn(&mut Workspace, &mut ViewContext<Workspace>) -> Box<dyn ItemHandle>;
78
79pub struct Dock {
80 position: DockPosition,
81 pane: ViewHandle<Pane>,
82 default_item_factory: DefaultItemFactory,
83}
84
85impl Dock {
86 pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
87 let anchor = cx.global::<Settings>().default_dock_anchor;
88 let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
89 let pane_id = pane.id();
90 cx.subscribe(&pane, move |workspace, _, event, cx| {
91 workspace.handle_pane_event(pane_id, event, cx);
92 })
93 .detach();
94
95 Self {
96 pane,
97 position: DockPosition::Hidden(anchor),
98 default_item_factory,
99 }
100 }
101
102 pub fn pane(&self) -> &ViewHandle<Pane> {
103 &self.pane
104 }
105
106 pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
107 self.position.is_visible().then(|| self.pane())
108 }
109
110 fn set_dock_position(
111 workspace: &mut Workspace,
112 new_position: DockPosition,
113 cx: &mut ViewContext<Workspace>,
114 ) {
115 workspace.dock.position = new_position;
116 // Tell the pane about the new anchor position
117 workspace.dock.pane.update(cx, |pane, cx| {
118 pane.set_docked(Some(new_position.anchor()), cx)
119 });
120
121 let now_visible = workspace.dock.visible_pane().is_some();
122 if now_visible {
123 // Ensure that the pane has at least one item or construct a default item to put in it
124 let pane = workspace.dock.pane.clone();
125 if pane.read(cx).items().next().is_none() {
126 let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
127 Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
128 }
129 cx.focus(pane);
130 } else {
131 if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() {
132 cx.focus(last_active_center_pane);
133 }
134 }
135 cx.emit(crate::Event::DockAnchorChanged);
136 cx.notify();
137 }
138
139 pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
140 Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
141 }
142
143 fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext<Workspace>) {
144 Self::set_dock_position(workspace, workspace.dock.position.toggle(), cx);
145 }
146
147 fn move_dock(
148 workspace: &mut Workspace,
149 &MoveDock(new_anchor): &MoveDock,
150 cx: &mut ViewContext<Workspace>,
151 ) {
152 Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
153 }
154
155 pub fn render(
156 &self,
157 theme: &Theme,
158 anchor: DockAnchor,
159 cx: &mut RenderContext<Workspace>,
160 ) -> Option<ElementBox> {
161 let style = &theme.workspace.dock;
162
163 self.position
164 .is_visible()
165 .then(|| self.position.anchor())
166 .filter(|current_anchor| *current_anchor == anchor)
167 .map(|anchor| match anchor {
168 DockAnchor::Bottom | DockAnchor::Right => {
169 let mut panel_style = style.panel.clone();
170 let resize_side = if anchor == DockAnchor::Bottom {
171 panel_style.margin = Margin {
172 top: panel_style.margin.top,
173 ..Default::default()
174 };
175 Side::Top
176 } else {
177 panel_style.margin = Margin {
178 left: panel_style.margin.left,
179 ..Default::default()
180 };
181 Side::Left
182 };
183
184 enum DockResizeHandle {}
185 Container::new(ChildView::new(self.pane.clone()).boxed())
186 .with_style(style.panel)
187 .with_resize_handle::<DockResizeHandle, _>(0, resize_side, 4., 200., cx)
188 .flex(style.flex, false)
189 .boxed()
190 }
191 DockAnchor::Expanded => Container::new(
192 MouseEventHandler::<Dock>::new(0, cx, |_state, _cx| {
193 Container::new(ChildView::new(self.pane.clone()).boxed())
194 .with_style(style.maximized)
195 .boxed()
196 })
197 .capture_all()
198 .with_cursor_style(CursorStyle::Arrow)
199 .boxed(),
200 )
201 .with_background_color(style.wash_color)
202 .boxed(),
203 })
204 }
205}
206
207pub struct ToggleDockButton {
208 workspace: WeakViewHandle<Workspace>,
209}
210
211impl ToggleDockButton {
212 pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
213 // When dock moves, redraw so that the icon and toggle status matches.
214 cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
215
216 Self {
217 workspace: workspace.downgrade(),
218 }
219 }
220}
221
222impl Entity for ToggleDockButton {
223 type Event = ();
224}
225
226impl View for ToggleDockButton {
227 fn ui_name() -> &'static str {
228 "Dock Toggle"
229 }
230
231 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
232 let workspace = self.workspace.upgrade(cx);
233
234 if workspace.is_none() {
235 return Empty::new().boxed();
236 }
237
238 let dock_position = workspace.unwrap().read(cx).dock.position;
239
240 let theme = cx.global::<Settings>().theme.clone();
241 MouseEventHandler::<Self>::new(0, cx, {
242 let theme = theme.clone();
243 move |state, _| {
244 let style = theme
245 .workspace
246 .status_bar
247 .sidebar_buttons
248 .item
249 .style_for(state, dock_position.is_visible());
250
251 Svg::new(icon_for_dock_anchor(dock_position.anchor()))
252 .with_color(style.icon_color)
253 .constrained()
254 .with_width(style.icon_size)
255 .with_height(style.icon_size)
256 .contained()
257 .with_style(style.container)
258 .boxed()
259 }
260 })
261 .with_cursor_style(CursorStyle::PointingHand)
262 .on_click(MouseButton::Left, |_, cx| {
263 cx.dispatch_action(ToggleDock);
264 })
265 .with_tooltip::<Self, _>(
266 0,
267 "Toggle Dock".to_string(),
268 Some(Box::new(ToggleDock)),
269 theme.tooltip.clone(),
270 cx,
271 )
272 .boxed()
273 }
274}
275
276impl StatusItemView for ToggleDockButton {
277 fn set_active_pane_item(
278 &mut self,
279 _active_pane_item: Option<&dyn crate::ItemHandle>,
280 _cx: &mut ViewContext<Self>,
281 ) {
282 //Not applicable
283 }
284}