doc: Fix TabPanel popup menu can't dispatch action to TabContent. (#208)

Co-authored-by: Floyd Wang <gassnake999@gmail.com>
This commit is contained in:
Jason Lee 2024-09-03 19:51:35 +08:00 committed by GitHub
parent 5e86c9cfa0
commit e318a9d8dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 51 additions and 61 deletions

View file

@ -48,8 +48,9 @@ use ui::{
dock::{Panel, PanelEvent, TabPanel}, dock::{Panel, PanelEvent, TabPanel},
h_flex, h_flex,
label::Label, label::Label,
notification::Notification,
popup_menu::PopupMenu, popup_menu::PopupMenu,
v_flex, Placement, v_flex, ContextModal, Placement,
}; };
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
@ -87,12 +88,6 @@ pub struct StoryContainer {
closeable: bool, closeable: bool,
} }
impl FocusableView for StoryContainer {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum ContainerEvent { pub enum ContainerEvent {
Close, Close,
@ -162,6 +157,13 @@ impl StoryContainer {
self.story = Some(story); self.story = Some(story);
self self
} }
fn on_action_panel_info(&mut self, _: &PanelInfo, cx: &mut ViewContext<Self>) {
struct Info;
let note = Notification::new(format!("You have clicked panel info on: {}", self.name))
.id::<Info>();
cx.push_notification(note);
}
} }
impl Panel for StoryContainer { impl Panel for StoryContainer {
@ -174,17 +176,25 @@ impl Panel for StoryContainer {
} }
fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu { fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu {
menu.menu("Panel Info", Box::new(PanelInfo)) menu.track_focus(&self.focus_handle)
.menu("Info", Box::new(PanelInfo))
} }
} }
impl EventEmitter<PanelEvent> for StoryContainer {} impl EventEmitter<PanelEvent> for StoryContainer {}
impl FocusableView for StoryContainer {
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for StoryContainer { impl Render for StoryContainer {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex() v_flex()
.id("story-container") .id("story-container")
.size_full() .size_full()
.overflow_scroll() .overflow_scroll()
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_action_panel_info))
.child( .child(
div() div()
.flex() .flex()

View file

@ -51,20 +51,6 @@ impl DockArea {
pub fn root(&self) -> View<StackPanel> { pub fn root(&self) -> View<StackPanel> {
self.root.clone() self.root.clone()
} }
/// Return the index of the panel.
pub fn index_of_panel<P: Panel>(
&self,
panel: View<P>,
cx: &mut ViewContext<Self>,
) -> Option<usize> {
self.root.read(cx).index_of_panel(panel)
}
/// Return the existing panel by type.
pub fn panel<P: Panel>(&self, cx: &mut ViewContext<Self>) -> Option<View<P>> {
self.root.read(cx).panel::<P>(cx)
}
} }
impl Render for DockArea { impl Render for DockArea {

View file

@ -9,9 +9,9 @@ use crate::{
use super::{DockArea, Panel, PanelEvent, PanelView, TabPanel}; use super::{DockArea, Panel, PanelEvent, PanelView, TabPanel};
use gpui::{ use gpui::{
prelude::FluentBuilder as _, Axis, DismissEvent, Entity, EventEmitter, FocusHandle, prelude::FluentBuilder as _, AppContext, Axis, DismissEvent, Entity, EventEmitter, FocusHandle,
FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, View, ViewContext,
VisualContext, WeakView, WindowContext, VisualContext, WeakView,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
@ -62,24 +62,6 @@ impl StackPanel {
.position(|p| p.view().entity_id() == entity_id) .position(|p| p.view().entity_id() == entity_id)
} }
/// Return the existing panel view by type.
pub fn panel<P>(&self, cx: &WindowContext) -> Option<View<P>>
where
P: Panel,
{
self.panels.iter().find_map(|p| {
if let Ok(p) = p.view().downcast::<P>() {
Some(p)
} else {
if let Ok(tab) = p.view().downcast::<TabPanel>() {
tab.read(cx).panel(cx)
} else {
None
}
}
})
}
/// Add a panel at the end of the stack. /// Add a panel at the end of the stack.
pub fn add_panel<P>( pub fn add_panel<P>(
&mut self, &mut self,
@ -287,7 +269,7 @@ impl StackPanel {
} }
impl FocusableView for StackPanel { impl FocusableView for StackPanel {
fn focus_handle(&self, _cx: &gpui::AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }

View file

@ -103,21 +103,6 @@ impl TabPanel {
cx.notify(); cx.notify();
} }
/// Return the existing panel view by type.
pub fn panel<P: Panel>(&self, cx: &WindowContext) -> Option<View<P>> {
self.panels.iter().find_map(|p| {
if let Ok(p) = p.view().downcast::<P>() {
Some(p)
} else {
if let Ok(stack) = p.view().downcast::<StackPanel>() {
stack.read(cx).panel::<P>(cx)
} else {
None
}
}
})
}
/// Add a panel to the end of the tabs /// Add a panel to the end of the tabs
pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) { pub fn add_panel(&mut self, panel: Arc<dyn PanelView>, cx: &mut ViewContext<Self>) {
if self if self
@ -553,8 +538,7 @@ impl Panel for TabPanel {
} }
} }
impl FocusableView for TabPanel { impl FocusableView for TabPanel {
fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
// FIXME: Delegate to the active panel
self.focus_handle.clone() self.focus_handle.clone()
} }
} }

View file

@ -75,6 +75,8 @@ pub struct PopupMenu {
max_width: Pixels, max_width: Pixels,
hovered_menu_ix: Option<usize>, hovered_menu_ix: Option<usize>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
action_focus_handle: Option<FocusHandle>,
_subscriptions: [gpui::Subscription; 1], _subscriptions: [gpui::Subscription; 1],
} }
@ -91,6 +93,7 @@ impl PopupMenu {
let menu = Self { let menu = Self {
focus_handle, focus_handle,
action_focus_handle: None,
parent_menu: None, parent_menu: None,
menu_items: Vec::new(), menu_items: Vec::new(),
selected_index: None, selected_index: None,
@ -106,6 +109,12 @@ impl PopupMenu {
}) })
} }
/// Bind the focus handle of the menu, when clicked, it will focus back to this handle and then dispath the action
pub fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
self.action_focus_handle = Some(focus_handle.clone());
self
}
/// Set min width of the popup menu, default is 120px /// Set min width of the popup menu, default is 120px
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self { pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
self.min_width = width.into(); self.min_width = width.into();
@ -190,12 +199,31 @@ impl PopupMenu {
self.has_icon = true; self.has_icon = true;
} }
let action_focus_handle = self.action_focus_handle.clone();
self.menu_items.push(PopupMenuItem::Item { self.menu_items.push(PopupMenuItem::Item {
icon, icon,
label: label.into(), label: label.into(),
action: Some(action.boxed_clone()), action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| { handler: Rc::new(move |cx| {
cx.activate_window(); cx.activate_window();
// Focus back to the user expected focus handle
// Then the actions listened on that focus handle can be received
//
// For example:
//
// TabPanel
// |- PopupMenu
// |- PanelContent (actions are listened here)
//
// The `PopupMenu` and `PanelContent` are at the same level in the TabPanel
// If the actions are listened on the `PanelContent`,
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
if let Some(handle) = action_focus_handle.as_ref() {
cx.focus(&handle);
}
cx.dispatch_action(action.boxed_clone()); cx.dispatch_action(action.boxed_clone());
}), }),
}); });