#[allow(clippy::module_inception)]
mod dock;
mod invalid_panel;
mod panel;
mod stack_panel;
mod state;
mod tab_panel;
mod tiles;
use std::{collections::HashSet, ops::Deref, sync::Arc};
use anyhow::Result;
use dock::ResizePanel;
pub use dock::*;
use gpui::{
AnyElement, AnyView, App, AppContext, Axis, Bounds, Context, DragMoveEvent, Edges, Entity,
EntityId, EventEmitter, InteractiveElement as _, IntoElement, MouseButton, ParentElement as _,
Pixels, Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div,
prelude::FluentBuilder, px,
};
pub use panel::*;
pub use stack_panel::*;
pub use state::*;
pub use tab_panel::*;
pub use tiles::{AnyDrag, DragDrop, DragMoving, DragResizing, TileItem, Tiles};
use super::resizable::resize_handle;
use crate::{ActiveTheme, DockPlacement, ElementExt, Placement, TabBarDirection};
pub(crate) fn init(cx: &mut App) {
PanelRegistry::init(cx);
}
actions!(dock, [ToggleZoom, ClosePanel]);
pub enum DockEvent {
LayoutChanged,
DragDrop(AnyDrag),
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct SplitPreview {
bounds: Bounds<Pixels>,
placement: Placement,
}
pub struct DockArea {
id: SharedString,
version: Option<usize>,
pub(crate) bounds: Bounds<Pixels>,
center: DockItem,
center_enabled: bool,
left_dock: Entity<Dock>,
bottom_dock: Entity<Dock>,
right_dock: Entity<Dock>,
toggle_button_panels: Edges<Option<EntityId>>,
toggle_button_visible: bool,
zoom_view: Option<AnyView>,
locked: bool,
pub(crate) panel_style: PanelStyle,
pub(crate) tab_bar_direction: TabBarDirection,
pub(crate) center_placeholder: Option<AnyView>,
_subscriptions: Vec<Subscription>,
subscribed_panel_ids: HashSet<EntityId>,
subscribed_tile_drop_ids: HashSet<EntityId>,
split_preview: Option<SplitPreview>,
}
#[derive(Clone)]
pub enum DockItem {
Split {
axis: Axis,
size: Option<Pixels>,
items: Vec<DockItem>,
sizes: Vec<Option<Pixels>>,
view: Entity<StackPanel>,
},
Tabs {
size: Option<Pixels>,
items: Vec<Arc<dyn PanelView>>,
active_ix: usize,
view: Entity<TabPanel>,
},
Panel {
size: Option<Pixels>,
view: Arc<dyn PanelView>,
},
Tiles {
size: Option<Pixels>,
items: Vec<TileItem>,
view: Entity<Tiles>,
},
}
impl std::fmt::Debug for DockItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DockItem::Split {
axis, items, sizes, ..
} => f
.debug_struct("Split")
.field("axis", axis)
.field("items", &items.len())
.field("sizes", sizes)
.finish(),
DockItem::Tabs {
items, active_ix, ..
} => f
.debug_struct("Tabs")
.field("items", &items.len())
.field("active_ix", active_ix)
.finish(),
DockItem::Panel { .. } => f.debug_struct("Panel").finish(),
DockItem::Tiles { .. } => f.debug_struct("Tiles").finish(),
}
}
}
impl DockItem {
pub fn has_real_panels(&self, cx: &App) -> bool {
match self {
Self::Tabs { view, .. } => !view.read(cx).panels.is_empty(),
Self::Split { items, .. } => items.iter().any(|item| item.has_real_panels(cx)),
Self::Panel { .. } => true,
Self::Tiles { view, .. } => !view.read(cx).panels().is_empty(),
}
}
fn get_size(&self) -> Option<Pixels> {
match self {
Self::Split { size, .. } => *size,
Self::Tabs { size, .. } => *size,
Self::Panel { size, .. } => *size,
Self::Tiles { size, .. } => *size,
}
}
pub fn size(mut self, new_size: impl Into<Pixels>) -> Self {
let new_size: Option<Pixels> = Some(new_size.into());
match self {
Self::Split { ref mut size, .. } => *size = new_size,
Self::Tabs { ref mut size, .. } => *size = new_size,
Self::Tiles { ref mut size, .. } => *size = new_size,
Self::Panel { ref mut size, .. } => *size = new_size,
}
self
}
pub fn active_index(mut self, new_active_ix: usize, cx: &mut App) -> Self {
debug_assert!(
matches!(self, Self::Tabs { .. }),
"active_ix can only be set for DockItem::Tabs"
);
if let Self::Tabs {
ref mut active_ix,
ref mut view,
..
} = self
{
*active_ix = new_active_ix;
view.update(cx, |tab_panel, _| {
tab_panel.active_ix = new_active_ix;
});
}
self
}
pub fn split(
axis: Axis, items: Vec<DockItem>, dock_area: &WeakEntity<DockArea>, window: &mut Window,
cx: &mut App,
) -> Self {
let sizes = items.iter().map(|item| item.get_size()).collect();
Self::split_with_sizes(axis, items, sizes, dock_area, window, cx)
}
pub fn v_split(
items: Vec<DockItem>, dock_area: &WeakEntity<DockArea>, window: &mut Window, cx: &mut App,
) -> Self {
Self::split(Axis::Vertical, items, dock_area, window, cx)
}
pub fn h_split(
items: Vec<DockItem>, dock_area: &WeakEntity<DockArea>, window: &mut Window, cx: &mut App,
) -> Self {
Self::split(Axis::Horizontal, items, dock_area, window, cx)
}
pub fn split_with_sizes(
axis: Axis, items: Vec<DockItem>, sizes: Vec<Option<Pixels>>, dock_area: &WeakEntity<DockArea>,
window: &mut Window, cx: &mut App,
) -> Self {
let mut items = items;
let stack_panel = cx.new(|cx| {
let mut stack_panel = StackPanel::new(axis, window, cx);
stack_panel.set_dock_area(dock_area.clone());
for (i, item) in items.iter_mut().enumerate() {
let view = item.view();
let size = sizes.get(i).copied().flatten();
stack_panel.add_panel(view.clone(), size, dock_area.clone(), window, cx)
}
stack_panel
});
window.defer(cx, {
let stack_panel = stack_panel.clone();
let dock_area = dock_area.clone();
move |window, cx| {
_ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&stack_panel, window, cx);
});
}
});
Self::Split {
axis,
size: None,
items,
sizes,
view: stack_panel,
}
}
pub fn panel(panel: Arc<dyn PanelView>) -> Self {
Self::Panel {
size: None,
view: panel,
}
}
pub fn tiles(
items: Vec<DockItem>, metas: Vec<impl Into<TileMeta> + Copy>, dock_area: &WeakEntity<DockArea>,
window: &mut Window, cx: &mut App,
) -> Self {
assert!(items.len() == metas.len());
let tile_panel = cx.new(|cx| {
let mut tiles = Tiles::new(window, cx);
for (ix, item) in items.clone().into_iter().enumerate() {
match item {
DockItem::Tabs { view, .. } => {
let meta: TileMeta = metas[ix].into();
let tile_item = TileItem::new(Arc::new(view), meta.bounds).z_index(meta.z_index);
tiles.add_item(tile_item, dock_area, window, cx);
}
DockItem::Panel { view, .. } => {
let meta: TileMeta = metas[ix].into();
let tile_item = TileItem::new(view.clone(), meta.bounds).z_index(meta.z_index);
tiles.add_item(tile_item, dock_area, window, cx);
}
_ => {
}
}
}
tiles
});
window.defer(cx, {
let tile_panel = tile_panel.clone();
let dock_area = dock_area.clone();
move |window, cx| {
_ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&tile_panel, window, cx);
this.subscribe_tiles_item_drop(&tile_panel, window, cx);
});
}
});
Self::Tiles {
size: None,
items: tile_panel.read(cx).panels.clone(),
view: tile_panel,
}
}
pub fn tabs(
items: Vec<Arc<dyn PanelView>>, dock_area: &WeakEntity<DockArea>, window: &mut Window,
cx: &mut App,
) -> Self {
let mut new_items: Vec<Arc<dyn PanelView>> = vec![];
for item in items.into_iter() {
new_items.push(item)
}
Self::new_tabs(new_items, None, dock_area, window, cx)
}
pub fn tab<P: Panel>(
item: Entity<P>, dock_area: &WeakEntity<DockArea>, window: &mut Window, cx: &mut App,
) -> Self {
Self::new_tabs(vec![Arc::new(item.clone())], None, dock_area, window, cx)
}
fn new_tabs(
items: Vec<Arc<dyn PanelView>>, active_ix: Option<usize>, dock_area: &WeakEntity<DockArea>,
window: &mut Window, cx: &mut App,
) -> Self {
let active_ix = active_ix.unwrap_or(0);
let tab_panel = cx.new(|cx| {
let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
for item in items.iter() {
tab_panel.add_panel(item.clone(), window, cx)
}
tab_panel.active_ix = active_ix;
tab_panel
});
Self::Tabs {
size: None,
items,
active_ix,
view: tab_panel,
}
}
pub fn view(&self) -> Arc<dyn PanelView> {
match self {
Self::Split { view, .. } => Arc::new(view.clone()),
Self::Tabs { view, .. } => Arc::new(view.clone()),
Self::Tiles { view, .. } => Arc::new(view.clone()),
Self::Panel { view, .. } => view.clone(),
}
}
pub fn find_panel(&self, panel: Arc<dyn PanelView>) -> Option<Arc<dyn PanelView>> {
match self {
Self::Split { items, .. } => items.iter().find_map(|item| item.find_panel(panel.clone())),
Self::Tabs { items, .. } => items.iter().find(|item| *item == &panel).cloned(),
Self::Panel { view, .. } => Some(view.clone()),
Self::Tiles { items, .. } => items.iter().find_map(|item| {
if item.panel == panel.clone() {
Some(item.panel.clone())
} else {
None
}
}),
}
}
pub fn add_panel(
&mut self, panel: Arc<dyn PanelView>, dock_area: &WeakEntity<DockArea>,
bounds: Option<Bounds<Pixels>>, window: &mut Window, cx: &mut App,
) {
match self {
Self::Tabs { view, items, .. } => {
items.push(panel.clone());
view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel, window, cx);
});
}
Self::Split { view, items, .. } => {
for item in items.iter_mut() {
if let DockItem::Tabs { view, .. } = item {
view.update(cx, |tab_panel, cx| {
tab_panel.add_panel(panel.clone(), window, cx);
});
return;
}
}
let new_item = Self::tabs(vec![panel.clone()], dock_area, window, cx);
items.push(new_item.clone());
view.update(cx, |stack_panel, cx| {
stack_panel.add_panel(new_item.view(), None, dock_area.clone(), window, cx);
});
}
Self::Tiles { view, items, .. } => {
let tile_item = TileItem::new(
Arc::new(cx.new(|cx| {
let mut tab_panel = TabPanel::new(None, dock_area.clone(), window, cx);
tab_panel.add_panel(panel.clone(), window, cx);
tab_panel
})),
bounds.unwrap_or_else(|| TileMeta::default().bounds),
);
items.push(tile_item.clone());
view.update(cx, |tiles, cx| {
tiles.add_item(tile_item, dock_area, window, cx);
});
}
Self::Panel { .. } => {}
}
}
pub fn remove_panel(&self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut App) {
match self {
DockItem::Tabs { view, .. } => {
view.update(cx, |tab_panel, cx| {
tab_panel.remove_panel(panel, window, cx);
});
}
DockItem::Split { items, view, .. } => {
for item in items {
item.remove_panel(panel.clone(), window, cx);
}
view.update(cx, |split, cx| {
split.remove_panel(panel, window, cx);
});
}
DockItem::Tiles { view, .. } => {
view.update(cx, |tiles, cx| {
tiles.remove(panel, window, cx);
});
}
DockItem::Panel { .. } => {}
}
}
pub fn set_collapsed(&self, collapsed: bool, window: &mut Window, cx: &mut App) {
match self {
DockItem::Tabs { view, .. } => {
view.update(cx, |tab_panel, cx| {
tab_panel.set_collapsed(collapsed, window, cx);
});
}
DockItem::Split { items, .. } => {
for item in items {
item.set_collapsed(collapsed, window, cx);
}
}
DockItem::Tiles { .. } => {}
DockItem::Panel { view, .. } => view.set_active(!collapsed, window, cx),
}
}
pub(crate) fn left_top_tab_panel(&self, cx: &App) -> Option<Entity<TabPanel>> {
match self {
DockItem::Tabs { view, .. } => Some(view.clone()),
DockItem::Split { view, .. } => view.read(cx).left_top_tab_panel(true, cx),
DockItem::Tiles { .. } => None,
DockItem::Panel { .. } => None,
}
}
fn collect_tab_panels(&self, out: &mut Vec<Entity<TabPanel>>, cx: &App) {
match self {
DockItem::Tabs { view, .. } => out.push(view.clone()),
DockItem::Split { items, .. } => {
for item in items {
item.collect_tab_panels(out, cx);
}
}
DockItem::Tiles { view, .. } => {
let panels = view.read(cx).panels().to_vec();
for tile_item in panels {
if tile_item.panel.panel_name(cx) == "TabPanel" {
let tab_panel: Entity<TabPanel> = tile_item.panel.as_ref().into();
out.push(tab_panel);
}
}
}
DockItem::Panel { view, .. } => {
if view.panel_name(cx) == "TabPanel" {
let tab_panel: Entity<TabPanel> = view.as_ref().into();
out.push(tab_panel);
}
}
}
}
}
impl DockArea {
pub fn new(
id: impl Into<SharedString>, version: Option<usize>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let weak_self = cx.entity().downgrade();
let stack_panel = cx.new(|cx| {
let mut sp = StackPanel::new(Axis::Horizontal, window, cx);
sp.set_dock_area(weak_self.clone());
sp
});
let center_tab =
cx.new(|cx| TabPanel::new(Some(stack_panel.downgrade()), weak_self.clone(), window, cx));
stack_panel.update(cx, |sp, cx| {
sp.add_panel(
Arc::new(center_tab.clone()),
None,
weak_self.clone(),
window,
cx,
);
});
let center = DockItem::Split {
axis: Axis::Horizontal,
size: None,
items: vec![DockItem::Tabs {
size: None,
items: vec![],
active_ix: 0,
view: center_tab,
}],
sizes: vec![None],
view: stack_panel.clone(),
};
let left_dock = cx.new(|cx| {
let mut d = Dock::left(weak_self.clone(), window, cx);
d.set_collapsed(true, window, cx);
d
});
let bottom_dock = cx.new(|cx| {
let mut d = Dock::bottom(weak_self.clone(), window, cx);
d.set_collapsed(true, window, cx);
d
});
let right_dock = cx.new(|cx| {
let mut d = Dock::right(weak_self.clone(), window, cx);
d.set_collapsed(true, window, cx);
d
});
let mut this = Self {
id: id.into(),
version,
bounds: Bounds::default(),
center,
center_enabled: true,
left_dock,
bottom_dock,
right_dock,
zoom_view: None,
toggle_button_panels: Edges::default(),
toggle_button_visible: true,
locked: false,
panel_style: PanelStyle::default(),
tab_bar_direction: TabBarDirection::default(),
center_placeholder: None,
_subscriptions: vec![],
subscribed_panel_ids: HashSet::new(),
subscribed_tile_drop_ids: HashSet::new(),
split_preview: None,
};
this.subscribe_panel(&stack_panel, window, cx);
this.update_toggle_button_tab_panels(window, cx);
this
}
pub fn bounds(&self) -> Bounds<Pixels> {
self.bounds
}
fn subscribe_tiles_item_drop(
&mut self, tile_panel: &Entity<Tiles>, _: &mut Window, cx: &mut Context<Self>,
) {
if !self.subscribed_tile_drop_ids.insert(tile_panel.entity_id()) {
return;
}
self
._subscriptions
.push(cx.subscribe(tile_panel, move |_, _, evt: &DragDrop, cx| {
let item = evt.0.clone();
cx.emit(DockEvent::DragDrop(item));
}));
}
pub fn panel_style(mut self, style: PanelStyle) -> Self {
self.panel_style = style;
self
}
pub fn tab_bar_direction(mut self, direction: TabBarDirection) -> Self {
self.tab_bar_direction = direction;
self
}
pub fn set_tab_bar_direction(
&mut self, direction: TabBarDirection, _: &mut Window, cx: &mut Context<Self>,
) {
self.tab_bar_direction = direction;
cx.notify();
}
pub fn set_version(&mut self, version: usize, _: &mut Window, cx: &mut Context<Self>) {
self.version = Some(version);
cx.notify();
}
pub fn set_center_placeholder(
&mut self, view: impl Into<AnyView>, _: &mut Window, cx: &mut Context<Self>,
) {
self.center_placeholder = Some(view.into());
cx.notify();
}
pub fn clear_center_placeholder(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.center_placeholder = None;
cx.notify();
}
pub fn center_placeholder(&self) -> Option<&AnyView> {
self.center_placeholder.as_ref()
}
pub fn center(&self) -> &DockItem {
&self.center
}
pub fn left_dock(&self) -> &Entity<Dock> {
&self.left_dock
}
pub fn bottom_dock(&self) -> &Entity<Dock> {
&self.bottom_dock
}
pub fn right_dock(&self) -> &Entity<Dock> {
&self.right_dock
}
pub fn add_to_center(
&mut self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut Context<Self>,
) {
let weak_self = cx.entity().downgrade();
self.center.add_panel(panel, &weak_self, None, window, cx);
cx.notify();
}
pub fn add_to_left_dock(
&mut self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut Context<Self>,
) {
self.left_dock.update(cx, |dock, cx| {
dock.add_panel(panel, window, cx);
});
}
pub fn add_to_right_dock(
&mut self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut Context<Self>,
) {
self.right_dock.update(cx, |dock, cx| {
dock.add_panel(panel, window, cx);
});
}
pub fn add_to_bottom_dock(
&mut self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut Context<Self>,
) {
self.bottom_dock.update(cx, |dock, cx| {
dock.add_panel(panel, window, cx);
});
}
pub fn enable_center(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.center_enabled = true;
cx.notify();
}
pub fn disable_center(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.center_enabled = false;
cx.notify();
}
pub fn set_center_enabled(&mut self, enabled: bool, _: &mut Window, cx: &mut Context<Self>) {
self.center_enabled = enabled;
cx.notify();
}
pub fn is_center_enabled(&self) -> bool {
self.center_enabled
}
pub fn set_dock_size(
&mut self, placement: DockPlacement, size: Pixels, window: &mut Window, cx: &mut Context<Self>,
) {
let dock = match placement {
DockPlacement::Left => &self.left_dock,
DockPlacement::Right => &self.right_dock,
DockPlacement::Bottom => &self.bottom_dock,
DockPlacement::Center => return,
};
dock.update(cx, |dock, cx| {
dock.set_size(size, window, cx);
});
}
pub fn set_dock_collapsed(
&mut self, placement: DockPlacement, collapsed: bool, window: &mut Window,
cx: &mut Context<Self>,
) {
let dock = match placement {
DockPlacement::Left => &self.left_dock,
DockPlacement::Right => &self.right_dock,
DockPlacement::Bottom => &self.bottom_dock,
DockPlacement::Center => return,
};
dock.update(cx, |dock, cx| {
dock.set_collapsed(collapsed, window, cx);
});
}
pub fn set_locked(&mut self, locked: bool, _window: &mut Window, _cx: &mut App) {
self.locked = locked;
}
#[inline]
pub fn is_locked(&self) -> bool {
self.locked
}
pub fn has_dock(&self, _placement: DockPlacement) -> bool {
true
}
pub fn is_dock_collapsed(&self, placement: DockPlacement, cx: &App) -> bool {
match placement {
DockPlacement::Left => self.left_dock.read(cx).is_collapsed(),
DockPlacement::Bottom => self.bottom_dock.read(cx).is_collapsed(),
DockPlacement::Right => self.right_dock.read(cx).is_collapsed(),
DockPlacement::Center => false,
}
}
#[deprecated(note = "Docks are now always collapsible. This method is a no-op.")]
pub fn set_dock_collapsible(
&mut self, _collapsible_edges: Edges<bool>, _window: &mut Window, _cx: &mut Context<Self>,
) {
}
#[deprecated(note = "Docks are now always collapsible. Always returns true.")]
pub fn is_dock_collapsible(&self, _placement: DockPlacement, _cx: &App) -> bool {
true
}
pub fn toggle_dock(&self, placement: DockPlacement, window: &mut Window, cx: &mut Context<Self>) {
let dock = match placement {
DockPlacement::Left => &self.left_dock,
DockPlacement::Bottom => &self.bottom_dock,
DockPlacement::Right => &self.right_dock,
DockPlacement::Center => return,
};
dock.update(cx, |view, cx| {
view.toggle_collapsed(window, cx);
});
}
pub fn set_toggle_button_visible(&mut self, visible: bool, _: &mut Context<Self>) {
self.toggle_button_visible = visible;
}
pub fn add_panel(
&mut self, panel: Arc<dyn PanelView>, placement: DockPlacement, bounds: Option<Bounds<Pixels>>,
window: &mut Window, cx: &mut Context<Self>,
) {
match placement {
DockPlacement::Left => {
self
.left_dock
.update(cx, |dock, cx| dock.add_panel(panel, window, cx));
}
DockPlacement::Bottom => {
self
.bottom_dock
.update(cx, |dock, cx| dock.add_panel(panel, window, cx));
}
DockPlacement::Right => {
self
.right_dock
.update(cx, |dock, cx| dock.add_panel(panel, window, cx));
}
DockPlacement::Center => {
self
.center
.add_panel(panel, &cx.entity().downgrade(), bounds, window, cx);
}
}
}
pub fn remove_panel(
&mut self, panel: Arc<dyn PanelView>, placement: DockPlacement, window: &mut Window,
cx: &mut Context<Self>,
) {
match placement {
DockPlacement::Left => {
self.left_dock.update(cx, |dock, cx| {
dock.remove_panel(panel, window, cx);
});
}
DockPlacement::Right => {
self.right_dock.update(cx, |dock, cx| {
dock.remove_panel(panel, window, cx);
});
}
DockPlacement::Bottom => {
self.bottom_dock.update(cx, |dock, cx| {
dock.remove_panel(panel, window, cx);
});
}
DockPlacement::Center => {
self.center.remove_panel(panel, window, cx);
}
}
cx.notify();
}
pub fn remove_panel_from_all_docks(
&mut self, panel: Arc<dyn PanelView>, window: &mut Window, cx: &mut Context<Self>,
) {
self.remove_panel(panel.clone(), DockPlacement::Center, window, cx);
self.remove_panel(panel.clone(), DockPlacement::Left, window, cx);
self.remove_panel(panel.clone(), DockPlacement::Right, window, cx);
self.remove_panel(panel.clone(), DockPlacement::Bottom, window, cx);
}
fn all_tab_panels(&self, cx: &App) -> Vec<Entity<TabPanel>> {
let mut panels = Vec::new();
self.center.collect_tab_panels(&mut panels, cx);
self
.left_dock
.read(cx)
.panel
.collect_tab_panels(&mut panels, cx);
self
.right_dock
.read(cx)
.panel
.collect_tab_panels(&mut panels, cx);
self
.bottom_dock
.read(cx)
.panel
.collect_tab_panels(&mut panels, cx);
panels
}
pub fn panel_by_id(&self, panel_id: &str, cx: &App) -> Option<Arc<dyn PanelView>> {
for tab_panel in self.all_tab_panels(cx) {
if let Some(panel) = tab_panel.read(cx).panel_by_id(panel_id, cx) {
return Some(panel);
}
}
None
}
pub fn get_panel_by_id(&self, panel_id: &str, cx: &App) -> Option<Arc<dyn PanelView>> {
self.panel_by_id(panel_id, cx)
}
pub fn activate_panel_by_id(
&mut self, panel_id: &str, window: &mut Window, cx: &mut Context<Self>,
) -> bool {
for tab_panel in self.all_tab_panels(cx) {
let mut activated = false;
tab_panel.update(cx, |tab_panel, cx| {
activated = tab_panel.activate_panel_by_id(panel_id, window, cx);
});
if activated {
return true;
}
}
false
}
pub fn highlight_panel_by_id(
&mut self, panel_id: &str, window: &mut Window, cx: &mut Context<Self>,
) -> bool {
if !self.activate_panel_by_id(panel_id, window, cx) {
return false;
}
if let Some(panel) = self.panel_by_id(panel_id, cx) {
panel.focus_handle(cx).focus(window);
return true;
}
false
}
pub fn close_panel_by_id(
&mut self, panel_id: &str, window: &mut Window, cx: &mut Context<Self>,
) -> bool {
let dock_area_locked = self.is_locked();
for tab_panel in self.all_tab_panels(cx) {
let mut closed = false;
tab_panel.update(cx, |tab_panel, cx| {
closed = tab_panel.close_panel_by_id(panel_id, dock_area_locked, window, cx);
});
if closed {
return true;
}
}
false
}
pub fn load(
&mut self, state: DockAreaState, window: &mut Window, cx: &mut Context<Self>,
) -> Result<()> {
self._subscriptions.clear();
self.subscribed_panel_ids.clear();
self.subscribed_tile_drop_ids.clear();
self.split_preview = None;
self.version = state.version;
self.center_enabled = state.center_enabled;
let weak_self = cx.entity().downgrade();
if let Some(left_dock_state) = state.left_dock {
self.left_dock = left_dock_state.to_dock(weak_self.clone(), window, cx);
}
if let Some(right_dock_state) = state.right_dock {
self.right_dock = right_dock_state.to_dock(weak_self.clone(), window, cx);
}
if let Some(bottom_dock_state) = state.bottom_dock {
self.bottom_dock = bottom_dock_state.to_dock(weak_self.clone(), window, cx);
}
self.center = state.center.to_item(weak_self.clone(), window, cx);
if let DockItem::Split { view, .. } = &self.center {
view.update(cx, |sp, _| sp.set_dock_area(weak_self));
}
self.update_toggle_button_tab_panels(window, cx);
Ok(())
}
pub fn dump(&self, cx: &App) -> DockAreaState {
let root = self.center.view();
let center = root.dump(cx);
DockAreaState {
version: self.version,
center,
center_enabled: self.center_enabled,
left_dock: Some(DockState::new(self.left_dock.clone(), cx)),
right_dock: Some(DockState::new(self.right_dock.clone(), cx)),
bottom_dock: Some(DockState::new(self.bottom_dock.clone(), cx)),
}
}
#[allow(clippy::only_used_in_recursion)]
#[allow(dead_code)]
fn subscribe_item(&mut self, item: &DockItem, window: &mut Window, cx: &mut Context<Self>) {
match item {
DockItem::Split { items, view, .. } => {
for item in items {
self.subscribe_item(item, window, cx);
}
self._subscriptions.push(
cx.subscribe_in(view, window, move |_, _, event, window, cx| {
if let PanelEvent::LayoutChanged = event {
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.update_toggle_button_tab_panels(window, cx)
});
})
.detach();
cx.emit(DockEvent::LayoutChanged);
}
}),
);
}
DockItem::Tabs { .. } => {
}
DockItem::Tiles { .. } => {
}
DockItem::Panel { .. } => {
}
}
}
pub(crate) fn subscribe_panel<P: Panel>(
&mut self, view: &Entity<P>, window: &mut Window, cx: &mut Context<DockArea>,
) {
if !self.subscribed_panel_ids.insert(view.entity_id()) {
return;
}
let subscription = cx.subscribe_in(
view,
window,
move |_, panel, event, window, cx| match event {
PanelEvent::ZoomIn => {
let panel = panel.clone();
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.set_zoomed_in(panel, window, cx);
cx.notify();
});
})
.detach();
}
PanelEvent::ZoomOut => cx
.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.set_zoomed_out(window, cx);
});
})
.detach(),
PanelEvent::LayoutChanged => {
cx.spawn_in(window, async move |view, window| {
_ = view.update_in(window, |view, window, cx| {
view.update_toggle_button_tab_panels(window, cx)
});
})
.detach();
cx.emit(DockEvent::LayoutChanged);
}
},
);
self._subscriptions.push(subscription);
}
pub fn id(&self) -> SharedString {
self.id.clone()
}
pub fn set_zoomed_in<P: Panel>(
&mut self, panel: Entity<P>, _: &mut Window, cx: &mut Context<Self>,
) {
self.zoom_view = Some(panel.into());
cx.notify();
}
pub fn set_zoomed_out(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.zoom_view = None;
cx.notify();
}
fn render_items(&self, _window: &mut Window, _cx: &mut Context<Self>) -> AnyElement {
match &self.center {
DockItem::Split { view, .. } => view.clone().into_any_element(),
DockItem::Tabs { view, .. } => view.clone().into_any_element(),
DockItem::Tiles { view, .. } => view.clone().into_any_element(),
DockItem::Panel { view, .. } => view.clone().view().into_any_element(),
}
}
pub fn update_toggle_button_tab_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.toggle_button_panels.bottom = self
.bottom_dock
.read(cx)
.panel
.left_top_tab_panel(cx)
.map(|view| view.entity_id());
}
pub(crate) fn set_split_preview(
&mut self, bounds: Bounds<Pixels>, placement: Placement, cx: &mut Context<Self>,
) {
let next_preview = Some(SplitPreview { bounds, placement });
if self.split_preview == next_preview {
return;
}
self.split_preview = next_preview;
cx.notify();
}
pub(crate) fn clear_split_preview(&mut self, cx: &mut Context<Self>) {
if self.split_preview.take().is_some() {
cx.notify();
}
}
fn render_split_preview(&self, cx: &App) -> Option<impl IntoElement> {
let preview = self.split_preview?;
let bounds = preview.bounds;
let left = bounds.left() - self.bounds.left();
let top = bounds.top() - self.bounds.top();
let overlay = match preview.placement {
Placement::Left => div()
.left(left)
.top(top)
.w(bounds.size.width * 0.5)
.h(bounds.size.height),
Placement::Right => div()
.left(left + bounds.size.width * 0.5)
.top(top)
.w(bounds.size.width * 0.5)
.h(bounds.size.height),
Placement::Top => div()
.left(left)
.top(top)
.w(bounds.size.width)
.h(bounds.size.height * 0.5),
Placement::Bottom => div()
.left(left)
.top(top + bounds.size.height * 0.5)
.w(bounds.size.width)
.h(bounds.size.height * 0.5),
};
Some(overlay.absolute().bg(cx.theme().drop_target))
}
}
impl EventEmitter<DockEvent> for DockArea {}
impl Render for DockArea {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity().clone();
div()
.id("dock-area")
.relative()
.size_full()
.overflow_hidden()
.on_drag_move(cx.listener(|this, drag: &DragMoveEvent<DragPanel>, _, cx| {
if let Some(preview) = this.split_preview
&& !preview.bounds.contains(&drag.event.position)
{
this.clear_split_preview(cx);
}
}))
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, _, cx| this.clear_split_preview(cx)),
)
.on_prepaint(move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds))
.map(|this| {
if let Some(zoom_view) = self.zoom_view.clone() {
this.child(zoom_view)
} else {
match &self.center {
DockItem::Tiles { view, .. } => {
this.child(view.clone())
}
_ => {
let left_dock = self.left_dock.clone();
let right_dock = self.right_dock.clone();
let bottom_dock = self.bottom_dock.clone();
this
.child(
div()
.flex()
.flex_row()
.h_full()
.child(div().flex().flex_none().child(left_dock.clone()))
.child(
div()
.flex()
.flex_1()
.flex_col()
.overflow_hidden()
.child(
div()
.flex_1()
.overflow_hidden()
.when(self.center_enabled, |this| {
this.child(self.render_items(window, cx))
}),
)
.child(bottom_dock.clone()),
)
.child(div().flex().flex_none().child(right_dock.clone())),
)
.when_some(self.render_split_preview(cx), |this, overlay| {
this.child(overlay)
})
.map(|this| {
let dock_read = left_dock.read(cx);
if dock_read.collapsed {
return this;
}
let size = dock_read.display_size();
let dock_clone = left_dock.clone();
this.child(
div()
.absolute()
.left(size)
.top_0()
.bottom_0()
.w(px(0.))
.child(
resize_handle::<ResizePanel, ResizePanel>(
"left-dock-resize",
Axis::Horizontal,
)
.on_drag(ResizePanel, move |info, _, _, cx| {
cx.stop_propagation();
dock_clone.update(cx, |dock, _| dock.set_resizing(true));
cx.new(|_| info.deref().clone())
}),
),
)
})
.map(|this| {
let dock_read = right_dock.read(cx);
if dock_read.collapsed {
return this;
}
let size = dock_read.display_size();
let dock_clone = right_dock.clone();
this.child(
div()
.absolute()
.right(size)
.top_0()
.bottom_0()
.w(px(0.))
.child(
resize_handle::<ResizePanel, ResizePanel>(
"right-dock-resize",
Axis::Horizontal,
)
.on_drag(ResizePanel, move |info, _, _, cx| {
cx.stop_propagation();
dock_clone.update(cx, |dock, _| dock.set_resizing(true));
cx.new(|_| info.deref().clone())
}),
),
)
})
.map(|this| {
let dock_read = bottom_dock.read(cx);
if dock_read.collapsed {
return this;
}
let size = dock_read.display_size();
let dock_clone = bottom_dock.clone();
let left_d = left_dock.read(cx);
let left_size = if left_d.collapsed {
px(41.)
} else {
left_d.display_size()
};
let right_d = right_dock.read(cx);
let right_size = if right_d.collapsed {
px(41.)
} else {
right_d.display_size()
};
this.child(
div()
.absolute()
.bottom(size)
.left(left_size)
.right(right_size)
.h(px(0.))
.child(
resize_handle::<ResizePanel, ResizePanel>(
"bottom-dock-resize",
Axis::Vertical,
)
.on_drag(ResizePanel, move |info, _, _, cx| {
cx.stop_propagation();
dock_clone.update(cx, |dock, _| dock.set_resizing(true));
cx.new(|_| info.deref().clone())
}),
),
)
})
}
}
}
})
}
}