dock: Limit drag move or close when tab panel or parent only have 1 panel. (#418)
This commit is contained in:
parent
b29f4fb0e6
commit
079cc35c48
3 changed files with 89 additions and 28 deletions
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue