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<Workspace>) -> View<Self> {
196 let focus_handle = cx.focus_handle();
197
198 let dock = cx.build_view(|cx: &mut ViewContext<Self>| {
199 let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
200 if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
201 active_entry.panel.focus_handle(cx).focus(cx)
202 }
203 });
204 Self {
205 position,
206 panel_entries: Default::default(),
207 active_panel_index: 0,
208 is_open: false,
209 focus_handle: focus_handle.clone(),
210 _focus_subscription: focus_subscription,
211 }
212 });
213
214 cx.observe(&dock, move |workspace, dock, cx| {
215 if dock.read(cx).is_open() {
216 if let Some(panel) = dock.read(cx).active_panel() {
217 if panel.is_zoomed(cx) {
218 workspace.zoomed = Some(panel.to_any().downgrade());
219 workspace.zoomed_position = Some(position);
220 return;
221 }
222 }
223 }
224 if workspace.zoomed_position == Some(position) {
225 workspace.zoomed = None;
226 workspace.zoomed_position = None;
227 }
228 })
229 .detach();
230
231 dock
232 }
233
234 pub fn position(&self) -> DockPosition {
235 self.position
236 }
237
238 pub fn is_open(&self) -> bool {
239 self.is_open
240 }
241
242 // todo!()
243 // pub fn has_focus(&self, cx: &WindowContext) -> bool {
244 // self.visible_panel()
245 // .map_or(false, |panel| panel.has_focus(cx))
246 // }
247
248 pub fn panel<T: Panel>(&self) -> Option<View<T>> {
249 self.panel_entries
250 .iter()
251 .find_map(|entry| entry.panel.to_any().clone().downcast().ok())
252 }
253
254 pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
255 self.panel_entries
256 .iter()
257 .position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
258 }
259
260 pub fn panel_index_for_persistent_name(
261 &self,
262 ui_name: &str,
263 _cx: &AppContext,
264 ) -> Option<usize> {
265 self.panel_entries
266 .iter()
267 .position(|entry| entry.panel.persistent_name() == ui_name)
268 }
269
270 pub fn active_panel_index(&self) -> usize {
271 self.active_panel_index
272 }
273
274 pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
275 if open != self.is_open {
276 self.is_open = open;
277 if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
278 active_panel.panel.set_active(open, cx);
279 }
280
281 cx.notify();
282 }
283 }
284
285 pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
286 for entry in &mut self.panel_entries {
287 if entry.panel.panel_id() == panel.entity_id() {
288 if zoomed != entry.panel.is_zoomed(cx) {
289 entry.panel.set_zoomed(zoomed, cx);
290 }
291 } else if entry.panel.is_zoomed(cx) {
292 entry.panel.set_zoomed(false, cx);
293 }
294 }
295
296 cx.notify();
297 }
298
299 pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
300 for entry in &mut self.panel_entries {
301 if entry.panel.is_zoomed(cx) {
302 entry.panel.set_zoomed(false, cx);
303 }
304 }
305 }
306
307 pub(crate) fn add_panel<T: Panel>(
308 &mut self,
309 panel: View<T>,
310 workspace: WeakView<Workspace>,
311 cx: &mut ViewContext<Self>,
312 ) {
313 let subscriptions = [
314 cx.observe(&panel, |_, _, cx| cx.notify()),
315 cx.subscribe(&panel, move |this, panel, event, cx| match event {
316 PanelEvent::ChangePosition => {
317 let new_position = panel.read(cx).position(cx);
318
319 let Ok(new_dock) = workspace.update(cx, |workspace, cx| {
320 if panel.is_zoomed(cx) {
321 workspace.zoomed_position = Some(new_position);
322 }
323 match new_position {
324 DockPosition::Left => &workspace.left_dock,
325 DockPosition::Bottom => &workspace.bottom_dock,
326 DockPosition::Right => &workspace.right_dock,
327 }
328 .clone()
329 }) else {
330 return;
331 };
332
333 let was_visible = this.is_open()
334 && this.visible_panel().map_or(false, |active_panel| {
335 active_panel.panel_id() == Entity::entity_id(&panel)
336 });
337
338 this.remove_panel(&panel, cx);
339
340 new_dock.update(cx, |new_dock, cx| {
341 new_dock.add_panel(panel.clone(), workspace.clone(), cx);
342 if was_visible {
343 new_dock.set_open(true, cx);
344 new_dock.activate_panel(new_dock.panels_len() - 1, cx);
345 }
346 });
347 }
348 PanelEvent::ZoomIn => {
349 this.set_panel_zoomed(&panel.to_any(), true, cx);
350 if !panel.focus_handle(cx).contains_focused(cx) {
351 cx.focus_view(&panel);
352 }
353 workspace
354 .update(cx, |workspace, cx| {
355 workspace.zoomed = Some(panel.downgrade().into());
356 workspace.zoomed_position = Some(panel.read(cx).position(cx));
357 })
358 .ok();
359 }
360 PanelEvent::ZoomOut => {
361 this.set_panel_zoomed(&panel.to_any(), false, cx);
362 workspace
363 .update(cx, |workspace, cx| {
364 if workspace.zoomed_position == Some(this.position) {
365 workspace.zoomed = None;
366 workspace.zoomed_position = None;
367 }
368 cx.notify();
369 })
370 .ok();
371 }
372 // todo!() we do not use this event in the production code (even in zed1), remove it
373 PanelEvent::Activate => {
374 if let Some(ix) = this
375 .panel_entries
376 .iter()
377 .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
378 {
379 this.set_open(true, cx);
380 this.activate_panel(ix, cx);
381 cx.focus_view(&panel);
382 }
383 }
384 PanelEvent::Close => {
385 if this
386 .visible_panel()
387 .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
388 {
389 this.set_open(false, cx);
390 }
391 }
392 PanelEvent::Focus => {}
393 }),
394 ];
395
396 // todo!()
397 // let dock_view_id = cx.view_id();
398 self.panel_entries.push(PanelEntry {
399 panel: Arc::new(panel),
400 // todo!()
401 // context_menu: cx.add_view(|cx| {
402 // let mut menu = ContextMenu::new(dock_view_id, cx);
403 // menu.set_position_mode(OverlayPositionMode::Local);
404 // menu
405 // }),
406 _subscriptions: subscriptions,
407 });
408 cx.notify()
409 }
410
411 pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
412 if let Some(panel_ix) = self
413 .panel_entries
414 .iter()
415 .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
416 {
417 if panel_ix == self.active_panel_index {
418 self.active_panel_index = 0;
419 self.set_open(false, cx);
420 } else if panel_ix < self.active_panel_index {
421 self.active_panel_index -= 1;
422 }
423 self.panel_entries.remove(panel_ix);
424 cx.notify();
425 }
426 }
427
428 pub fn panels_len(&self) -> usize {
429 self.panel_entries.len()
430 }
431
432 pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
433 if panel_ix != self.active_panel_index {
434 if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
435 active_panel.panel.set_active(false, cx);
436 }
437
438 self.active_panel_index = panel_ix;
439 if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
440 active_panel.panel.set_active(true, cx);
441 }
442
443 cx.notify();
444 }
445 }
446
447 pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
448 let entry = self.visible_entry()?;
449 Some(&entry.panel)
450 }
451
452 pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
453 Some(&self.panel_entries.get(self.active_panel_index)?.panel)
454 }
455
456 fn visible_entry(&self) -> Option<&PanelEntry> {
457 if self.is_open {
458 self.panel_entries.get(self.active_panel_index)
459 } else {
460 None
461 }
462 }
463
464 pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
465 let entry = self.visible_entry()?;
466 if entry.panel.is_zoomed(cx) {
467 Some(entry.panel.clone())
468 } else {
469 None
470 }
471 }
472
473 pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
474 self.panel_entries
475 .iter()
476 .find(|entry| entry.panel.panel_id() == panel.panel_id())
477 .map(|entry| entry.panel.size(cx))
478 }
479
480 pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
481 if self.is_open {
482 self.panel_entries
483 .get(self.active_panel_index)
484 .map(|entry| entry.panel.size(cx))
485 } else {
486 None
487 }
488 }
489
490 pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
491 if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
492 let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
493 entry.panel.set_size(size, cx);
494 cx.notify();
495 }
496 }
497
498 pub fn toggle_action(&self) -> Box<dyn Action> {
499 match self.position {
500 DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
501 DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
502 DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
503 }
504 }
505}
506
507impl Render for Dock {
508 type Element = Div;
509
510 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
511 if let Some(entry) = self.visible_entry() {
512 let size = entry.panel.size(cx);
513
514 let position = self.position;
515 let mut handle = div()
516 .id("resize-handle")
517 .on_drag(DraggedDock(position), |dock, cx| {
518 cx.stop_propagation();
519 cx.build_view(|_| dock.clone())
520 })
521 .on_click(cx.listener(|v, e: &ClickEvent, cx| {
522 if e.down.button == MouseButton::Left && e.down.click_count == 2 {
523 v.resize_active_panel(None, cx);
524 cx.stop_propagation();
525 }
526 }))
527 .z_index(1)
528 .block_mouse();
529
530 match self.position() {
531 DockPosition::Left => {
532 handle = handle
533 .absolute()
534 .right(px(0.))
535 .top(px(0.))
536 .h_full()
537 .w(RESIZE_HANDLE_SIZE)
538 .cursor_col_resize();
539 }
540 DockPosition::Bottom => {
541 handle = handle
542 .absolute()
543 .top(px(0.))
544 .left(px(0.))
545 .w_full()
546 .h(RESIZE_HANDLE_SIZE)
547 .cursor_row_resize();
548 }
549 DockPosition::Right => {
550 handle = handle
551 .absolute()
552 .top(px(0.))
553 .left(px(0.))
554 .h_full()
555 .w(RESIZE_HANDLE_SIZE)
556 .cursor_col_resize();
557 }
558 }
559
560 div()
561 .flex()
562 .bg(cx.theme().colors().panel_background)
563 .border_color(cx.theme().colors().border)
564 .overflow_hidden()
565 .map(|this| match self.position().axis() {
566 Axis::Horizontal => this.w(size).h_full().flex_row(),
567 Axis::Vertical => this.h(size).w_full().flex_col(),
568 })
569 .map(|this| match self.position() {
570 DockPosition::Left => this.border_r(),
571 DockPosition::Right => this.border_l(),
572 DockPosition::Bottom => this.border_t(),
573 })
574 .child(
575 div()
576 .map(|this| match self.position().axis() {
577 Axis::Horizontal => this.min_w(size).h_full(),
578 Axis::Vertical => this.min_h(size).w_full(),
579 })
580 .child(entry.panel.to_any()),
581 )
582 .child(handle)
583 } else {
584 div()
585 }
586 }
587}
588
589impl PanelButtons {
590 pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
591 cx.observe(&dock, |_, _, cx| cx.notify()).detach();
592 Self { dock }
593 }
594}
595
596impl Render for PanelButtons {
597 type Element = Div;
598
599 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
600 // todo!()
601 let dock = self.dock.read(cx);
602 let active_index = dock.active_panel_index;
603 let is_open = dock.is_open;
604 let dock_position = dock.position;
605
606 let (menu_anchor, menu_attach) = match dock.position {
607 DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
608 DockPosition::Bottom | DockPosition::Right => {
609 (AnchorCorner::BottomRight, AnchorCorner::TopRight)
610 }
611 };
612
613 let buttons = dock
614 .panel_entries
615 .iter()
616 .enumerate()
617 .filter_map(|(i, entry)| {
618 let icon = entry.panel.icon(cx)?;
619 let name = entry.panel.persistent_name();
620 let panel = entry.panel.clone();
621
622 let is_active_button = i == active_index && is_open;
623
624 let (action, tooltip) = if is_active_button {
625 let action = dock.toggle_action();
626
627 let tooltip: SharedString =
628 format!("Close {} dock", dock.position.to_label()).into();
629
630 (action, tooltip)
631 } else {
632 let action = entry.panel.toggle_action(cx);
633
634 (action, name.into())
635 };
636
637 Some(
638 right_click_menu(name)
639 .menu(move |cx| {
640 const POSITIONS: [DockPosition; 3] = [
641 DockPosition::Left,
642 DockPosition::Right,
643 DockPosition::Bottom,
644 ];
645
646 ContextMenu::build(cx, |mut menu, cx| {
647 for position in POSITIONS {
648 if position != dock_position
649 && panel.position_is_valid(position, cx)
650 {
651 let panel = panel.clone();
652 menu = menu.entry(position.to_label(), None, move |cx| {
653 panel.set_position(position, cx);
654 })
655 }
656 }
657 menu
658 })
659 })
660 .anchor(menu_anchor)
661 .attach(menu_attach)
662 .trigger(
663 IconButton::new(name, icon)
664 .icon_size(IconSize::Small)
665 .selected(is_active_button)
666 .on_click({
667 let action = action.boxed_clone();
668 move |_, cx| cx.dispatch_action(action.boxed_clone())
669 })
670 .tooltip(move |cx| {
671 Tooltip::for_action(tooltip.clone(), &*action, cx)
672 }),
673 ),
674 )
675 });
676
677 h_stack().gap_0p5().children(buttons)
678 }
679}
680
681impl StatusItemView for PanelButtons {
682 fn set_active_pane_item(
683 &mut self,
684 _active_pane_item: Option<&dyn crate::ItemHandle>,
685 _cx: &mut ViewContext<Self>,
686 ) {
687 // Nothing to do, panel buttons don't depend on the active center item
688 }
689}
690
691#[cfg(any(test, feature = "test-support"))]
692pub mod test {
693 use super::*;
694 use gpui::{actions, div, Div, ViewContext, WindowContext};
695
696 pub struct TestPanel {
697 pub position: DockPosition,
698 pub zoomed: bool,
699 pub active: bool,
700 pub focus_handle: FocusHandle,
701 pub size: Pixels,
702 }
703 actions!(test, [ToggleTestPanel]);
704
705 impl EventEmitter<PanelEvent> for TestPanel {}
706
707 impl TestPanel {
708 pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
709 Self {
710 position,
711 zoomed: false,
712 active: false,
713 focus_handle: cx.focus_handle(),
714 size: px(300.),
715 }
716 }
717 }
718
719 impl Render for TestPanel {
720 type Element = Div;
721
722 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
723 div()
724 }
725 }
726
727 impl Panel for TestPanel {
728 fn persistent_name() -> &'static str {
729 "TestPanel"
730 }
731
732 fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
733 self.position
734 }
735
736 fn position_is_valid(&self, _: super::DockPosition) -> bool {
737 true
738 }
739
740 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
741 self.position = position;
742 cx.emit(PanelEvent::ChangePosition);
743 }
744
745 fn size(&self, _: &WindowContext) -> Pixels {
746 self.size
747 }
748
749 fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
750 self.size = size.unwrap_or(px(300.));
751 }
752
753 fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
754 None
755 }
756
757 fn toggle_action(&self) -> Box<dyn Action> {
758 ToggleTestPanel.boxed_clone()
759 }
760
761 fn is_zoomed(&self, _: &WindowContext) -> bool {
762 self.zoomed
763 }
764
765 fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
766 self.zoomed = zoomed;
767 }
768
769 fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
770 self.active = active;
771 }
772 }
773
774 impl FocusableView for TestPanel {
775 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
776 self.focus_handle.clone()
777 }
778 }
779}