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