dock.rs

  1use crate::{status_bar::StatusItemView, Axis, Workspace};
  2use gpui2::{
  3    Action, AnyView, EventEmitter, Render, Subscription, View, ViewContext, WeakView, WindowContext,
  4};
  5use schemars::JsonSchema;
  6use serde::{Deserialize, Serialize};
  7use std::sync::Arc;
  8
  9pub trait Panel: Render + EventEmitter {
 10    fn position(&self, cx: &WindowContext) -> DockPosition;
 11    fn position_is_valid(&self, position: DockPosition) -> bool;
 12    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
 13    fn size(&self, cx: &WindowContext) -> f32;
 14    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>);
 15    fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>;
 16    fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>);
 17    fn icon_label(&self, _: &WindowContext) -> Option<String> {
 18        None
 19    }
 20    fn should_change_position_on_event(_: &Self::Event) -> bool;
 21    fn should_zoom_in_on_event(_: &Self::Event) -> bool {
 22        false
 23    }
 24    fn should_zoom_out_on_event(_: &Self::Event) -> bool {
 25        false
 26    }
 27    fn is_zoomed(&self, _cx: &WindowContext) -> bool {
 28        false
 29    }
 30    fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
 31    fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
 32    fn should_activate_on_event(_: &Self::Event) -> bool {
 33        false
 34    }
 35    fn should_close_on_event(_: &Self::Event) -> bool {
 36        false
 37    }
 38    fn has_focus(&self, cx: &WindowContext) -> bool;
 39    fn is_focus_event(_: &Self::Event) -> bool;
 40}
 41
 42pub trait PanelHandle {
 43    fn id(&self) -> usize;
 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 to_any(&self) -> AnyView;
 57}
 58
 59impl<T> PanelHandle for View<T>
 60where
 61    T: Panel,
 62{
 63    fn id(&self) -> usize {
 64        self.id()
 65    }
 66
 67    fn position(&self, cx: &WindowContext) -> DockPosition {
 68        self.read(cx).position(cx)
 69    }
 70
 71    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
 72        self.read(cx).position_is_valid(position)
 73    }
 74
 75    fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
 76        self.update(cx, |this, cx| this.set_position(position, cx))
 77    }
 78
 79    fn size(&self, cx: &WindowContext) -> f32 {
 80        self.read(cx).size(cx)
 81    }
 82
 83    fn set_size(&self, size: Option<f32>, cx: &mut WindowContext) {
 84        self.update(cx, |this, cx| this.set_size(size, 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 icon_path(&self, cx: &WindowContext) -> Option<&'static str> {
100        self.read(cx).icon_path(cx)
101    }
102
103    fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>) {
104        self.read(cx).icon_tooltip()
105    }
106
107    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
108        self.read(cx).icon_label(cx)
109    }
110
111    fn has_focus(&self, cx: &WindowContext) -> bool {
112        self.read(cx).has_focus(cx)
113    }
114
115    fn to_any(&self) -> AnyView {
116        self.clone().into_any()
117    }
118}
119
120impl From<&dyn PanelHandle> for AnyView {
121    fn from(val: &dyn PanelHandle) -> Self {
122        val.to_any()
123    }
124}
125
126pub struct Dock {
127    position: DockPosition,
128    panel_entries: Vec<PanelEntry>,
129    is_open: bool,
130    active_panel_index: usize,
131}
132
133#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
134#[serde(rename_all = "lowercase")]
135pub enum DockPosition {
136    Left,
137    Bottom,
138    Right,
139}
140
141impl DockPosition {
142    fn to_label(&self) -> &'static str {
143        match self {
144            Self::Left => "left",
145            Self::Bottom => "bottom",
146            Self::Right => "right",
147        }
148    }
149
150    // todo!()
151    // fn to_resize_handle_side(self) -> HandleSide {
152    //     match self {
153    //         Self::Left => HandleSide::Right,
154    //         Self::Bottom => HandleSide::Top,
155    //         Self::Right => HandleSide::Left,
156    //     }
157    // }
158
159    pub fn axis(&self) -> Axis {
160        match self {
161            Self::Left | Self::Right => Axis::Horizontal,
162            Self::Bottom => Axis::Vertical,
163        }
164    }
165}
166
167struct PanelEntry {
168    panel: Arc<dyn PanelHandle>,
169    // todo!()
170    // context_menu: View<ContextMenu>,
171    _subscriptions: [Subscription; 2],
172}
173
174pub struct PanelButtons {
175    dock: View<Dock>,
176    workspace: WeakView<Workspace>,
177}
178
179// impl Dock {
180//     pub fn new(position: DockPosition) -> Self {
181//         Self {
182//             position,
183//             panel_entries: Default::default(),
184//             active_panel_index: 0,
185//             is_open: false,
186//         }
187//     }
188
189//     pub fn position(&self) -> DockPosition {
190//         self.position
191//     }
192
193//     pub fn is_open(&self) -> bool {
194//         self.is_open
195//     }
196
197//     pub fn has_focus(&self, cx: &WindowContext) -> bool {
198//         self.visible_panel()
199//             .map_or(false, |panel| panel.has_focus(cx))
200//     }
201
202//     pub fn panel<T: Panel>(&self) -> Option<View<T>> {
203//         self.panel_entries
204//             .iter()
205//             .find_map(|entry| entry.panel.as_any().clone().downcast())
206//     }
207
208//     pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
209//         self.panel_entries
210//             .iter()
211//             .position(|entry| entry.panel.as_any().is::<T>())
212//     }
213
214//     pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> {
215//         todo!()
216//         // self.panel_entries.iter().position(|entry| {
217//         //     let panel = entry.panel.as_any();
218//         //     cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
219//         // })
220//     }
221
222//     pub fn active_panel_index(&self) -> usize {
223//         self.active_panel_index
224//     }
225
226//     pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
227//         if open != self.is_open {
228//             self.is_open = open;
229//             if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
230//                 active_panel.panel.set_active(open, cx);
231//             }
232
233//             cx.notify();
234//         }
235//     }
236
237//     pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
238//         for entry in &mut self.panel_entries {
239//             if entry.panel.as_any() == panel {
240//                 if zoomed != entry.panel.is_zoomed(cx) {
241//                     entry.panel.set_zoomed(zoomed, cx);
242//                 }
243//             } else if entry.panel.is_zoomed(cx) {
244//                 entry.panel.set_zoomed(false, cx);
245//             }
246//         }
247
248//         cx.notify();
249//     }
250
251//     pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
252//         for entry in &mut self.panel_entries {
253//             if entry.panel.is_zoomed(cx) {
254//                 entry.panel.set_zoomed(false, cx);
255//             }
256//         }
257//     }
258
259//     pub(crate) fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
260//         let subscriptions = [
261//             cx.observe(&panel, |_, _, cx| cx.notify()),
262//             cx.subscribe(&panel, |this, panel, event, cx| {
263//                 if T::should_activate_on_event(event) {
264//                     if let Some(ix) = this
265//                         .panel_entries
266//                         .iter()
267//                         .position(|entry| entry.panel.id() == panel.id())
268//                     {
269//                         this.set_open(true, cx);
270//                         this.activate_panel(ix, cx);
271//                         cx.focus(&panel);
272//                     }
273//                 } else if T::should_close_on_event(event)
274//                     && this.visible_panel().map_or(false, |p| p.id() == panel.id())
275//                 {
276//                     this.set_open(false, cx);
277//                 }
278//             }),
279//         ];
280
281//         let dock_view_id = cx.view_id();
282//         self.panel_entries.push(PanelEntry {
283//             panel: Arc::new(panel),
284//             // todo!()
285//             // context_menu: cx.add_view(|cx| {
286//             //     let mut menu = ContextMenu::new(dock_view_id, cx);
287//             //     menu.set_position_mode(OverlayPositionMode::Local);
288//             //     menu
289//             // }),
290//             _subscriptions: subscriptions,
291//         });
292//         cx.notify()
293//     }
294
295//     pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
296//         if let Some(panel_ix) = self
297//             .panel_entries
298//             .iter()
299//             .position(|entry| entry.panel.id() == panel.id())
300//         {
301//             if panel_ix == self.active_panel_index {
302//                 self.active_panel_index = 0;
303//                 self.set_open(false, cx);
304//             } else if panel_ix < self.active_panel_index {
305//                 self.active_panel_index -= 1;
306//             }
307//             self.panel_entries.remove(panel_ix);
308//             cx.notify();
309//         }
310//     }
311
312//     pub fn panels_len(&self) -> usize {
313//         self.panel_entries.len()
314//     }
315
316//     pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
317//         if panel_ix != self.active_panel_index {
318//             if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
319//                 active_panel.panel.set_active(false, cx);
320//             }
321
322//             self.active_panel_index = panel_ix;
323//             if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
324//                 active_panel.panel.set_active(true, cx);
325//             }
326
327//             cx.notify();
328//         }
329//     }
330
331//     pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
332//         let entry = self.visible_entry()?;
333//         Some(&entry.panel)
334//     }
335
336//     pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
337//         Some(&self.panel_entries.get(self.active_panel_index)?.panel)
338//     }
339
340//     fn visible_entry(&self) -> Option<&PanelEntry> {
341//         if self.is_open {
342//             self.panel_entries.get(self.active_panel_index)
343//         } else {
344//             None
345//         }
346//     }
347
348//     pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
349//         let entry = self.visible_entry()?;
350//         if entry.panel.is_zoomed(cx) {
351//             Some(entry.panel.clone())
352//         } else {
353//             None
354//         }
355//     }
356
357//     pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
358//         self.panel_entries
359//             .iter()
360//             .find(|entry| entry.panel.id() == panel.id())
361//             .map(|entry| entry.panel.size(cx))
362//     }
363
364//     pub fn active_panel_size(&self, cx: &WindowContext) -> Option<f32> {
365//         if self.is_open {
366//             self.panel_entries
367//                 .get(self.active_panel_index)
368//                 .map(|entry| entry.panel.size(cx))
369//         } else {
370//             None
371//         }
372//     }
373
374//     pub fn resize_active_panel(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
375//         if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
376//             entry.panel.set_size(size, cx);
377//             cx.notify();
378//         }
379//     }
380
381//     pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
382//         todo!()
383// if let Some(active_entry) = self.visible_entry() {
384//     Empty::new()
385//         .into_any()
386//         .contained()
387//         .with_style(self.style(cx))
388//         .resizable::<WorkspaceBounds>(
389//             self.position.to_resize_handle_side(),
390//             active_entry.panel.size(cx),
391//             |_, _, _| {},
392//         )
393//         .into_any()
394// } else {
395//     Empty::new().into_any()
396// }
397//     }
398// }
399
400// todo!()
401// impl View for Dock {
402//     fn ui_name() -> &'static str {
403//         "Dock"
404//     }
405
406//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
407//         if let Some(active_entry) = self.visible_entry() {
408//             let style = self.style(cx);
409//             ChildView::new(active_entry.panel.as_any(), cx)
410//                 .contained()
411//                 .with_style(style)
412//                 .resizable::<WorkspaceBounds>(
413//                     self.position.to_resize_handle_side(),
414//                     active_entry.panel.size(cx),
415//                     |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx),
416//                 )
417//                 .into_any()
418//         } else {
419//             Empty::new().into_any()
420//         }
421//     }
422
423//     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
424//         if cx.is_self_focused() {
425//             if let Some(active_entry) = self.visible_entry() {
426//                 cx.focus(active_entry.panel.as_any());
427//             } else {
428//                 cx.focus_parent();
429//             }
430//         }
431//     }
432// }
433
434// todo!()
435// impl PanelButtons {
436//     pub fn new(
437//         dock: View<Dock>,
438//         workspace: WeakViewHandle<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
599// impl StatusItemView for PanelButtons {
600//     fn set_active_pane_item(
601//         &mut self,
602//         active_pane_item: Option<&dyn crate::ItemHandle>,
603//         cx: &mut ViewContext<Self>,
604//     ) {
605//         todo!()
606//     }
607// }
608
609#[cfg(any(test, feature = "test-support"))]
610pub mod test {
611    use super::*;
612    use gpui2::{div, Div, ViewContext, WindowContext};
613
614    #[derive(Debug)]
615    pub enum TestPanelEvent {
616        PositionChanged,
617        Activated,
618        Closed,
619        ZoomIn,
620        ZoomOut,
621        Focus,
622    }
623
624    pub struct TestPanel {
625        pub position: DockPosition,
626        pub zoomed: bool,
627        pub active: bool,
628        pub has_focus: bool,
629        pub size: f32,
630    }
631
632    impl EventEmitter for TestPanel {
633        type Event = TestPanelEvent;
634    }
635
636    impl TestPanel {
637        pub fn new(position: DockPosition) -> Self {
638            Self {
639                position,
640                zoomed: false,
641                active: false,
642                has_focus: false,
643                size: 300.,
644            }
645        }
646    }
647
648    impl Render for TestPanel {
649        type Element = Div<Self>;
650
651        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
652            div()
653        }
654    }
655
656    impl Panel for TestPanel {
657        fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition {
658            self.position
659        }
660
661        fn position_is_valid(&self, _: super::DockPosition) -> bool {
662            true
663        }
664
665        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
666            self.position = position;
667            cx.emit(TestPanelEvent::PositionChanged);
668        }
669
670        fn is_zoomed(&self, _: &WindowContext) -> bool {
671            self.zoomed
672        }
673
674        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
675            self.zoomed = zoomed;
676        }
677
678        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
679            self.active = active;
680        }
681
682        fn size(&self, _: &WindowContext) -> f32 {
683            self.size
684        }
685
686        fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
687            self.size = size.unwrap_or(300.);
688        }
689
690        fn icon_path(&self, _: &WindowContext) -> Option<&'static str> {
691            Some("icons/test_panel.svg")
692        }
693
694        fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
695            ("Test Panel".into(), None)
696        }
697
698        fn should_change_position_on_event(event: &Self::Event) -> bool {
699            matches!(event, TestPanelEvent::PositionChanged)
700        }
701
702        fn should_zoom_in_on_event(event: &Self::Event) -> bool {
703            matches!(event, TestPanelEvent::ZoomIn)
704        }
705
706        fn should_zoom_out_on_event(event: &Self::Event) -> bool {
707            matches!(event, TestPanelEvent::ZoomOut)
708        }
709
710        fn should_activate_on_event(event: &Self::Event) -> bool {
711            matches!(event, TestPanelEvent::Activated)
712        }
713
714        fn should_close_on_event(event: &Self::Event) -> bool {
715            matches!(event, TestPanelEvent::Closed)
716        }
717
718        fn has_focus(&self, _cx: &WindowContext) -> bool {
719            self.has_focus
720        }
721
722        fn is_focus_event(event: &Self::Event) -> bool {
723            matches!(event, TestPanelEvent::Focus)
724        }
725    }
726}