dock: Limit drag move or close when tab panel or parent only have 1 panel. (#418)

This commit is contained in:
Jason Lee 2024-11-14 18:33:49 +08:00 committed by GitHub
parent b29f4fb0e6
commit 079cc35c48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 28 deletions

View file

@ -233,12 +233,18 @@ impl StoryWorkspace {
cx, cx,
); );
let bottom_panels = DockItem::tabs( let bottom_panels = DockItem::split_with_sizes(
vec![ Axis::Vertical,
Arc::new(StoryContainer::panel::<TooltipStory>(cx)), vec![DockItem::tabs(
Arc::new(StoryContainer::panel::<IconStory>(cx)), vec![
], Arc::new(StoryContainer::panel::<TooltipStory>(cx)),
None, Arc::new(StoryContainer::panel::<IconStory>(cx)),
],
None,
&dock_area,
cx,
)],
vec![None],
&dock_area, &dock_area,
cx, cx,
); );

View file

@ -80,6 +80,21 @@ impl StackPanel {
self.parent.is_none() self.parent.is_none()
} }
/// Return true if self or parent only have last panel.
pub(super) fn is_last_panel(&self, cx: &AppContext) -> bool {
if self.is_root() {
return self.panels.len() == 1;
}
if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.read(cx).is_last_panel(cx);
}
}
return false;
}
pub(super) fn panels_len(&self) -> usize { pub(super) fn panels_len(&self) -> usize {
self.panels.len() self.panels.len()
} }

View file

