dock.rs

  1use crate::DraggedDock;
  2use crate::{status_bar::StatusItemView, Workspace};
  3use gpui::{
  4    div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Div, Entity, EntityId,
  5    EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
  6    SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
  7};
  8use schemars::JsonSchema;
  9use serde::{Deserialize, Serialize};
 10use std::sync::Arc;
 11use ui::{h_stack, ContextMenu, IconButton, Tooltip};
 12use ui::{prelude::*, right_click_menu};
 13
 14const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
 15
 16pub enum PanelEvent {
 17    ChangePosition,
 18    ZoomIn,
 19    ZoomOut,
 20    Activate,
 21    Close,
 22    Focus,
 23}
 24
 25pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
 26    fn persistent_name() -> &'static str;
 27    fn position(&self, cx: &WindowContext) -> DockPosition;
 28    fn position_is_valid(&self, position: DockPosition) -> bool;
 29    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
 30    fn size(&self, cx: &WindowContext) -> Pixels;
 31    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
 32    // todo!("We should have a icon tooltip method, rather than using persistant_name")
 33    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
 34    fn toggle_action(&self) -> Box<dyn Action>;
 35    fn icon_label(&self, _: &WindowContext) -> Option<String> {
 36        None
 37    }
 38    fn is_zoomed(&self, _cx: &WindowContext) -> bool {
 39        false
 40    }
 41    fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
 42    fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
 43}
 44
 45pub trait PanelHandle: Send + Sync {
 46    fn panel_id(&self) -> EntityId;
 47    fn persistent_name(&self) -> &'static str;
 48    fn position(&self, cx: &WindowContext) -> DockPosition;
 49    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
 50    fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
 51    fn is_zoomed(&self, cx: &WindowContext) -> bool;
 52    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
 53    fn set_active(&self, active: bool, cx: &mut WindowContext);
 54    fn size(&self, cx: &WindowContext) -> Pixels;
 55    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
 56    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
 57    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
 58    fn icon_label(&self, cx: &WindowContext) -> Option<String>;
 59    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 60    fn to_any(&self) -> AnyView;
 61}
 62
 63impl<T> PanelHandle for View<T>
 64where
 65    T: Panel,
 66{
 67    fn panel_id(&self) -> EntityId {
 68        Entity::entity_id(self)
 69    }
 70
 71    fn persistent_name(&self) -> &'static str {
 72        T::persistent_name()
 73    }
 74
 75    fn position(&self, cx: &WindowContext) -> DockPosition {
 76        self.read(cx).position(cx)
 77    }
 78
 79    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
 80        self.read(cx).position_is_valid(position)
 81    }
 82
 83    fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
 84        self.update(cx, |this, cx| this.set_position(position, cx))
 85    }
 86
 87    fn is_zoomed(&self, cx: &WindowContext) -> bool {
 88        self.read(cx).is_zoomed(cx)
 89    }
 90
 91    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
 92        self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
 93    }
 94
 95    fn set_active(&self, active: bool, cx: &mut WindowContext) {
 96        self.update(cx, |this, cx| this.set_active(active, cx))
 97    }
 98
 99    fn size(&self, cx: &WindowContext) -> Pixels {
100        self.read(cx).size(cx)
101    }
102
103    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
104        self.update(cx, |this, cx| this.set_size(size, cx))
105    }
106
107    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
108        self.read(cx).icon(cx)
109    }
110
111    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action> {
112        self.read(cx).toggle_action()
113    }
114
115    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
116        self.read(cx).icon_label(cx)
117    }
118
119    fn to_any(&self) -> AnyView {
120        self.clone().into()
121    }
122
123    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
124        self.read(cx).focus_handle(cx).clone()
125    }
126}
127
128impl From<&dyn PanelHandle> for AnyView {
129    fn from(val: &dyn PanelHandle) -> Self {
130        val.to_any()
131    }
132}
133
134pub struct Dock {
135    position: DockPosition,
136    panel_entries: Vec<PanelEntry>,
137    is_open: bool,
138    active_panel_index: usize,
139    focus_handle: FocusHandle,
140    _focus_subscription: Subscription,
141}
142
143impl FocusableView for Dock {
144    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
145        self.focus_handle.clone()
146    }
147}
148
149#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
150#[serde(rename_all = "lowercase")]
151pub enum DockPosition {
152    Left,
153    Bottom,
154    Right,
155}
156
157impl DockPosition {
158    fn to_label(&self) -> &'static str {
159        match self {
160            Self::Left => "left",
161            Self::Bottom => "bottom",
162            Self::Right => "right",
163        }
164    }
165
166    // todo!()
167    // fn to_resize_handle_side(self) -> HandleSide {
168    //     match self {
169    //         Self::Left => HandleSide::Right,
170    //         Self::Bottom => HandleSide::Top,
171    //         Self::Right => HandleSide::Left,
172    //     }
173    // }
174
175    pub fn axis(&self) -> Axis {
176        match self {
177            Self::Left | Self::Right => Axis::Horizontal,
178            Self::Bottom => Axis::Vertical,
179        }
180    }
181}
182
183struct PanelEntry {
184    panel: Arc<dyn PanelHandle>,
185    // todo!()
186    // context_menu: View<ContextMenu>,
187    _subscriptions: [Subscription; 2],
188}
189
190pub struct PanelButtons {
191    dock: View<Dock>,
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: 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.panel_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.panel_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(new_dock.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.panel_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.panel_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.panel_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<Pixels> {
452        self.panel_entries
453            .iter()
454            .find(|entry| entry.panel.panel_id() == panel.panel_id())
455            .map(|entry| entry.panel.size(cx))
456    }
457
458    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
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<Pixels>, cx: &mut ViewContext<Self>) {
469        if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
470            let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
471            entry.panel.set_size(size, cx);
472            cx.notify();
473        }
474    }
475
476    pub fn toggle_action(&self) -> Box<dyn Action> {
477        match self.position {
478            DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
479            DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
480            DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
481        }
482    }
483}
484
485impl Render for Dock {
486    type Element = Div;
487
488    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
489        if let Some(entry) = self.visible_entry() {
490            let size = entry.panel.size(cx);
491
492            let position = self.position;
493            let mut handle = div()
494                .id("resize-handle")
495                .on_drag(DraggedDock(position), |dock, cx| {
496                    cx.stop_propagation();
497                    cx.build_view(|_| dock.clone())
498                })
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                        cx.stop_propagation();
503                    }
504                }))
505                .z_index(1)
506                .block_mouse();
507
508            match self.position() {
509                DockPosition::Left => {
510                    handle = handle
511                        .absolute()
512                        .right(px(0.))
513                        .top(px(0.))
514                        .h_full()
515                        .w(RESIZE_HANDLE_SIZE)
516                        .cursor_col_resize();
517                }
518                DockPosition::Bottom => {
519                    handle = handle
520                        .absolute()
521                        .top(px(0.))
522                        .left(px(0.))
523                        .w_full()
524                        .h(RESIZE_HANDLE_SIZE)
525                        .cursor_row_resize();
526                }
527                DockPosition::Right => {
528                    handle = handle
529                        .absolute()
530                        .top(px(0.))
531                        .left(px(0.))
532                        .h_full()
533                        .w(RESIZE_HANDLE_SIZE)
534                        .cursor_col_resize();
535                }
536            }
537
538            div()
539                .flex()
540                .bg(cx.theme().colors().panel_background)
541                .border_color(cx.theme().colors().border)
542                .overflow_hidden()
543                .map(|this| match self.position().axis() {
544                    Axis::Horizontal => this.w(size).h_full().flex_row(),
545                    Axis::Vertical => this.h(size).w_full().flex_col(),
546                })
547                .map(|this| match self.position() {
548                    DockPosition::Left => this.border_r(),
549                    DockPosition::Right => this.border_l(),
550                    DockPosition::Bottom => this.border_t(),
551                })
552                .child(
553                    div()
554                        .map(|this| match self.position().axis() {
555                            Axis::Horizontal => this.min_w(size).h_full(),
556                            Axis::Vertical => this.min_h(size).w_full(),
557                        })
558                        .child(entry.panel.to_any()),
559                )
560                .child(handle)
561        } else {
562            div()
563        }
564    }
565}
566
567impl PanelButtons {
568    pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
569        cx.observe(&dock, |_, _, cx| cx.notify()).detach();
570        Self { dock }
571    }
572}
573
574impl Render for PanelButtons {
575    type Element = Div;
576
577    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
578        // todo!()
579        let dock = self.dock.read(cx);
580        let active_index = dock.active_panel_index;
581        let is_open = dock.is_open;
582        let dock_position = dock.position;
583
584        let (menu_anchor, menu_attach) = match dock.position {
585            DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
586            DockPosition::Bottom | DockPosition::Right => {
587                (AnchorCorner::BottomRight, AnchorCorner::TopRight)
588            }
589        };
590
591        let buttons = dock
592            .panel_entries
593            .iter()
594            .enumerate()
595            .filter_map(|(i, entry)| {
596                let icon = entry.panel.icon(cx)?;
597                let name = entry.panel.persistent_name();
598                let panel = entry.panel.clone();
599
600                let is_active_button = i == active_index && is_open;
601
602                let (action, tooltip) = if is_active_button {
603                    let action = dock.toggle_action();
604
605                    let tooltip: SharedString =
606                        format!("Close {} dock", dock.position.to_label()).into();
607
608                    (action, tooltip)
609                } else {
610                    let action = entry.panel.toggle_action(cx);
611
612                    (action, name.into())
613                };
614
615                Some(
616                    right_click_menu(name)
617                        .menu(move |cx| {
618                            const POSITIONS: [DockPosition; 3] = [
619                                DockPosition::Left,
620                                DockPosition::Right,
621                                DockPosition::Bottom,
622                            ];
623
624                            ContextMenu::build(cx, |mut menu, cx| {
625                                for position in POSITIONS {
626                                    if position != dock_position
627                                        && panel.position_is_valid(position, cx)
628                                    {
629                                        let panel = panel.clone();
630                                        menu = menu.entry(position.to_label(), None, move |cx| {
631                                            panel.set_position(position, cx);
632                                        })
633                                    }
634                                }
635                                menu
636                            })
637                        })
638                        .anchor(menu_anchor)
639                        .attach(menu_attach)
640                        .trigger(
641                            IconButton::new(name, icon)
642                                .icon_size(IconSize::Small)
643                                .selected(is_active_button)
644                                .on_click({
645                                    let action = action.boxed_clone();
646                                    move |_, cx| cx.dispatch_action(action.boxed_clone())
647                                })
648                                .tooltip(move |cx| {
649                                    Tooltip::for_action(tooltip.clone(), &*action, cx)
650                                }),
651                        ),
652                )
653            });
654
655        h_stack().gap_0p5().children(buttons)
656    }
657}
658
659impl StatusItemView for PanelButtons {
660    fn set_active_pane_item(
661        &mut self,
662        _active_pane_item: Option<&dyn crate::ItemHandle>,
663        _cx: &mut ViewContext<Self>,
664    ) {
665        // Nothing to do, panel buttons don't depend on the active center item
666    }
667}
668
669#[cfg(any(test, feature = "test-support"))]
670pub mod test {
671    use super::*;
672    use gpui::{actions, div, Div, ViewContext, WindowContext};
673
674    pub struct TestPanel {
675        pub position: DockPosition,
676        pub zoomed: bool,
677        pub active: bool,
678        pub focus_handle: FocusHandle,
679        pub size: Pixels,
680    }
681    actions!(test, [ToggleTestPanel]);
682
683    impl EventEmitter<PanelEvent> for TestPanel {}
684
685    impl TestPanel {
686        pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
687            Self {
688                position,
689                zoomed: false,
690                active: false,
691                focus_handle: cx.focus_handle(),
692                size: px(300.),
693            }
694        }
695    }
696
697    impl Render for TestPanel {
698        type Element = Div;
699
700        fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
701            div()
702        }
703    }
704
705    impl Panel for TestPanel {
706        fn persistent_name() -> &'static str {
707            "TestPanel"
708        }
709
710        fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
711            self.position
712        }
713
714        fn position_is_valid(&self, _: super::DockPosition) -> bool {
715            true
716        }
717
718        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
719            self.position = position;
720            cx.emit(PanelEvent::ChangePosition);
721        }
722
723        fn size(&self, _: &WindowContext) -> Pixels {
724            self.size
725        }
726
727        fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
728            self.size = size.unwrap_or(px(300.));
729        }
730
731        fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
732            None
733        }
734
735        fn toggle_action(&self) -> Box<dyn Action> {
736            ToggleTestPanel.boxed_clone()
737        }
738
739        fn is_zoomed(&self, _: &WindowContext) -> bool {
740            self.zoomed
741        }
742
743        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
744            self.zoomed = zoomed;
745        }
746
747        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
748            self.active = active;
749        }
750    }
751
752    impl FocusableView for TestPanel {
753        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
754            self.focus_handle.clone()
755        }
756    }
757}