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