1use crate::{status_bar::StatusItemView, Axis, Workspace};
2use gpui::{
3 div, px, Action, AnchorCorner, AnyView, AppContext, Component, Div, Entity, EntityId,
4 EventEmitter, FocusHandle, FocusableView, ParentComponent, 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 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.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 // 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 toggle_action(&self) -> Box<dyn Action> {
421 match self.position {
422 DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
423 DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
424 DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
425 }
426 }
427}
428
429impl Render for Dock {
430 type Element = Div<Self>;
431
432 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
433 if let Some(entry) = self.visible_entry() {
434 let size = entry.panel.size(cx);
435
436 div()
437 .border_color(cx.theme().colors().border)
438 .map(|this| match self.position().axis() {
439 Axis::Horizontal => this.w(px(size)).h_full(),
440 Axis::Vertical => this.h(px(size)).w_full(),
441 })
442 .map(|this| match self.position() {
443 DockPosition::Left => this.border_r(),
444 DockPosition::Right => this.border_l(),
445 DockPosition::Bottom => this.border_t(),
446 })
447 .child(entry.panel.to_any())
448 } else {
449 div()
450 }
451 }
452}
453
454impl PanelButtons {
455 pub fn new(
456 dock: View<Dock>,
457 workspace: WeakView<Workspace>,
458 cx: &mut ViewContext<Self>,
459 ) -> Self {
460 cx.observe(&dock, |_, _, cx| cx.notify()).detach();
461 Self { dock, workspace }
462 }
463}
464
465// impl Render for PanelButtons {
466// type Element = ();
467
468// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
469// todo!("")
470// }
471
472// fn ui_name() -> &'static str {
473// "PanelButtons"
474// }
475
476// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
477// let theme = &settings::get::<ThemeSettings>(cx).theme;
478// let tooltip_style = theme.tooltip.clone();
479// let theme = &theme.workspace.status_bar.panel_buttons;
480// let button_style = theme.button.clone();
481// let dock = self.dock.read(cx);
482// let active_ix = dock.active_panel_index;
483// let is_open = dock.is_open;
484// let dock_position = dock.position;
485// let group_style = match dock_position {
486// DockPosition::Left => theme.group_left,
487// DockPosition::Bottom => theme.group_bottom,
488// DockPosition::Right => theme.group_right,
489// };
490// let menu_corner = match dock_position {
491// DockPosition::Left => AnchorCorner::BottomLeft,
492// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
493// };
494
495// let panels = dock
496// .panel_entries
497// .iter()
498// .map(|item| (item.panel.clone(), item.context_menu.clone()))
499// .collect::<Vec<_>>();
500// Flex::row()
501// .with_children(panels.into_iter().enumerate().filter_map(
502// |(panel_ix, (view, context_menu))| {
503// let icon_path = view.icon_path(cx)?;
504// let is_active = is_open && panel_ix == active_ix;
505// let (tooltip, tooltip_action) = if is_active {
506// (
507// format!("Close {} dock", dock_position.to_label()),
508// Some(match dock_position {
509// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
510// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
511// DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
512// }),
513// )
514// } else {
515// view.icon_tooltip(cx)
516// };
517// Some(
518// Stack::new()
519// .with_child(
520// MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
521// let style = button_style.in_state(is_active);
522
523// let style = style.style_for(state);
524// Flex::row()
525// .with_child(
526// Svg::new(icon_path)
527// .with_color(style.icon_color)
528// .constrained()
529// .with_width(style.icon_size)
530// .aligned(),
531// )
532// .with_children(if let Some(label) = view.icon_label(cx) {
533// Some(
534// Label::new(label, style.label.text.clone())
535// .contained()
536// .with_style(style.label.container)
537// .aligned(),
538// )
539// } else {
540// None
541// })
542// .constrained()
543// .with_height(style.icon_size)
544// .contained()
545// .with_style(style.container)
546// })
547// .with_cursor_style(CursorStyle::PointingHand)
548// .on_click(MouseButton::Left, {
549// let tooltip_action =
550// tooltip_action.as_ref().map(|action| action.boxed_clone());
551// move |_, this, cx| {
552// if let Some(tooltip_action) = &tooltip_action {
553// let window = cx.window();
554// let view_id = this.workspace.id();
555// let tooltip_action = tooltip_action.boxed_clone();
556// cx.spawn(|_, mut cx| async move {
557// window.dispatch_action(
558// view_id,
559// &*tooltip_action,
560// &mut cx,
561// );
562// })
563// .detach();
564// }
565// }
566// })
567// .on_click(MouseButton::Right, {
568// let view = view.clone();
569// let menu = context_menu.clone();
570// move |_, _, cx| {
571// const POSITIONS: [DockPosition; 3] = [
572// DockPosition::Left,
573// DockPosition::Right,
574// DockPosition::Bottom,
575// ];
576
577// menu.update(cx, |menu, cx| {
578// let items = POSITIONS
579// .into_iter()
580// .filter(|position| {
581// *position != dock_position
582// && view.position_is_valid(*position, cx)
583// })
584// .map(|position| {
585// let view = view.clone();
586// ContextMenuItem::handler(
587// format!("Dock {}", position.to_label()),
588// move |cx| view.set_position(position, cx),
589// )
590// })
591// .collect();
592// menu.show(Default::default(), menu_corner, items, cx);
593// })
594// }
595// })
596// .with_tooltip::<Self>(
597// panel_ix,
598// tooltip,
599// tooltip_action,
600// tooltip_style.clone(),
601// cx,
602// ),
603// )
604// .with_child(ChildView::new(&context_menu, cx)),
605// )
606// },
607// ))
608// .contained()
609// .with_style(group_style)
610// .into_any()
611// }
612// }
613
614// here be kittens
615impl Render for PanelButtons {
616 type Element = Div<Self>;
617
618 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
619 // todo!()
620 let dock = self.dock.read(cx);
621 let active_index = dock.active_panel_index;
622 let is_open = dock.is_open;
623
624 let (menu_anchor, menu_attach) = match dock.position {
625 DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
626 DockPosition::Bottom | DockPosition::Right => {
627 (AnchorCorner::BottomRight, AnchorCorner::TopRight)
628 }
629 };
630
631 let buttons = dock
632 .panel_entries
633 .iter()
634 .enumerate()
635 .filter_map(|(i, panel)| {
636 let icon = panel.panel.icon(cx)?;
637 let name = panel.panel.persistent_name();
638
639 let mut button: IconButton<Self> = if i == active_index && is_open {
640 let action = dock.toggle_action();
641 let tooltip: SharedString =
642 format!("Close {} dock", dock.position.to_label()).into();
643 IconButton::new(name, icon)
644 .state(InteractionState::Active)
645 .action(action.boxed_clone())
646 .tooltip(move |_, cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
647 } else {
648 let action = panel.panel.toggle_action(cx);
649
650 IconButton::new(name, icon)
651 .action(action.boxed_clone())
652 .tooltip(move |_, cx| Tooltip::for_action(name, &*action, cx))
653 };
654
655 Some(
656 menu_handle()
657 .id(name)
658 .menu(move |_, cx| {
659 cx.build_view(|cx| ContextMenu::new(cx).header("SECTION"))
660 })
661 .anchor(menu_anchor)
662 .attach(menu_attach)
663 .child(|is_open| button.selected(is_open)),
664 )
665 });
666
667 h_stack().gap_0p5().children(buttons)
668 }
669}
670
671impl StatusItemView for PanelButtons {
672 fn set_active_pane_item(
673 &mut self,
674 _active_pane_item: Option<&dyn crate::ItemHandle>,
675 _cx: &mut ViewContext<Self>,
676 ) {
677 // Nothing to do, panel buttons don't depend on the active center item
678 }
679}
680
681#[cfg(any(test, feature = "test-support"))]
682pub mod test {
683 use super::*;
684 use gpui::{actions, div, Div, ViewContext, WindowContext};
685
686 pub struct TestPanel {
687 pub position: DockPosition,
688 pub zoomed: bool,
689 pub active: bool,
690 pub has_focus: bool,
691 pub size: f32,
692 }
693 actions!(ToggleTestPanel);
694
695 impl EventEmitter<PanelEvent> for TestPanel {}
696
697 impl TestPanel {
698 pub fn new(position: DockPosition) -> Self {
699 Self {
700 position,
701 zoomed: false,
702 active: false,
703 has_focus: false,
704 size: 300.,
705 }
706 }
707 }
708
709 impl Render for TestPanel {
710 type Element = Div<Self>;
711
712 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
713 div()
714 }
715 }
716
717 impl Panel for TestPanel {
718 fn persistent_name() -> &'static str {
719 "TestPanel"
720 }
721
722 fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
723 self.position
724 }
725
726 fn position_is_valid(&self, _: super::DockPosition) -> bool {
727 true
728 }
729
730 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
731 self.position = position;
732 cx.emit(PanelEvent::ChangePosition);
733 }
734
735 fn size(&self, _: &WindowContext) -> f32 {
736 self.size
737 }
738
739 fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
740 self.size = size.unwrap_or(300.);
741 }
742
743 fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
744 None
745 }
746
747 fn toggle_action(&self) -> Box<dyn Action> {
748 ToggleTestPanel.boxed_clone()
749 }
750
751 fn is_zoomed(&self, _: &WindowContext) -> bool {
752 self.zoomed
753 }
754
755 fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
756 self.zoomed = zoomed;
757 }
758
759 fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
760 self.active = active;
761 }
762
763 fn has_focus(&self, _cx: &WindowContext) -> bool {
764 self.has_focus
765 }
766 }
767
768 impl FocusableView for TestPanel {
769 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
770 unimplemented!()
771 }
772 }
773}