dock.rs

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