1use crate::{StatusItemView, Workspace};
2use gpui::{
3 elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
4 AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
5};
6use serde::Deserialize;
7use settings::Settings;
8use std::rc::Rc;
9
10pub trait Panel: View {
11 fn position(&self, cx: &WindowContext) -> DockPosition;
12 fn position_is_valid(&self, position: DockPosition) -> bool;
13 fn icon_path(&self) -> &'static str;
14 fn icon_tooltip(&self) -> String;
15 fn icon_label(&self, _: &AppContext) -> Option<String> {
16 None
17 }
18 fn should_change_position_on_event(_: &Self::Event) -> bool;
19 fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool;
20 fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool;
21}
22
23pub trait PanelHandle {
24 fn id(&self) -> usize;
25 fn position(&self, cx: &WindowContext) -> DockPosition;
26 fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
27 fn icon_path(&self, cx: &WindowContext) -> &'static str;
28 fn icon_tooltip(&self, cx: &WindowContext) -> String;
29 fn icon_label(&self, cx: &WindowContext) -> Option<String>;
30 fn is_focused(&self, cx: &WindowContext) -> bool;
31 fn as_any(&self) -> &AnyViewHandle;
32}
33
34impl<T> PanelHandle for ViewHandle<T>
35where
36 T: Panel,
37{
38 fn id(&self) -> usize {
39 self.id()
40 }
41
42 fn position(&self, cx: &WindowContext) -> DockPosition {
43 self.read(cx).position(cx)
44 }
45
46 fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
47 self.read(cx).position_is_valid(position)
48 }
49
50 fn icon_path(&self, cx: &WindowContext) -> &'static str {
51 self.read(cx).icon_path()
52 }
53
54 fn icon_tooltip(&self, cx: &WindowContext) -> String {
55 self.read(cx).icon_tooltip()
56 }
57
58 fn icon_label(&self, cx: &WindowContext) -> Option<String> {
59 self.read(cx).icon_label(cx)
60 }
61
62 fn is_focused(&self, cx: &WindowContext) -> bool {
63 ViewHandle::is_focused(self, cx)
64 }
65
66 fn as_any(&self) -> &AnyViewHandle {
67 self
68 }
69}
70
71impl From<&dyn PanelHandle> for AnyViewHandle {
72 fn from(val: &dyn PanelHandle) -> Self {
73 val.as_any().clone()
74 }
75}
76
77pub enum Event {
78 Close,
79}
80
81pub struct Dock {
82 position: DockPosition,
83 panels: Vec<PanelEntry>,
84 is_open: bool,
85 active_item_ix: usize,
86}
87
88#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
89pub enum DockPosition {
90 Left,
91 Bottom,
92 Right,
93}
94
95impl From<settings::DockPosition> for DockPosition {
96 fn from(value: settings::DockPosition) -> Self {
97 match value {
98 settings::DockPosition::Left => Self::Left,
99 settings::DockPosition::Bottom => Self::Bottom,
100 settings::DockPosition::Right => Self::Right,
101 }
102 }
103}
104
105impl DockPosition {
106 fn to_resizable_side(self) -> Side {
107 match self {
108 Self::Left => Side::Right,
109 Self::Bottom => Side::Top,
110 Self::Right => Side::Left,
111 }
112 }
113}
114
115struct PanelEntry {
116 panel: Rc<dyn PanelHandle>,
117 _subscriptions: [Subscription; 2],
118}
119
120pub struct PanelButtons {
121 dock: ViewHandle<Dock>,
122 workspace: WeakViewHandle<Workspace>,
123}
124
125#[derive(Clone, Debug, Deserialize, PartialEq)]
126pub struct TogglePanel {
127 pub dock_position: DockPosition,
128 pub item_index: usize,
129}
130
131impl_actions!(workspace, [TogglePanel]);
132
133impl Dock {
134 pub fn new(position: DockPosition) -> Self {
135 Self {
136 position,
137 panels: Default::default(),
138 active_item_ix: 0,
139 is_open: false,
140 }
141 }
142
143 pub fn is_open(&self) -> bool {
144 self.is_open
145 }
146
147 pub fn active_item_ix(&self) -> usize {
148 self.active_item_ix
149 }
150
151 pub fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
152 if open != self.is_open {
153 self.is_open = open;
154 cx.notify();
155 }
156 }
157
158 pub fn toggle_open(&mut self, cx: &mut ViewContext<Self>) {
159 if self.is_open {}
160 self.is_open = !self.is_open;
161 cx.notify();
162 }
163
164 pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
165 let subscriptions = [
166 cx.observe(&panel, |_, _, cx| cx.notify()),
167 cx.subscribe(&panel, |this, view, event, cx| {
168 if view.read(cx).should_activate_on_event(event, cx) {
169 if let Some(ix) = this
170 .panels
171 .iter()
172 .position(|item| item.panel.id() == view.id())
173 {
174 this.activate_item(ix, cx);
175 }
176 } else if view.read(cx).should_close_on_event(event, cx) {
177 cx.emit(Event::Close);
178 }
179 }),
180 ];
181
182 self.panels.push(PanelEntry {
183 panel: Rc::new(panel),
184 _subscriptions: subscriptions,
185 });
186 cx.notify()
187 }
188
189 pub fn remove_panel<T: Panel>(&mut self, panel: &ViewHandle<T>, cx: &mut ViewContext<Self>) {
190 if let Some(panel_ix) = self
191 .panels
192 .iter()
193 .position(|item| item.panel.id() == panel.id())
194 {
195 if panel_ix == self.active_item_ix {
196 self.active_item_ix = 0;
197 cx.emit(Event::Close);
198 }
199 self.panels.remove(panel_ix);
200 cx.notify();
201 }
202 }
203
204 pub fn panels_len(&self) -> usize {
205 self.panels.len()
206 }
207
208 pub fn activate_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
209 self.active_item_ix = item_ix;
210 cx.notify();
211 }
212
213 pub fn toggle_item(&mut self, item_ix: usize, cx: &mut ViewContext<Self>) {
214 if self.active_item_ix == item_ix {
215 self.is_open = false;
216 } else {
217 self.active_item_ix = item_ix;
218 }
219 cx.notify();
220 }
221
222 pub fn active_item(&self) -> Option<&Rc<dyn PanelHandle>> {
223 if self.is_open {
224 self.panels.get(self.active_item_ix).map(|item| &item.panel)
225 } else {
226 None
227 }
228 }
229}
230
231impl Entity for Dock {
232 type Event = Event;
233}
234
235impl View for Dock {
236 fn ui_name() -> &'static str {
237 "Dock"
238 }
239
240 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
241 if let Some(active_item) = self.active_item() {
242 enum ResizeHandleTag {}
243 let style = &cx.global::<Settings>().theme.workspace.dock;
244 ChildView::new(active_item.as_any(), cx)
245 .contained()
246 .with_style(style.container)
247 .with_resize_handle::<ResizeHandleTag>(
248 self.position as usize,
249 self.position.to_resizable_side(),
250 4.,
251 style.initial_size,
252 cx,
253 )
254 .into_any()
255 } else {
256 Empty::new().into_any()
257 }
258 }
259}
260
261impl PanelButtons {
262 pub fn new(
263 dock: ViewHandle<Dock>,
264 workspace: WeakViewHandle<Workspace>,
265 cx: &mut ViewContext<Self>,
266 ) -> Self {
267 cx.observe(&dock, |_, _, cx| cx.notify()).detach();
268 Self { dock, workspace }
269 }
270}
271
272impl Entity for PanelButtons {
273 type Event = ();
274}
275
276impl View for PanelButtons {
277 fn ui_name() -> &'static str {
278 "PanelButtons"
279 }
280
281 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
282 let theme = &cx.global::<Settings>().theme;
283 let tooltip_style = theme.tooltip.clone();
284 let theme = &theme.workspace.status_bar.panel_buttons;
285 let item_style = theme.button.clone();
286 let dock = self.dock.read(cx);
287 let active_ix = dock.active_item_ix;
288 let is_open = dock.is_open;
289 let dock_position = dock.position;
290 let group_style = match dock_position {
291 DockPosition::Left => theme.group_left,
292 DockPosition::Bottom => theme.group_bottom,
293 DockPosition::Right => theme.group_right,
294 };
295
296 let items = dock
297 .panels
298 .iter()
299 .map(|item| item.panel.clone())
300 .collect::<Vec<_>>();
301 Flex::row()
302 .with_children(items.into_iter().enumerate().map(|(ix, view)| {
303 let action = TogglePanel {
304 dock_position,
305 item_index: ix,
306 };
307 MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
308 let is_active = is_open && ix == active_ix;
309 let style = item_style.style_for(state, is_active);
310 Flex::row()
311 .with_child(
312 Svg::new(view.icon_path(cx))
313 .with_color(style.icon_color)
314 .constrained()
315 .with_width(style.icon_size)
316 .aligned(),
317 )
318 .with_children(if let Some(label) = view.icon_label(cx) {
319 Some(
320 Label::new(label, style.label.text.clone())
321 .contained()
322 .with_style(style.label.container)
323 .aligned(),
324 )
325 } else {
326 None
327 })
328 .constrained()
329 .with_height(style.icon_size)
330 .contained()
331 .with_style(style.container)
332 })
333 .with_cursor_style(CursorStyle::PointingHand)
334 .on_click(MouseButton::Left, {
335 let action = action.clone();
336 move |_, this, cx| {
337 if let Some(workspace) = this.workspace.upgrade(cx) {
338 let action = action.clone();
339 cx.window_context().defer(move |cx| {
340 workspace.update(cx, |workspace, cx| {
341 workspace.toggle_panel(&action, cx)
342 });
343 });
344 }
345 }
346 })
347 .with_tooltip::<Self>(
348 ix,
349 view.icon_tooltip(cx),
350 Some(Box::new(action)),
351 tooltip_style.clone(),
352 cx,
353 )
354 }))
355 .contained()
356 .with_style(group_style)
357 .into_any()
358 }
359}
360
361impl StatusItemView for PanelButtons {
362 fn set_active_pane_item(
363 &mut self,
364 _: Option<&dyn crate::ItemHandle>,
365 _: &mut ViewContext<Self>,
366 ) {
367 }
368}