1use collections::HashMap;
2use gpui::{
3 actions,
4 elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg},
5 impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton,
6 MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
7};
8use serde::Deserialize;
9use settings::{DockAnchor, Settings};
10use theme::Theme;
11
12use crate::{sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace};
13
14#[derive(PartialEq, Clone, Deserialize)]
15pub struct MoveDock(pub DockAnchor);
16
17#[derive(PartialEq, Clone)]
18pub struct AddDefaultItemToDock;
19
20actions!(
21 dock,
22 [
23 FocusDock,
24 HideDock,
25 AnchorDockRight,
26 AnchorDockBottom,
27 ExpandDock,
28 MoveActiveItemToDock,
29 ]
30);
31impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
32
33pub fn init(cx: &mut MutableAppContext) {
34 cx.add_action(Dock::focus_dock);
35 cx.add_action(Dock::hide_dock);
36 cx.add_action(Dock::move_dock);
37 cx.add_action(
38 |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
39 Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx)
40 },
41 );
42 cx.add_action(
43 |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
44 Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx)
45 },
46 );
47 cx.add_action(
48 |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
49 Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
50 },
51 );
52 cx.add_action(
53 |workspace: &mut Workspace, _: &MoveActiveItemToDock, cx: &mut ViewContext<Workspace>| {
54 if let Some(active_item) = workspace.active_item(cx) {
55 let item_id = active_item.id();
56
57 let from = workspace.active_pane();
58 let to = workspace.dock_pane();
59 if from.id() == to.id() {
60 return;
61 }
62
63 let destination_index = to.read(cx).items_len() + 1;
64
65 Pane::move_item(
66 workspace,
67 from.clone(),
68 to.clone(),
69 item_id,
70 destination_index,
71 cx,
72 );
73 }
74 },
75 );
76}
77
78#[derive(Copy, Clone, PartialEq, Eq, Debug)]
79pub enum DockPosition {
80 Shown(DockAnchor),
81 Hidden(DockAnchor),
82}
83
84impl Default for DockPosition {
85 fn default() -> Self {
86 DockPosition::Hidden(Default::default())
87 }
88}
89
90pub fn icon_for_dock_anchor(anchor: DockAnchor) -> &'static str {
91 match anchor {
92 DockAnchor::Right => "icons/dock_right_12.svg",
93 DockAnchor::Bottom => "icons/dock_bottom_12.svg",
94 DockAnchor::Expanded => "icons/dock_modal_12.svg",
95 }
96}
97
98impl DockPosition {
99 fn is_visible(&self) -> bool {
100 match self {
101 DockPosition::Shown(_) => true,
102 DockPosition::Hidden(_) => false,
103 }
104 }
105
106 fn anchor(&self) -> DockAnchor {
107 match self {
108 DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
109 }
110 }
111
112 fn hide(self) -> Self {
113 match self {
114 DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
115 DockPosition::Hidden(_) => self,
116 }
117 }
118
119 fn show(self) -> Self {
120 match self {
121 DockPosition::Hidden(anchor) => DockPosition::Shown(anchor),
122 DockPosition::Shown(_) => self,
123 }
124 }
125}
126
127pub type DefaultItemFactory =
128 fn(&mut Workspace, &mut ViewContext<Workspace>) -> Box<dyn ItemHandle>;
129
130pub struct Dock {
131 position: DockPosition,
132 panel_sizes: HashMap<DockAnchor, f32>,
133 pane: ViewHandle<Pane>,
134 default_item_factory: DefaultItemFactory,
135}
136
137impl Dock {
138 pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
139 let anchor = cx.global::<Settings>().default_dock_anchor;
140 let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
141 pane.update(cx, |pane, cx| {
142 pane.set_active(false, cx);
143 });
144 let pane_id = pane.id();
145 cx.subscribe(&pane, move |workspace, _, event, cx| {
146 workspace.handle_pane_event(pane_id, event, cx);
147 })
148 .detach();
149
150 Self {
151 pane,
152 panel_sizes: Default::default(),
153 position: DockPosition::Hidden(anchor),
154 default_item_factory,
155 }
156 }
157
158 pub fn pane(&self) -> &ViewHandle<Pane> {
159 &self.pane
160 }
161
162 pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
163 self.position.is_visible().then(|| self.pane())
164 }
165
166 pub fn is_anchored_at(&self, anchor: DockAnchor) -> bool {
167 self.position.is_visible() && self.position.anchor() == anchor
168 }
169
170 fn set_dock_position(
171 workspace: &mut Workspace,
172 new_position: DockPosition,
173 cx: &mut ViewContext<Workspace>,
174 ) {
175 workspace.dock.position = new_position;
176 // Tell the pane about the new anchor position
177 workspace.dock.pane.update(cx, |pane, cx| {
178 pane.set_docked(Some(new_position.anchor()), cx)
179 });
180
181 if workspace.dock.position.is_visible() {
182 // Close the right sidebar if the dock is on the right side and the right sidebar is open
183 if workspace.dock.position.anchor() == DockAnchor::Right {
184 if workspace.right_sidebar().read(cx).is_open() {
185 workspace.toggle_sidebar(SidebarSide::Right, cx);
186 }
187 }
188
189 // Ensure that the pane has at least one item or construct a default item to put in it
190 let pane = workspace.dock.pane.clone();
191 if pane.read(cx).items().next().is_none() {
192 let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
193 // Adding the item focuses the pane by default
194 Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
195 } else {
196 cx.focus(pane);
197 }
198 } else if let Some(last_active_center_pane) = workspace
199 .last_active_center_pane
200 .as_ref()
201 .and_then(|pane| pane.upgrade(cx))
202 {
203 cx.focus(last_active_center_pane);
204 }
205 cx.emit(crate::Event::DockAnchorChanged);
206 cx.notify();
207 }
208
209 pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
210 Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
211 }
212
213 pub fn show(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
214 Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
215 }
216
217 pub fn hide_on_sidebar_shown(
218 workspace: &mut Workspace,
219 sidebar_side: SidebarSide,
220 cx: &mut ViewContext<Workspace>,
221 ) {
222 if (sidebar_side == SidebarSide::Right && workspace.dock.is_anchored_at(DockAnchor::Right))
223 || workspace.dock.is_anchored_at(DockAnchor::Expanded)
224 {
225 Self::hide(workspace, cx);
226 }
227 }
228
229 fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
230 Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
231 }
232
233 fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
234 Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
235 }
236
237 fn move_dock(
238 workspace: &mut Workspace,
239 &MoveDock(new_anchor): &MoveDock,
240 cx: &mut ViewContext<Workspace>,
241 ) {
242 Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
243 }
244
245 pub fn render(
246 &self,
247 theme: &Theme,
248 anchor: DockAnchor,
249 cx: &mut RenderContext<Workspace>,
250 ) -> Option<ElementBox> {
251 let style = &theme.workspace.dock;
252
253 self.position
254 .is_visible()
255 .then(|| self.position.anchor())
256 .filter(|current_anchor| *current_anchor == anchor)
257 .map(|anchor| match anchor {
258 DockAnchor::Bottom | DockAnchor::Right => {
259 let mut panel_style = style.panel.clone();
260 let (resize_side, initial_size) = if anchor == DockAnchor::Bottom {
261 panel_style.border = Border {
262 top: true,
263 bottom: false,
264 left: false,
265 right: false,
266 ..panel_style.border
267 };
268
269 (Side::Top, style.initial_size_bottom)
270 } else {
271 panel_style.border = Border {
272 top: false,
273 bottom: false,
274 left: true,
275 right: false,
276 ..panel_style.border
277 };
278 (Side::Left, style.initial_size_right)
279 };
280
281 enum DockResizeHandle {}
282
283 let resizable = Container::new(ChildView::new(self.pane.clone(), cx).boxed())
284 .with_style(panel_style)
285 .with_resize_handle::<DockResizeHandle, _>(
286 resize_side as usize,
287 resize_side,
288 4.,
289 self.panel_sizes
290 .get(&anchor)
291 .copied()
292 .unwrap_or(initial_size),
293 cx,
294 );
295
296 let size = resizable.current_size();
297 let workspace = cx.handle();
298 cx.defer(move |cx| {
299 if let Some(workspace) = workspace.upgrade(cx) {
300 workspace.update(cx, |workspace, _| {
301 workspace.dock.panel_sizes.insert(anchor, size);
302 })
303 }
304 });
305
306 resizable.flex(5., false).boxed()
307 }
308 DockAnchor::Expanded => {
309 enum ExpandedDockWash {}
310 enum ExpandedDockPane {}
311 Stack::new()
312 .with_child(
313 // Render wash under the dock which when clicked hides it
314 MouseEventHandler::<ExpandedDockWash>::new(0, cx, |_, _| {
315 Empty::new()
316 .contained()
317 .with_background_color(style.wash_color)
318 .boxed()
319 })
320 .capture_all()
321 .on_down(MouseButton::Left, |_, cx| {
322 cx.dispatch_action(HideDock);
323 })
324 .with_cursor_style(CursorStyle::Arrow)
325 .boxed(),
326 )
327 .with_child(
328 MouseEventHandler::<ExpandedDockPane>::new(0, cx, |_state, cx| {
329 ChildView::new(&self.pane, cx).boxed()
330 })
331 // Make sure all events directly under the dock pane
332 // are captured
333 .capture_all()
334 .contained()
335 .with_style(style.maximized)
336 .boxed(),
337 )
338 .boxed()
339 }
340 })
341 }
342}
343
344pub struct ToggleDockButton {
345 workspace: WeakViewHandle<Workspace>,
346}
347
348impl ToggleDockButton {
349 pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
350 // When dock moves, redraw so that the icon and toggle status matches.
351 cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
352
353 Self {
354 workspace: workspace.downgrade(),
355 }
356 }
357}
358
359impl Entity for ToggleDockButton {
360 type Event = ();
361}
362
363impl View for ToggleDockButton {
364 fn ui_name() -> &'static str {
365 "Dock Toggle"
366 }
367
368 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
369 let workspace = self.workspace.upgrade(cx);
370
371 if workspace.is_none() {
372 return Empty::new().boxed();
373 }
374
375 let workspace = workspace.unwrap();
376 let dock_position = workspace.read(cx).dock.position;
377
378 let theme = cx.global::<Settings>().theme.clone();
379 let button = MouseEventHandler::<Self>::new(0, cx, {
380 let theme = theme.clone();
381 move |state, _| {
382 let style = theme
383 .workspace
384 .status_bar
385 .sidebar_buttons
386 .item
387 .style_for(state, dock_position.is_visible());
388
389 Svg::new(icon_for_dock_anchor(dock_position.anchor()))
390 .with_color(style.icon_color)
391 .constrained()
392 .with_width(style.icon_size)
393 .with_height(style.icon_size)
394 .contained()
395 .with_style(style.container)
396 .boxed()
397 }
398 })
399 .with_cursor_style(CursorStyle::PointingHand)
400 .on_up(MouseButton::Left, move |_, cx| {
401 let dock_pane = workspace.read(cx.app).dock_pane();
402 let drop_index = dock_pane.read(cx.app).items_len() + 1;
403 Pane::handle_dropped_item(&dock_pane.downgrade(), drop_index, false, cx);
404 });
405
406 if dock_position.is_visible() {
407 button
408 .on_click(MouseButton::Left, |_, cx| {
409 cx.dispatch_action(HideDock);
410 })
411 .with_tooltip::<Self, _>(
412 0,
413 "Hide Dock".into(),
414 Some(Box::new(HideDock)),
415 theme.tooltip.clone(),
416 cx,
417 )
418 } else {
419 button
420 .on_click(MouseButton::Left, |_, cx| {
421 cx.dispatch_action(FocusDock);
422 })
423 .with_tooltip::<Self, _>(
424 0,
425 "Focus Dock".into(),
426 Some(Box::new(FocusDock)),
427 theme.tooltip.clone(),
428 cx,
429 )
430 }
431 .boxed()
432 }
433}
434
435impl StatusItemView for ToggleDockButton {
436 fn set_active_pane_item(
437 &mut self,
438 _active_pane_item: Option<&dyn crate::ItemHandle>,
439 _cx: &mut ViewContext<Self>,
440 ) {
441 //Not applicable
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use std::ops::{Deref, DerefMut};
448
449 use gpui::{AppContext, TestAppContext, UpdateView, ViewContext};
450 use project::{FakeFs, Project};
451 use settings::Settings;
452
453 use super::*;
454 use crate::{sidebar::Sidebar, tests::TestItem, ItemHandle, Workspace};
455
456 pub fn default_item_factory(
457 _workspace: &mut Workspace,
458 cx: &mut ViewContext<Workspace>,
459 ) -> Box<dyn ItemHandle> {
460 Box::new(cx.add_view(|_| TestItem::new()))
461 }
462
463 #[gpui::test]
464 async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) {
465 let mut cx = DockTestContext::new(cx).await;
466
467 // Closing the last item in the dock hides the dock
468 cx.move_dock(DockAnchor::Right);
469 let old_items = cx.dock_items();
470 assert!(!old_items.is_empty());
471 cx.close_dock_items().await;
472 cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right));
473
474 // Reopening the dock adds a new item
475 cx.move_dock(DockAnchor::Right);
476 let new_items = cx.dock_items();
477 assert!(!new_items.is_empty());
478 assert!(new_items
479 .into_iter()
480 .all(|new_item| !old_items.contains(&new_item)));
481 }
482
483 #[gpui::test]
484 async fn test_dock_panel_collisions(cx: &mut TestAppContext) {
485 let mut cx = DockTestContext::new(cx).await;
486
487 // Dock closes when expanded for either panel
488 cx.move_dock(DockAnchor::Expanded);
489 cx.open_sidebar(SidebarSide::Left);
490 cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
491 cx.close_sidebar(SidebarSide::Left);
492 cx.move_dock(DockAnchor::Expanded);
493 cx.open_sidebar(SidebarSide::Right);
494 cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
495
496 // Dock closes in the right position if the right sidebar is opened
497 cx.move_dock(DockAnchor::Right);
498 cx.open_sidebar(SidebarSide::Left);
499 cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
500 cx.open_sidebar(SidebarSide::Right);
501 cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Right));
502 cx.close_sidebar(SidebarSide::Right);
503
504 // Dock in bottom position ignores sidebars
505 cx.move_dock(DockAnchor::Bottom);
506 cx.open_sidebar(SidebarSide::Left);
507 cx.open_sidebar(SidebarSide::Right);
508 cx.assert_dock_position(DockPosition::Shown(DockAnchor::Bottom));
509
510 // Opening the dock in the right position closes the right sidebar
511 cx.move_dock(DockAnchor::Right);
512 cx.assert_sidebar_closed(SidebarSide::Right);
513 }
514
515 #[gpui::test]
516 async fn test_focusing_panes_shows_and_hides_dock(cx: &mut TestAppContext) {
517 let mut cx = DockTestContext::new(cx).await;
518
519 // Focusing an item not in the dock when expanded hides the dock
520 let center_item = cx.add_item_to_center_pane();
521 cx.move_dock(DockAnchor::Expanded);
522 let dock_item = cx
523 .dock_items()
524 .get(0)
525 .cloned()
526 .expect("Dock should have an item at this point");
527 center_item.update(&mut cx, |_, cx| cx.focus_self());
528 cx.assert_dock_position(DockPosition::Hidden(DockAnchor::Expanded));
529
530 // Focusing an item not in the dock when not expanded, leaves the dock open but inactive
531 cx.move_dock(DockAnchor::Right);
532 center_item.update(&mut cx, |_, cx| cx.focus_self());
533 cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
534 cx.assert_dock_pane_inactive();
535 cx.assert_workspace_pane_active();
536
537 // Focusing an item in the dock activates it's pane
538 dock_item.update(&mut cx, |_, cx| cx.focus_self());
539 cx.assert_dock_position(DockPosition::Shown(DockAnchor::Right));
540 cx.assert_dock_pane_active();
541 cx.assert_workspace_pane_inactive();
542 }
543
544 #[gpui::test]
545 async fn test_toggle_dock_focus(cx: &mut TestAppContext) {
546 let cx = DockTestContext::new(cx).await;
547
548 cx.move_dock(DockAnchor::Right);
549 cx.assert_dock_pane_active();
550 cx.hide_dock();
551 cx.move_dock(DockAnchor::Right);
552 cx.assert_dock_pane_active();
553 }
554
555 struct DockTestContext<'a> {
556 pub cx: &'a mut TestAppContext,
557 pub window_id: usize,
558 pub workspace: ViewHandle<Workspace>,
559 }
560
561 impl<'a> DockTestContext<'a> {
562 pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> {
563 Settings::test_async(cx);
564 let fs = FakeFs::new(cx.background());
565
566 cx.update(|cx| init(cx));
567 let project = Project::test(fs, [], cx).await;
568 let (window_id, workspace) =
569 cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
570
571 workspace.update(cx, |workspace, cx| {
572 let left_panel = cx.add_view(|_| TestItem::new());
573 workspace.left_sidebar().update(cx, |sidebar, cx| {
574 sidebar.add_item(
575 "icons/folder_tree_16.svg",
576 "Left Test Panel".to_string(),
577 left_panel.clone(),
578 cx,
579 );
580 });
581
582 let right_panel = cx.add_view(|_| TestItem::new());
583 workspace.right_sidebar().update(cx, |sidebar, cx| {
584 sidebar.add_item(
585 "icons/folder_tree_16.svg",
586 "Right Test Panel".to_string(),
587 right_panel.clone(),
588 cx,
589 );
590 });
591 });
592
593 Self {
594 cx,
595 window_id,
596 workspace,
597 }
598 }
599
600 pub fn workspace<F, T>(&self, read: F) -> T
601 where
602 F: FnOnce(&Workspace, &AppContext) -> T,
603 {
604 self.workspace.read_with(self.cx, read)
605 }
606
607 pub fn update_workspace<F, T>(&mut self, update: F) -> T
608 where
609 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
610 {
611 self.workspace.update(self.cx, update)
612 }
613
614 pub fn sidebar<F, T>(&self, sidebar_side: SidebarSide, read: F) -> T
615 where
616 F: FnOnce(&Sidebar, &AppContext) -> T,
617 {
618 self.workspace(|workspace, cx| {
619 let sidebar = match sidebar_side {
620 SidebarSide::Left => workspace.left_sidebar(),
621 SidebarSide::Right => workspace.right_sidebar(),
622 }
623 .read(cx);
624
625 read(sidebar, cx)
626 })
627 }
628
629 pub fn center_pane_handle(&self) -> ViewHandle<Pane> {
630 self.workspace(|workspace, cx| {
631 workspace
632 .last_active_center_pane
633 .clone()
634 .and_then(|pane| pane.upgrade(cx))
635 .unwrap_or_else(|| workspace.center.panes()[0].clone())
636 })
637 }
638
639 pub fn add_item_to_center_pane(&mut self) -> ViewHandle<TestItem> {
640 self.update_workspace(|workspace, cx| {
641 let item = cx.add_view(|_| TestItem::new());
642 let pane = workspace
643 .last_active_center_pane
644 .clone()
645 .and_then(|pane| pane.upgrade(cx))
646 .unwrap_or_else(|| workspace.center.panes()[0].clone());
647 Pane::add_item(
648 workspace,
649 &pane,
650 Box::new(item.clone()),
651 true,
652 true,
653 None,
654 cx,
655 );
656 item
657 })
658 }
659
660 pub fn dock_pane<F, T>(&self, read: F) -> T
661 where
662 F: FnOnce(&Pane, &AppContext) -> T,
663 {
664 self.workspace(|workspace, cx| {
665 let dock_pane = workspace.dock_pane().read(cx);
666 read(dock_pane, cx)
667 })
668 }
669
670 pub fn move_dock(&self, anchor: DockAnchor) {
671 self.cx.dispatch_action(self.window_id, MoveDock(anchor));
672 }
673
674 pub fn hide_dock(&self) {
675 self.cx.dispatch_action(self.window_id, HideDock);
676 }
677
678 pub fn open_sidebar(&mut self, sidebar_side: SidebarSide) {
679 if !self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) {
680 self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx));
681 }
682 }
683
684 pub fn close_sidebar(&mut self, sidebar_side: SidebarSide) {
685 if self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()) {
686 self.update_workspace(|workspace, cx| workspace.toggle_sidebar(sidebar_side, cx));
687 }
688 }
689
690 pub fn dock_items(&self) -> Vec<ViewHandle<TestItem>> {
691 self.dock_pane(|pane, cx| {
692 pane.items()
693 .map(|item| {
694 item.act_as::<TestItem>(cx)
695 .expect("Dock Test Context uses TestItems in the dock")
696 })
697 .collect()
698 })
699 }
700
701 pub async fn close_dock_items(&mut self) {
702 self.update_workspace(|workspace, cx| {
703 Pane::close_items(workspace, workspace.dock_pane().clone(), cx, |_| true)
704 })
705 .await
706 .expect("Could not close dock items")
707 }
708
709 pub fn assert_dock_position(&self, expected_position: DockPosition) {
710 self.workspace(|workspace, _| assert_eq!(workspace.dock.position, expected_position));
711 }
712
713 pub fn assert_sidebar_closed(&self, sidebar_side: SidebarSide) {
714 assert!(!self.sidebar(sidebar_side, |sidebar, _| sidebar.is_open()));
715 }
716
717 pub fn assert_workspace_pane_active(&self) {
718 assert!(self
719 .center_pane_handle()
720 .read_with(self.cx, |pane, _| pane.is_active()));
721 }
722
723 pub fn assert_workspace_pane_inactive(&self) {
724 assert!(!self
725 .center_pane_handle()
726 .read_with(self.cx, |pane, _| pane.is_active()));
727 }
728
729 pub fn assert_dock_pane_active(&self) {
730 assert!(self.dock_pane(|pane, _| pane.is_active()))
731 }
732
733 pub fn assert_dock_pane_inactive(&self) {
734 assert!(!self.dock_pane(|pane, _| pane.is_active()))
735 }
736 }
737
738 impl<'a> Deref for DockTestContext<'a> {
739 type Target = gpui::TestAppContext;
740
741 fn deref(&self) -> &Self::Target {
742 self.cx
743 }
744 }
745
746 impl<'a> DerefMut for DockTestContext<'a> {
747 fn deref_mut(&mut self) -> &mut Self::Target {
748 &mut self.cx
749 }
750 }
751
752 impl<'a> UpdateView for DockTestContext<'a> {
753 fn update_view<T, S>(
754 &mut self,
755 handle: &ViewHandle<T>,
756 update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
757 ) -> S
758 where
759 T: View,
760 {
761 handle.update(self.cx, update)
762 }
763 }
764}