dock.rs

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