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