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