@ -22,6 +22,14 @@ use super::{
ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel, ToggleZoom, ClosePanel, DockArea, DockItemState, Panel, PanelEvent, PanelView, StackPanel, ToggleZoom,
}; };
#[derive(Clone, Copy)]
struct TabState {
closeable: bool,
zoomable: bool,
draggable: bool,
droppable: bool,
}
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct DragPanel { pub(crate) struct DragPanel {
pub(crate) panel: Arc<dyn PanelView>, pub(crate) panel: Arc<dyn PanelView>,
@ -267,7 +275,7 @@ impl TabPanel {
fn is_locked(&self, cx: &AppContext) -> bool { fn is_locked(&self, cx: &AppContext) -> bool {
let Some(dock_area) = self.dock_area.upgrade() else { let Some(dock_area) = self.dock_area.upgrade() else {
return false; return true;
}; };
if dock_area.read(cx).is_locked() { if dock_area.read(cx).is_locked() {
@ -281,16 +289,40 @@ impl TabPanel {
self.stack_panel.is_none() self.stack_panel.is_none()
} }
/// Return true if self or parent only have last panel.
fn is_last_panel(&self, cx: &AppContext) -> bool {
if let Some(parent) = &self.stack_panel {
if let Some(stack_panel) = parent.upgrade() {
if !stack_panel.read(cx).is_last_panel(cx) {
return false;
}
}
}
self.panels.len() <= 1
}
/// Return true if the tab panel is draggable.
///
/// E.g. if the parent and self only have one panel, it is not draggable.
fn draggable(&self, cx: &AppContext) -> bool {
!self.is_locked(cx) && !self.is_last_panel(cx)
}
/// Return true if the tab panel is droppable.
///
/// E.g. if the tab panel is locked, it is not droppable.
fn droppable(&self, cx: &AppContext) -> bool {
!self.is_locked(cx)
}
pub(super) fn set_collapsed(&mut self, collapsed: bool, cx: &mut ViewContext<Self>) { pub(super) fn set_collapsed(&mut self, collapsed: bool, cx: &mut ViewContext<Self>) {
self.is_collapsed = collapsed; self.is_collapsed = collapsed;
cx.notify(); cx.notify();
} }
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_toolbar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
let closeable = self.closeable(cx); let is_zoomed = self.is_zoomed && state.zoomable;
let zoomable = self.zoomable(cx);
let is_zoomed = self.is_zoomed && zoomable;
let view = cx.view().clone(); let view = cx.view().clone();
let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx); let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx);
@ -324,7 +356,7 @@ impl TabPanel {
.ghost() .ghost()
.popup_menu(move |this, cx| { .popup_menu(move |this, cx| {
build_popup_menu(this, cx) build_popup_menu(this, cx)
.when(zoomable, |this| { .when(state.zoomable, |this| {
let name = if is_zoomed { let name = if is_zoomed {
t!("Dock.Zoom Out") t!("Dock.Zoom Out")
} else { } else {
@ -332,7 +364,7 @@ impl TabPanel {
}; };
this.separator().menu(name, Box::new(ToggleZoom)) this.separator().menu(name, Box::new(ToggleZoom))
}) })
.when(closeable, |this| { .when(state.closeable, |this| {
this.separator() this.separator()
.menu(t!("Dock.Close"), Box::new(ClosePanel)) .menu(t!("Dock.Close"), Box::new(ClosePanel))
}) })
@ -341,9 +373,8 @@ impl TabPanel {
) )
} }
fn render_title_bar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_title_bar(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
let view = cx.view().clone(); let view = cx.view().clone();
let is_locked = self.is_locked(cx);
if self.panels.len() == 1 { if self.panels.len() == 1 {
let panel = self.panels.get(0).unwrap(); let panel = self.panels.get(0).unwrap();
@ -368,7 +399,7 @@ impl TabPanel {
.text_ellipsis() .text_ellipsis()
.whitespace_nowrap() .whitespace_nowrap()
.child(panel.title(cx)) .child(panel.title(cx))
.when(!is_locked, |this| { .when(state.draggable, |this| {
this.on_drag( this.on_drag(
DragPanel { DragPanel {
panel: panel.clone(), panel: panel.clone(),
@ -386,7 +417,7 @@ impl TabPanel {
.flex_shrink_0() .flex_shrink_0()
.ml_1() .ml_1()
.gap_1() .gap_1()
.child(self.render_toolbar(cx)), .child(self.render_toolbar(state, cx)),
) )
.into_any_element(); .into_any_element();
} }
@ -409,12 +440,14 @@ impl TabPanel {
.on_click(cx.listener(move |view, _, cx| { .on_click(cx.listener(move |view, _, cx| {
view.set_active_ix(ix, cx); view.set_active_ix(ix, cx);
})) }))
.when(!is_locked, |this| { .when(state.draggable, |this| {
this.on_drag(DragPanel::new(panel.clone(), view.clone()), |drag, cx| { this.on_drag(DragPanel::new(panel.clone(), view.clone()), |drag, cx| {
cx.stop_propagation(); cx.stop_propagation();
cx.new_view(|_| drag.clone()) cx.new_view(|_| drag.clone())
}) })
.drag_over::<DragPanel>(|this, _, cx| { })
.when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, cx| {
this.rounded_l_none() this.rounded_l_none()
.border_l_2() .border_l_2()
.border_r_0() .border_r_0()
@ -435,7 +468,7 @@ impl TabPanel {
.h_full() .h_full()
.flex_grow() .flex_grow()
.min_w_16() .min_w_16()
.when(!is_locked, |this| { .when(state.droppable, |this| {
this.drag_over::<DragPanel>(|this, _, cx| this.bg(cx.theme().drop_target)) this.drag_over::<DragPanel>(|this, _, cx| this.bg(cx.theme().drop_target))
.on_drop(cx.listener(move |this, drag: &DragPanel, cx| { .on_drop(cx.listener(move |this, drag: &DragPanel, cx| {
this.will_split_placement = None; this.will_split_placement = None;
@ -462,14 +495,12 @@ impl TabPanel {
.bg(cx.theme().tab_bar) .bg(cx.theme().tab_bar)
.px_2() .px_2()
.gap_1() .gap_1()
.child(self.render_toolbar(cx)), .child(self.render_toolbar(state, cx)),
) )
.into_any_element() .into_any_element()
} }
fn render_active_panel(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_active_panel(&self, state: TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_locked = self.is_locked(cx);
self.active_panel() self.active_panel()
.map(|panel| { .map(|panel| {
div() div()
@ -479,7 +510,7 @@ impl TabPanel {
.overflow_x_hidden() .overflow_x_hidden()
.flex_1() .flex_1()
.child(panel.view()) .child(panel.view())
.when(!is_locked, |this| { .when(state.droppable, |this| {
this.on_drag_move(cx.listener(Self::on_panel_drag_move)) this.on_drag_move(cx.listener(Self::on_panel_drag_move))
.child( .child(
div() div()
@ -718,6 +749,15 @@ impl EventEmitter<PanelEvent> for TabPanel {}
impl Render for TabPanel { impl Render for TabPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl gpui::IntoElement {
let focus_handle = self.focus_handle(cx); let focus_handle = self.focus_handle(cx);
let mut state = TabState {
closeable: self.closeable(cx),
draggable: self.draggable(cx),
droppable: self.droppable(cx),
zoomable: self.zoomable(cx),
};
if !state.draggable {
state.closeable = false;
}
v_flex() v_flex()
.id("tab-panel") .id("tab-panel")
@ -727,7 +767,7 @@ impl Render for TabPanel {
.size_full() .size_full()
.overflow_hidden() .overflow_hidden()
.bg(cx.theme().background) .bg(cx.theme().background)
.child(self.render_title_bar(cx)) .child(self.render_title_bar(state, cx))
.child(self.render_active_panel(cx)) .child(self.render_active_panel(state, cx))
} }
} }