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