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