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