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