1use crate::{status_bar::StatusItemView, Axis, Workspace};
2use gpui::{
3 div, px, Action, AnyView, AppContext, Component, Div, Entity, EntityId, EventEmitter,
4 FocusHandle, FocusableView, ParentComponent, Render, Styled, Subscription, View, ViewContext,
5 WeakView, WindowContext,
6};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10use theme2::ActiveTheme;
11use ui::{h_stack, 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 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 id(&self) -> EntityId {
66 self.entity_id()
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.as_any().clone().downcast())
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 // todo!()
258 // pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
259 // for entry in &mut self.panel_entries {
260 // if entry.panel.as_any() == panel {
261 // if zoomed != entry.panel.is_zoomed(cx) {
262 // entry.panel.set_zoomed(zoomed, cx);
263 // }
264 // } else if entry.panel.is_zoomed(cx) {
265 // entry.panel.set_zoomed(false, cx);
266 // }
267 // }
268
269 // cx.notify();
270 // }
271
272 pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
273 for entry in &mut self.panel_entries {
274 if entry.panel.is_zoomed(cx) {
275 entry.panel.set_zoomed(false, cx);
276 }
277 }
278 }
279
280 pub(crate) fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
281 let subscriptions = [
282 cx.observe(&panel, |_, _, cx| cx.notify()),
283 cx.subscribe(&panel, |this, panel, event, cx| {
284 match event {
285 PanelEvent::ChangePosition => {
286 //todo!()
287 // see: Workspace::add_panel_with_extra_event_handler
288 }
289 PanelEvent::ZoomIn => {
290 //todo!()
291 // see: Workspace::add_panel_with_extra_event_handler
292 }
293 PanelEvent::ZoomOut => {
294 // todo!()
295 // // see: Workspace::add_panel_with_extra_event_handler
296 }
297 PanelEvent::Activate => {
298 if let Some(ix) = this
299 .panel_entries
300 .iter()
301 .position(|entry| entry.panel.id() == panel.id())
302 {
303 this.set_open(true, cx);
304 this.activate_panel(ix, cx);
305 //` todo!()
306 // cx.focus(&panel);
307 }
308 }
309 PanelEvent::Close => {
310 if this.visible_panel().map_or(false, |p| p.id() == panel.id()) {
311 this.set_open(false, cx);
312 }
313 }
314 PanelEvent::Focus => todo!(),
315 }
316 }),
317 ];
318
319 // todo!()
320 // let dock_view_id = cx.view_id();
321 self.panel_entries.push(PanelEntry {
322 panel: Arc::new(panel),
323 // todo!()
324 // context_menu: cx.add_view(|cx| {
325 // let mut menu = ContextMenu::new(dock_view_id, cx);
326 // menu.set_position_mode(OverlayPositionMode::Local);
327 // menu
328 // }),
329 _subscriptions: subscriptions,
330 });
331 cx.notify()
332 }
333
334 pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
335 if let Some(panel_ix) = self
336 .panel_entries
337 .iter()
338 .position(|entry| entry.panel.id() == panel.id())
339 {
340 if panel_ix == self.active_panel_index {
341 self.active_panel_index = 0;
342 self.set_open(false, cx);
343 } else if panel_ix < self.active_panel_index {
344 self.active_panel_index -= 1;
345 }
346 self.panel_entries.remove(panel_ix);
347 cx.notify();
348 }
349 }
350
351 pub fn panels_len(&self) -> usize {
352 self.panel_entries.len()
353 }
354
355 pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
356 if panel_ix != self.active_panel_index {
357 if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
358 active_panel.panel.set_active(false, cx);
359 }
360
361 self.active_panel_index = panel_ix;
362 if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
363 active_panel.panel.set_active(true, cx);
364 }
365
366 cx.notify();
367 }
368 }
369
370 pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
371 let entry = self.visible_entry()?;
372 Some(&entry.panel)
373 }
374
375 pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
376 Some(&self.panel_entries.get(self.active_panel_index)?.panel)
377 }
378
379 fn visible_entry(&self) -> Option<&PanelEntry> {
380 if self.is_open {
381 self.panel_entries.get(self.active_panel_index)
382 } else {
383 None
384 }
385 }
386
387 pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
388 let entry = self.visible_entry()?;
389 if entry.panel.is_zoomed(cx) {
390 Some(entry.panel.clone())
391 } else {
392 None
393 }
394 }
395
396 pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
397 self.panel_entries
398 .iter()
399 .find(|entry| entry.panel.id() == panel.id())
400 .map(|entry| entry.panel.size(cx))
401 }
402
403 pub fn active_panel_size(&self, cx: &WindowContext) -> Option<f32> {
404 if self.is_open {
405 self.panel_entries
406 .get(self.active_panel_index)
407 .map(|entry| entry.panel.size(cx))
408 } else {
409 None
410 }
411 }
412
413 pub fn resize_active_panel(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
414 if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
415 entry.panel.set_size(size, cx);
416 cx.notify();
417 }
418 }
419
420 // pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
421 // todo!()
422 // if let Some(active_entry) = self.visible_entry() {
423 // Empty::new()
424 // .into_any()
425 // .contained()
426 // .with_style(self.style(cx))
427 // .resizable::<WorkspaceBounds>(
428 // self.position.to_resize_handle_side(),
429 // active_entry.panel.size(cx),
430 // |_, _, _| {},
431 // )
432 // .into_any()
433 // } else {
434 // Empty::new().into_any()
435 // }
436 // }
437}
438
439impl Render for Dock {
440 type Element = Div<Self>;
441
442 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
443 if let Some(entry) = self.visible_entry() {
444 let size = entry.panel.size(cx);
445
446 div()
447 .border_color(cx.theme().colors().border)
448 .map(|this| match self.position().axis() {
449 Axis::Horizontal => this.w(px(size)).h_full(),
450 Axis::Vertical => this.h(px(size)).w_full(),
451 })
452 .map(|this| match self.position() {
453 DockPosition::Left => this.border_r(),
454 DockPosition::Right => this.border_l(),
455 DockPosition::Bottom => this.border_t(),
456 })
457 .child(entry.panel.to_any())
458 } else {
459 div()
460 }
461 }
462}
463
464// todo!()
465// impl View for Dock {
466// fn ui_name() -> &'static str {
467// "Dock"
468// }
469
470// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
471// if let Some(active_entry) = self.visible_entry() {
472// let style = self.style(cx);
473// ChildView::new(active_entry.panel.as_any(), cx)
474// .contained()
475// .with_style(style)
476// .resizable::<WorkspaceBounds>(
477// self.position.to_resize_handle_side(),
478// active_entry.panel.size(cx),
479// |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx),
480// )
481// .into_any()
482// } else {
483// Empty::new().into_any()
484// }
485// }
486
487// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
488// if cx.is_self_focused() {
489// if let Some(active_entry) = self.visible_entry() {
490// cx.focus(active_entry.panel.as_any());
491// } else {
492// cx.focus_parent();
493// }
494// }
495// }
496// }
497
498impl PanelButtons {
499 pub fn new(
500 dock: View<Dock>,
501 workspace: WeakView<Workspace>,
502 cx: &mut ViewContext<Self>,
503 ) -> Self {
504 cx.observe(&dock, |_, _, cx| cx.notify()).detach();
505 Self { dock, workspace }
506 }
507}
508
509// impl Render for PanelButtons {
510// type Element = ();
511
512// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
513// todo!("")
514// }
515
516// fn ui_name() -> &'static str {
517// "PanelButtons"
518// }
519
520// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
521// let theme = &settings::get::<ThemeSettings>(cx).theme;
522// let tooltip_style = theme.tooltip.clone();
523// let theme = &theme.workspace.status_bar.panel_buttons;
524// let button_style = theme.button.clone();
525// let dock = self.dock.read(cx);
526// let active_ix = dock.active_panel_index;
527// let is_open = dock.is_open;
528// let dock_position = dock.position;
529// let group_style = match dock_position {
530// DockPosition::Left => theme.group_left,
531// DockPosition::Bottom => theme.group_bottom,
532// DockPosition::Right => theme.group_right,
533// };
534// let menu_corner = match dock_position {
535// DockPosition::Left => AnchorCorner::BottomLeft,
536// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
537// };
538
539// let panels = dock
540// .panel_entries
541// .iter()
542// .map(|item| (item.panel.clone(), item.context_menu.clone()))
543// .collect::<Vec<_>>();
544// Flex::row()
545// .with_children(panels.into_iter().enumerate().filter_map(
546// |(panel_ix, (view, context_menu))| {
547// let icon_path = view.icon_path(cx)?;
548// let is_active = is_open && panel_ix == active_ix;
549// let (tooltip, tooltip_action) = if is_active {
550// (
551// format!("Close {} dock", dock_position.to_label()),
552// Some(match dock_position {
553// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
554// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
555// DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
556// }),
557// )
558// } else {
559// view.icon_tooltip(cx)
560// };
561// Some(
562// Stack::new()
563// .with_child(
564// MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
565// let style = button_style.in_state(is_active);
566
567// let style = style.style_for(state);
568// Flex::row()
569// .with_child(
570// Svg::new(icon_path)
571// .with_color(style.icon_color)
572// .constrained()
573// .with_width(style.icon_size)
574// .aligned(),
575// )
576// .with_children(if let Some(label) = view.icon_label(cx) {
577// Some(
578// Label::new(label, style.label.text.clone())
579// .contained()
580// .with_style(style.label.container)
581// .aligned(),
582// )
583// } else {
584// None
585// })
586// .constrained()
587// .with_height(style.icon_size)
588// .contained()
589// .with_style(style.container)
590// })
591// .with_cursor_style(CursorStyle::PointingHand)
592// .on_click(MouseButton::Left, {
593// let tooltip_action =
594// tooltip_action.as_ref().map(|action| action.boxed_clone());
595// move |_, this, cx| {
596// if let Some(tooltip_action) = &tooltip_action {
597// let window = cx.window();
598// let view_id = this.workspace.id();
599// let tooltip_action = tooltip_action.boxed_clone();
600// cx.spawn(|_, mut cx| async move {
601// window.dispatch_action(
602// view_id,
603// &*tooltip_action,
604// &mut cx,
605// );
606// })
607// .detach();
608// }
609// }
610// })
611// .on_click(MouseButton::Right, {
612// let view = view.clone();
613// let menu = context_menu.clone();
614// move |_, _, cx| {
615// const POSITIONS: [DockPosition; 3] = [
616// DockPosition::Left,
617// DockPosition::Right,
618// DockPosition::Bottom,
619// ];
620
621// menu.update(cx, |menu, cx| {
622// let items = POSITIONS
623// .into_iter()
624// .filter(|position| {
625// *position != dock_position
626// && view.position_is_valid(*position, cx)
627// })
628// .map(|position| {
629// let view = view.clone();
630// ContextMenuItem::handler(
631// format!("Dock {}", position.to_label()),
632// move |cx| view.set_position(position, cx),
633// )
634// })
635// .collect();
636// menu.show(Default::default(), menu_corner, items, cx);
637// })
638// }
639// })
640// .with_tooltip::<Self>(
641// panel_ix,
642// tooltip,
643// tooltip_action,
644// tooltip_style.clone(),
645// cx,
646// ),
647// )
648// .with_child(ChildView::new(&context_menu, cx)),
649// )
650// },
651// ))
652// .contained()
653// .with_style(group_style)
654// .into_any()
655// }
656// }
657
658impl Render for PanelButtons {
659 type Element = Div<Self>;
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
667 let buttons = dock
668 .panel_entries
669 .iter()
670 .enumerate()
671 .filter_map(|(i, panel)| {
672 let icon = panel.panel.icon(cx)?;
673 let name = panel.panel.persistent_name();
674 let action = panel.panel.toggle_action(cx);
675 let action2 = action.boxed_clone();
676
677 let mut button = IconButton::new(panel.panel.persistent_name(), icon)
678 .when(i == active_index, |el| el.state(InteractionState::Active))
679 .on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
680 .tooltip(move |_, cx| Tooltip::for_action(name, &*action2, cx));
681
682 Some(button)
683 });
684
685 h_stack().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, Div, ViewContext, WindowContext};
703
704 pub struct TestPanel {
705 pub position: DockPosition,
706 pub zoomed: bool,
707 pub active: bool,
708 pub has_focus: bool,
709 pub size: f32,
710 }
711 actions!(ToggleTestPanel);
712
713 impl EventEmitter<PanelEvent> for TestPanel {}
714
715 impl TestPanel {
716 pub fn new(position: DockPosition) -> Self {
717 Self {
718 position,
719 zoomed: false,
720 active: false,
721 has_focus: false,
722 size: 300.,
723 }
724 }
725 }
726
727 impl Render for TestPanel {
728 type Element = Div<Self>;
729
730 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
731 div()
732 }
733 }
734
735 impl Panel for TestPanel {
736 fn persistent_name() -> &'static str {
737 "TestPanel"
738 }
739
740 fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
741 self.position
742 }
743
744 fn position_is_valid(&self, _: super::DockPosition) -> bool {
745 true
746 }
747
748 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
749 self.position = position;
750 cx.emit(PanelEvent::ChangePosition);
751 }
752
753 fn size(&self, _: &WindowContext) -> f32 {
754 self.size
755 }
756
757 fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
758 self.size = size.unwrap_or(300.);
759 }
760
761 fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
762 None
763 }
764
765 fn toggle_action(&self) -> Box<dyn Action> {
766 ToggleTestPanel.boxed_clone()
767 }
768
769 fn is_zoomed(&self, _: &WindowContext) -> bool {
770 self.zoomed
771 }
772
773 fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
774 self.zoomed = zoomed;
775 }
776
777 fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
778 self.active = active;
779 }
780
781 fn has_focus(&self, _cx: &WindowContext) -> bool {
782 self.has_focus
783 }
784 }
785
786 impl FocusableView for TestPanel {
787 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
788 unimplemented!()
789 }
790 }
791}