dock.rs

  1use crate::{status_bar::StatusItemView, Axis, Workspace};
  2use gpui2::{
  3    Action, AnyView, Div, EventEmitter, Render, Subscription, View, ViewContext, WeakView,
  4    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 {
 44    fn id(&self) -> usize;
 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) -> usize {
 65        self.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_any()
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
435// todo!()
436// impl PanelButtons {
437//     pub fn new(
438//         dock: View<Dock>,
439//         workspace: WeakViewHandle<Workspace>,
440//         cx: &mut ViewContext<Self>,
441//     ) -> Self {
442//         cx.observe(&dock, |_, _, cx| cx.notify()).detach();
443//         Self { dock, workspace }
444//     }
445// }
446
447impl EventEmitter for PanelButtons {
448    type Event = ();
449}
450
451// impl Render for PanelButtons {
452//     type Element = ();
453
454//     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
455//         todo!("")
456//     }
457
458//     fn ui_name() -> &'static str {
459//         "PanelButtons"
460//     }
461
462//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
463//         let theme = &settings::get::<ThemeSettings>(cx).theme;
464//         let tooltip_style = theme.tooltip.clone();
465//         let theme = &theme.workspace.status_bar.panel_buttons;
466//         let button_style = theme.button.clone();
467//         let dock = self.dock.read(cx);
468//         let active_ix = dock.active_panel_index;
469//         let is_open = dock.is_open;
470//         let dock_position = dock.position;
471//         let group_style = match dock_position {
472//             DockPosition::Left => theme.group_left,
473//             DockPosition::Bottom => theme.group_bottom,
474//             DockPosition::Right => theme.group_right,
475//         };
476//         let menu_corner = match dock_position {
477//             DockPosition::Left => AnchorCorner::BottomLeft,
478//             DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
479//         };
480
481//         let panels = dock
482//             .panel_entries
483//             .iter()
484//             .map(|item| (item.panel.clone(), item.context_menu.clone()))
485//             .collect::<Vec<_>>();
486//         Flex::row()
487//             .with_children(panels.into_iter().enumerate().filter_map(
488//                 |(panel_ix, (view, context_menu))| {
489//                     let icon_path = view.icon_path(cx)?;
490//                     let is_active = is_open && panel_ix == active_ix;
491//                     let (tooltip, tooltip_action) = if is_active {
492//                         (
493//                             format!("Close {} dock", dock_position.to_label()),
494//                             Some(match dock_position {
495//                                 DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
496//                                 DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
497//                                 DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
498//                             }),
499//                         )
500//                     } else {
501//                         view.icon_tooltip(cx)
502//                     };
503//                     Some(
504//                         Stack::new()
505//                             .with_child(
506//                                 MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
507//                                     let style = button_style.in_state(is_active);
508
509//                                     let style = style.style_for(state);
510//                                     Flex::row()
511//                                         .with_child(
512//                                             Svg::new(icon_path)
513//                                                 .with_color(style.icon_color)
514//                                                 .constrained()
515//                                                 .with_width(style.icon_size)
516//                                                 .aligned(),
517//                                         )
518//                                         .with_children(if let Some(label) = view.icon_label(cx) {
519//                                             Some(
520//                                                 Label::new(label, style.label.text.clone())
521//                                                     .contained()
522//                                                     .with_style(style.label.container)
523//                                                     .aligned(),
524//                                             )
525//                                         } else {
526//                                             None
527//                                         })
528//                                         .constrained()
529//                                         .with_height(style.icon_size)
530//                                         .contained()
531//                                         .with_style(style.container)
532//                                 })
533//                                 .with_cursor_style(CursorStyle::PointingHand)
534//                                 .on_click(MouseButton::Left, {
535//                                     let tooltip_action =
536//                                         tooltip_action.as_ref().map(|action| action.boxed_clone());
537//                                     move |_, this, cx| {
538//                                         if let Some(tooltip_action) = &tooltip_action {
539//                                             let window = cx.window();
540//                                             let view_id = this.workspace.id();
541//                                             let tooltip_action = tooltip_action.boxed_clone();
542//                                             cx.spawn(|_, mut cx| async move {
543//                                                 window.dispatch_action(
544//                                                     view_id,
545//                                                     &*tooltip_action,
546//                                                     &mut cx,
547//                                                 );
548//                                             })
549//                                             .detach();
550//                                         }
551//                                     }
552//                                 })
553//                                 .on_click(MouseButton::Right, {
554//                                     let view = view.clone();
555//                                     let menu = context_menu.clone();
556//                                     move |_, _, cx| {
557//                                         const POSITIONS: [DockPosition; 3] = [
558//                                             DockPosition::Left,
559//                                             DockPosition::Right,
560//                                             DockPosition::Bottom,
561//                                         ];
562
563//                                         menu.update(cx, |menu, cx| {
564//                                             let items = POSITIONS
565//                                                 .into_iter()
566//                                                 .filter(|position| {
567//                                                     *position != dock_position
568//                                                         && view.position_is_valid(*position, cx)
569//                                                 })
570//                                                 .map(|position| {
571//                                                     let view = view.clone();
572//                                                     ContextMenuItem::handler(
573//                                                         format!("Dock {}", position.to_label()),
574//                                                         move |cx| view.set_position(position, cx),
575//                                                     )
576//                                                 })
577//                                                 .collect();
578//                                             menu.show(Default::default(), menu_corner, items, cx);
579//                                         })
580//                                     }
581//                                 })
582//                                 .with_tooltip::<Self>(
583//                                     panel_ix,
584//                                     tooltip,
585//                                     tooltip_action,
586//                                     tooltip_style.clone(),
587//                                     cx,
588//                                 ),
589//                             )
590//                             .with_child(ChildView::new(&context_menu, cx)),
591//                     )
592//                 },
593//             ))
594//             .contained()
595//             .with_style(group_style)
596//             .into_any()
597//     }
598// }
599
600impl Render for PanelButtons {
601    type Element = Div<Self>;
602
603    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
604        todo!()
605    }
606}
607
608impl StatusItemView for PanelButtons {
609    fn set_active_pane_item(
610        &mut self,
611        active_pane_item: Option<&dyn crate::ItemHandle>,
612        cx: &mut ViewContext<Self>,
613    ) {
614        todo!()
615    }
616}
617
618#[cfg(any(test, feature = "test-support"))]
619pub mod test {
620    use super::*;
621    use gpui2::{div, Div, ViewContext, WindowContext};
622
623    #[derive(Debug)]
624    pub enum TestPanelEvent {
625        PositionChanged,
626        Activated,
627        Closed,
628        ZoomIn,
629        ZoomOut,
630        Focus,
631    }
632
633    pub struct TestPanel {
634        pub position: DockPosition,
635        pub zoomed: bool,
636        pub active: bool,
637        pub has_focus: bool,
638        pub size: f32,
639    }
640
641    impl EventEmitter for TestPanel {
642        type Event = TestPanelEvent;
643    }
644
645    impl TestPanel {
646        pub fn new(position: DockPosition) -> Self {
647            Self {
648                position,
649                zoomed: false,
650                active: false,
651                has_focus: false,
652                size: 300.,
653            }
654        }
655    }
656
657    impl Render for TestPanel {
658        type Element = Div<Self>;
659
660        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
661            div()
662        }
663    }
664
665    impl Panel for TestPanel {
666        fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition {
667            self.position
668        }
669
670        fn position_is_valid(&self, _: super::DockPosition) -> bool {
671            true
672        }
673
674        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
675            self.position = position;
676            cx.emit(TestPanelEvent::PositionChanged);
677        }
678
679        fn is_zoomed(&self, _: &WindowContext) -> bool {
680            self.zoomed
681        }
682
683        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
684            self.zoomed = zoomed;
685        }
686
687        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
688            self.active = active;
689        }
690
691        fn size(&self, _: &WindowContext) -> f32 {
692            self.size
693        }
694
695        fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
696            self.size = size.unwrap_or(300.);
697        }
698
699        fn icon_path(&self, _: &WindowContext) -> Option<&'static str> {
700            Some("icons/test_panel.svg")
701        }
702
703        fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
704            ("Test Panel".into(), None)
705        }
706
707        fn should_change_position_on_event(event: &Self::Event) -> bool {
708            matches!(event, TestPanelEvent::PositionChanged)
709        }
710
711        fn should_zoom_in_on_event(event: &Self::Event) -> bool {
712            matches!(event, TestPanelEvent::ZoomIn)
713        }
714
715        fn should_zoom_out_on_event(event: &Self::Event) -> bool {
716            matches!(event, TestPanelEvent::ZoomOut)
717        }
718
719        fn should_activate_on_event(event: &Self::Event) -> bool {
720            matches!(event, TestPanelEvent::Activated)
721        }
722
723        fn should_close_on_event(event: &Self::Event) -> bool {
724            matches!(event, TestPanelEvent::Closed)
725        }
726
727        fn has_focus(&self, _cx: &WindowContext) -> bool {
728            self.has_focus
729        }
730
731        fn is_focus_event(event: &Self::Event) -> bool {
732            matches!(event, TestPanelEvent::Focus)
733        }
734    }
735}