dock.rs

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