dock.rs

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