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