dock.rs

  1use crate::DraggedDock;
  2use crate::{status_bar::StatusItemView, Workspace};
  3use gpui::{
  4    div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Div, Entity, EntityId,
  5    EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
  6    SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
  7};
  8use schemars::JsonSchema;
  9use serde::{Deserialize, Serialize};
 10use std::sync::Arc;
 11use ui::{h_stack, ContextMenu, IconButton, Tooltip};
 12use ui::{prelude::*, right_click_menu};
 13
 14const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
 15
 16pub enum PanelEvent {
 17    ChangePosition,
 18    ZoomIn,
 19    ZoomOut,
 20    Activate,
 21    Close,
 22    Focus,
 23}
 24
 25pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
 26    fn persistent_name() -> &'static str;
 27    fn position(&self, cx: &WindowContext) -> DockPosition;
 28    fn position_is_valid(&self, position: DockPosition) -> bool;
 29    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
 30    fn size(&self, cx: &WindowContext) -> Pixels;
 31    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
 32    // todo!("We should have a icon tooltip method, rather than using persistant_name")
 33    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
 34    fn toggle_action(&self) -> Box<dyn Action>;
 35    fn icon_label(&self, _: &WindowContext) -> Option<String> {
 36        None
 37    }
 38    fn is_zoomed(&self, _cx: &WindowContext) -> bool {
 39        false
 40    }
 41    fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
 42    fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
 43}
 44
 45pub trait PanelHandle: Send + Sync {
 46    fn panel_id(&self) -> EntityId;
 47    fn persistent_name(&self) -> &'static str;
 48    fn position(&self, cx: &WindowContext) -> DockPosition;
 49    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
 50    fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
 51    fn is_zoomed(&self, cx: &WindowContext) -> bool;
 52    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
 53    fn set_active(&self, active: bool, cx: &mut WindowContext);
 54    fn size(&self, cx: &WindowContext) -> Pixels;
 55    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
 56    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
 57    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
 58    fn icon_label(&self, cx: &WindowContext) -> Option<String>;
 59    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 60    fn to_any(&self) -> AnyView;
 61}
 62
 63impl<T> PanelHandle for View<T>
 64where
 65    T: Panel,
 66{
 67    fn panel_id(&self) -> EntityId {
 68        Entity::entity_id(self)
 69    }
 70
 71    fn persistent_name(&self) -> &'static str {
 72        T::persistent_name()
 73    }
 74
 75    fn position(&self, cx: &WindowContext) -> DockPosition {
 76        self.read(cx).position(cx)
 77    }
 78
 79    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
 80        self.read(cx).position_is_valid(position)
 81    }
 82
 83    fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
 84        self.update(cx, |this, cx| this.set_position(position, cx))
 85    }
 86
 87    fn is_zoomed(&self, cx: &WindowContext) -> bool {
 88        self.read(cx).is_zoomed(cx)
 89    }
 90
 91    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
 92        self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
 93    }
 94
 95    fn set_active(&self, active: bool, cx: &mut WindowContext) {
 96        self.update(cx, |this, cx| this.set_active(active, cx))
 97    }
 98
 99    fn size(&self, cx: &WindowContext) -> Pixels {
100        self.read(cx).size(cx)
101    }
102
103    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
104        self.update(cx, |this, cx| this.set_size(size, cx))
105    }
106
107    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
108        self.read(cx).icon(cx)
109    }
110
111    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action> {
112        self.read(cx).toggle_action()
113    }
114
115    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
116        self.read(cx).icon_label(cx)
117    }
118
119    fn to_any(&self) -> AnyView {
120        self.clone().into()
121    }
122
123    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
124        self.read(cx).focus_handle(cx).clone()
125    }
126}
127
128impl From<&dyn PanelHandle> for AnyView {
129    fn from(val: &dyn PanelHandle) -> Self {
130        val.to_any()
131    }
132}
133
134pub struct Dock {
135    position: DockPosition,
136    panel_entries: Vec<PanelEntry>,
137    is_open: bool,
138    active_panel_index: usize,
139    focus_handle: FocusHandle,
140    _focus_subscription: Subscription,
141}
142
143impl FocusableView for Dock {
144    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
145        self.focus_handle.clone()
146    }
147}
148
149#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
150#[serde(rename_all = "lowercase")]
151pub enum DockPosition {
152    Left,
153    Bottom,
154    Right,
155}
156
157impl DockPosition {
158    fn to_label(&self) -> &'static str {
159        match self {
160            Self::Left => "left",
161            Self::Bottom => "bottom",
162            Self::Right => "right",
163        }
164    }
165
166    // todo!()
167    // fn to_resize_handle_side(self) -> HandleSide {
168    //     match self {
169    //         Self::Left => HandleSide::Right,
170    //         Self::Bottom => HandleSide::Top,
171    //         Self::Right => HandleSide::Left,
172    //     }
173    // }
174
175    pub fn axis(&self) -> Axis {
176        match self {
177            Self::Left | Self::Right => Axis::Horizontal,
178            Self::Bottom => Axis::Vertical,
179        }
180    }
181}
182
183struct PanelEntry {
184    panel: Arc<dyn PanelHandle>,
185    // todo!()
186    // context_menu: View<ContextMenu>,
187    _subscriptions: [Subscription; 2],
188}
189
190pub struct PanelButtons {
191    dock: View<Dock>,
192}
193
194impl Dock {
195    pub fn new(position: DockPosition, cx: &mut ViewContext<Workspace>) -> View<Self> {
196        let focus_handle = cx.focus_handle();
197
198        let dock = cx.build_view(|cx: &mut ViewContext<Self>| {
199            let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
200                if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
201                    active_entry.panel.focus_handle(cx).focus(cx)
202                }
203            });
204            Self {
205                position,
206                panel_entries: Default::default(),
207                active_panel_index: 0,
208                is_open: false,
209                focus_handle: focus_handle.clone(),
210                _focus_subscription: focus_subscription,
211            }
212        });
213
214        cx.observe(&dock, move |workspace, dock, cx| {
215            if dock.read(cx).is_open() {
216                if let Some(panel) = dock.read(cx).active_panel() {
217                    if panel.is_zoomed(cx) {
218                        workspace.zoomed = Some(panel.to_any().downgrade());
219                        workspace.zoomed_position = Some(position);
220                        return;
221                    }
222                }
223            }
224            if workspace.zoomed_position == Some(position) {
225                workspace.zoomed = None;
226                workspace.zoomed_position = None;
227            }
228        })
229        .detach();
230
231        dock
232    }
233
234    pub fn position(&self) -> DockPosition {
235        self.position
236    }
237
238    pub fn is_open(&self) -> bool {
239        self.is_open
240    }
241
242    // todo!()
243    //     pub fn has_focus(&self, cx: &WindowContext) -> bool {
244    //         self.visible_panel()
245    //             .map_or(false, |panel| panel.has_focus(cx))
246    //     }
247
248    pub fn panel<T: Panel>(&self) -> Option<View<T>> {
249        self.panel_entries
250            .iter()
251            .find_map(|entry| entry.panel.to_any().clone().downcast().ok())
252    }
253
254    pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
255        self.panel_entries
256            .iter()
257            .position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
258    }
259
260    pub fn panel_index_for_persistent_name(
261        &self,
262        ui_name: &str,
263        _cx: &AppContext,
264    ) -> Option<usize> {
265        self.panel_entries
266            .iter()
267            .position(|entry| entry.panel.persistent_name() == ui_name)
268    }
269
270    pub fn active_panel_index(&self) -> usize {
271        self.active_panel_index
272    }
273
274    pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
275        if open != self.is_open {
276            self.is_open = open;
277            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
278                active_panel.panel.set_active(open, cx);
279            }
280
281            cx.notify();
282        }
283    }
284
285    pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
286        for entry in &mut self.panel_entries {
287            if entry.panel.panel_id() == panel.entity_id() {
288                if zoomed != entry.panel.is_zoomed(cx) {
289                    entry.panel.set_zoomed(zoomed, cx);
290                }
291            } else if entry.panel.is_zoomed(cx) {
292                entry.panel.set_zoomed(false, cx);
293            }
294        }
295
296        cx.notify();
297    }
298
299    pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
300        for entry in &mut self.panel_entries {
301            if entry.panel.is_zoomed(cx) {
302                entry.panel.set_zoomed(false, cx);
303            }
304        }
305    }
306
307    pub(crate) fn add_panel<T: Panel>(
308        &mut self,
309        panel: View<T>,
310        workspace: WeakView<Workspace>,
311        cx: &mut ViewContext<Self>,
312    ) {
313        let subscriptions = [
314            cx.observe(&panel, |_, _, cx| cx.notify()),
315            cx.subscribe(&panel, move |this, panel, event, cx| match event {
316                PanelEvent::ChangePosition => {
317                    let new_position = panel.read(cx).position(cx);
318
319                    let Ok(new_dock) = workspace.update(cx, |workspace, cx| {
320                        if panel.is_zoomed(cx) {
321                            workspace.zoomed_position = Some(new_position);
322                        }
323                        match new_position {
324                            DockPosition::Left => &workspace.left_dock,
325                            DockPosition::Bottom => &workspace.bottom_dock,
326                            DockPosition::Right => &workspace.right_dock,
327                        }
328                        .clone()
329                    }) else {
330                        return;
331                    };
332
333                    let was_visible = this.is_open()
334                        && this.visible_panel().map_or(false, |active_panel| {
335                            active_panel.panel_id() == Entity::entity_id(&panel)
336                        });
337
338                    this.remove_panel(&panel, cx);
339
340                    new_dock.update(cx, |new_dock, cx| {
341                        new_dock.add_panel(panel.clone(), workspace.clone(), cx);
342                        if was_visible {
343                            new_dock.set_open(true, cx);
344                            new_dock.activate_panel(new_dock.panels_len() - 1, cx);
345                        }
346                    });
347                }
348                PanelEvent::ZoomIn => {
349                    this.set_panel_zoomed(&panel.to_any(), true, cx);
350                    if !panel.focus_handle(cx).contains_focused(cx) {
351                        cx.focus_view(&panel);
352                    }
353                    workspace
354                        .update(cx, |workspace, cx| {
355                            workspace.zoomed = Some(panel.downgrade().into());
356                            workspace.zoomed_position = Some(panel.read(cx).position(cx));
357                        })
358                        .ok();
359                }
360                PanelEvent::ZoomOut => {
361                    this.set_panel_zoomed(&panel.to_any(), false, cx);
362                    workspace
363                        .update(cx, |workspace, cx| {
364                            if workspace.zoomed_position == Some(this.position) {
365                                workspace.zoomed = None;
366                                workspace.zoomed_position = None;
367                            }
368                            cx.notify();
369                        })
370                        .ok();
371                }
372                // todo!() we do not use this event in the production code (even in zed1), remove it
373                PanelEvent::Activate => {
374                    if let Some(ix) = this
375                        .panel_entries
376                        .iter()
377                        .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
378                    {
379                        this.set_open(true, cx);
380                        this.activate_panel(ix, cx);
381                        cx.focus_view(&panel);
382                    }
383                }
384                PanelEvent::Close => {
385                    if this
386                        .visible_panel()
387                        .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
388                    {
389                        this.set_open(false, cx);
390                    }
391                }
392                PanelEvent::Focus => {}
393            }),
394        ];
395
396        // todo!()
397        // let dock_view_id = cx.view_id();
398        self.panel_entries.push(PanelEntry {
399            panel: Arc::new(panel),
400            // todo!()
401            // context_menu: cx.add_view(|cx| {
402            //     let mut menu = ContextMenu::new(dock_view_id, cx);
403            //     menu.set_position_mode(OverlayPositionMode::Local);
404            //     menu
405            // }),
406            _subscriptions: subscriptions,
407        });
408        cx.notify()
409    }
410
411    pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
412        if let Some(panel_ix) = self
413            .panel_entries
414            .iter()
415            .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
416        {
417            if panel_ix == self.active_panel_index {
418                self.active_panel_index = 0;
419                self.set_open(false, cx);
420            } else if panel_ix < self.active_panel_index {
421                self.active_panel_index -= 1;
422            }
423            self.panel_entries.remove(panel_ix);
424            cx.notify();
425        }
426    }
427
428    pub fn panels_len(&self) -> usize {
429        self.panel_entries.len()
430    }
431
432    pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
433        if panel_ix != self.active_panel_index {
434            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
435                active_panel.panel.set_active(false, cx);
436            }
437
438            self.active_panel_index = panel_ix;
439            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
440                active_panel.panel.set_active(true, cx);
441            }
442
443            cx.notify();
444        }
445    }
446
447    pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
448        let entry = self.visible_entry()?;
449        Some(&entry.panel)
450    }
451
452    pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
453        Some(&self.panel_entries.get(self.active_panel_index)?.panel)
454    }
455
456    fn visible_entry(&self) -> Option<&PanelEntry> {
457        if self.is_open {
458            self.panel_entries.get(self.active_panel_index)
459        } else {
460            None
461        }
462    }
463
464    pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
465        let entry = self.visible_entry()?;
466        if entry.panel.is_zoomed(cx) {
467            Some(entry.panel.clone())
468        } else {
469            None
470        }
471    }
472
473    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
474        self.panel_entries
475            .iter()
476            .find(|entry| entry.panel.panel_id() == panel.panel_id())
477            .map(|entry| entry.panel.size(cx))
478    }
479
480    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
481        if self.is_open {
482            self.panel_entries
483                .get(self.active_panel_index)
484                .map(|entry| entry.panel.size(cx))
485        } else {
486            None
487        }
488    }
489
490    pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
491        if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
492            let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
493            entry.panel.set_size(size, cx);
494            cx.notify();
495        }
496    }
497
498    pub fn toggle_action(&self) -> Box<dyn Action> {
499        match self.position {
500            DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
501            DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
502            DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
503        }
504    }
505}
506
507impl Render for Dock {
508    type Element = Div;
509
510    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
511        if let Some(entry) = self.visible_entry() {
512            let size = entry.panel.size(cx);
513
514            let position = self.position;
515            let mut handle = div()
516                .id("resize-handle")
517                .on_drag(DraggedDock(position), |dock, cx| {
518                    cx.stop_propagation();
519                    cx.build_view(|_| dock.clone())
520                })
521                .on_click(cx.listener(|v, e: &ClickEvent, cx| {
522                    if e.down.button == MouseButton::Left && e.down.click_count == 2 {
523                        v.resize_active_panel(None, cx);
524                        cx.stop_propagation();
525                    }
526                }))
527                .z_index(1)
528                .block_mouse();
529
530            match self.position() {
531                DockPosition::Left => {
532                    handle = handle
533                        .absolute()
534                        .right(px(0.))
535                        .top(px(0.))
536                        .h_full()
537                        .w(RESIZE_HANDLE_SIZE)
538                        .cursor_col_resize();
539                }
540                DockPosition::Bottom => {
541                    handle = handle
542                        .absolute()
543                        .top(px(0.))
544                        .left(px(0.))
545                        .w_full()
546                        .h(RESIZE_HANDLE_SIZE)
547                        .cursor_row_resize();
548                }
549                DockPosition::Right => {
550                    handle = handle
551                        .absolute()
552                        .top(px(0.))
553                        .left(px(0.))
554                        .h_full()
555                        .w(RESIZE_HANDLE_SIZE)
556                        .cursor_col_resize();
557                }
558            }
559
560            div()
561                .flex()
562                .bg(cx.theme().colors().panel_background)
563                .border_color(cx.theme().colors().border)
564                .overflow_hidden()
565                .map(|this| match self.position().axis() {
566                    Axis::Horizontal => this.w(size).h_full().flex_row(),
567                    Axis::Vertical => this.h(size).w_full().flex_col(),
568                })
569                .map(|this| match self.position() {
570                    DockPosition::Left => this.border_r(),
571                    DockPosition::Right => this.border_l(),
572                    DockPosition::Bottom => this.border_t(),
573                })
574                .child(
575                    div()
576                        .map(|this| match self.position().axis() {
577                            Axis::Horizontal => this.min_w(size).h_full(),
578                            Axis::Vertical => this.min_h(size).w_full(),
579                        })
580                        .child(entry.panel.to_any()),
581                )
582                .child(handle)
583        } else {
584            div()
585        }
586    }
587}
588
589impl PanelButtons {
590    pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
591        cx.observe(&dock, |_, _, cx| cx.notify()).detach();
592        Self { dock }
593    }
594}
595
596impl Render for PanelButtons {
597    type Element = Div;
598
599    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
600        // todo!()
601        let dock = self.dock.read(cx);
602        let active_index = dock.active_panel_index;
603        let is_open = dock.is_open;
604        let dock_position = dock.position;
605
606        let (menu_anchor, menu_attach) = match dock.position {
607            DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
608            DockPosition::Bottom | DockPosition::Right => {
609                (AnchorCorner::BottomRight, AnchorCorner::TopRight)
610            }
611        };
612
613        let buttons = dock
614            .panel_entries
615            .iter()
616            .enumerate()
617            .filter_map(|(i, entry)| {
618                let icon = entry.panel.icon(cx)?;
619                let name = entry.panel.persistent_name();
620                let panel = entry.panel.clone();
621
622                let is_active_button = i == active_index && is_open;
623
624                let (action, tooltip) = if is_active_button {
625                    let action = dock.toggle_action();
626
627                    let tooltip: SharedString =
628                        format!("Close {} dock", dock.position.to_label()).into();
629
630                    (action, tooltip)
631                } else {
632                    let action = entry.panel.toggle_action(cx);
633
634                    (action, name.into())
635                };
636
637                Some(
638                    right_click_menu(name)
639                        .menu(move |cx| {
640                            const POSITIONS: [DockPosition; 3] = [
641                                DockPosition::Left,
642                                DockPosition::Right,
643                                DockPosition::Bottom,
644                            ];
645
646                            ContextMenu::build(cx, |mut menu, cx| {
647                                for position in POSITIONS {
648                                    if position != dock_position
649                                        && panel.position_is_valid(position, cx)
650                                    {
651                                        let panel = panel.clone();
652                                        menu = menu.entry(position.to_label(), None, move |cx| {
653                                            panel.set_position(position, cx);
654                                        })
655                                    }
656                                }
657                                menu
658                            })
659                        })
660                        .anchor(menu_anchor)
661                        .attach(menu_attach)
662                        .trigger(
663                            IconButton::new(name, icon)
664                                .icon_size(IconSize::Small)
665                                .selected(is_active_button)
666                                .on_click({
667                                    let action = action.boxed_clone();
668                                    move |_, cx| cx.dispatch_action(action.boxed_clone())
669                                })
670                                .tooltip(move |cx| {
671                                    Tooltip::for_action(tooltip.clone(), &*action, cx)
672                                }),
673                        ),
674                )
675            });
676
677        h_stack().gap_0p5().children(buttons)
678    }
679}
680
681impl StatusItemView for PanelButtons {
682    fn set_active_pane_item(
683        &mut self,
684        _active_pane_item: Option<&dyn crate::ItemHandle>,
685        _cx: &mut ViewContext<Self>,
686    ) {
687        // Nothing to do, panel buttons don't depend on the active center item
688    }
689}
690
691#[cfg(any(test, feature = "test-support"))]
692pub mod test {
693    use super::*;
694    use gpui::{actions, div, Div, ViewContext, WindowContext};
695
696    pub struct TestPanel {
697        pub position: DockPosition,
698        pub zoomed: bool,
699        pub active: bool,
700        pub focus_handle: FocusHandle,
701        pub size: Pixels,
702    }
703    actions!(test, [ToggleTestPanel]);
704
705    impl EventEmitter<PanelEvent> for TestPanel {}
706
707    impl TestPanel {
708        pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
709            Self {
710                position,
711                zoomed: false,
712                active: false,
713                focus_handle: cx.focus_handle(),
714                size: px(300.),
715            }
716        }
717    }
718
719    impl Render for TestPanel {
720        type Element = Div;
721
722        fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
723            div()
724        }
725    }
726
727    impl Panel for TestPanel {
728        fn persistent_name() -> &'static str {
729            "TestPanel"
730        }
731
732        fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
733            self.position
734        }
735
736        fn position_is_valid(&self, _: super::DockPosition) -> bool {
737            true
738        }
739
740        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
741            self.position = position;
742            cx.emit(PanelEvent::ChangePosition);
743        }
744
745        fn size(&self, _: &WindowContext) -> Pixels {
746            self.size
747        }
748
749        fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
750            self.size = size.unwrap_or(px(300.));
751        }
752
753        fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
754            None
755        }
756
757        fn toggle_action(&self) -> Box<dyn Action> {
758            ToggleTestPanel.boxed_clone()
759        }
760
761        fn is_zoomed(&self, _: &WindowContext) -> bool {
762            self.zoomed
763        }
764
765        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
766            self.zoomed = zoomed;
767        }
768
769        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
770            self.active = active;
771        }
772    }
773
774    impl FocusableView for TestPanel {
775        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
776            self.focus_handle.clone()
777        }
778    }
779}