dock.rs

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