dock.rs

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