use super::splitter::DockSplitter;
use super::tabs::DockTabs;
use super::types::DockZone;
use crate::tree::{NodeId, UiTree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DockError {
NodeNotFound(NodeId),
InvalidWidgetType,
InvalidTabIndex,
SameContainerTransfer,
EdgeZoneRequired,
NoRollbackData,
NoRemainingSibling,
}
impl std::fmt::Display for DockError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DockError::NodeNotFound(id) => write!(f, "Node {:?} not found", id),
DockError::InvalidWidgetType => write!(f, "Widget is not the expected type"),
DockError::InvalidTabIndex => write!(f, "Tab index out of bounds"),
DockError::SameContainerTransfer => write!(f, "Cannot transfer to same container"),
DockError::EdgeZoneRequired => {
write!(f, "SplitContainerOperation requires an edge zone")
}
DockError::NoRollbackData => write!(f, "No rollback data available"),
DockError::NoRemainingSibling => write!(f, "Splitter has no remaining child"),
}
}
}
impl std::error::Error for DockError {}
pub type DockResult<T> = Result<T, DockError>;
pub trait DockOperation {
fn execute(&mut self, tree: &mut UiTree) -> DockResult<()>;
fn rollback(&mut self, tree: &mut UiTree) -> DockResult<()>;
}
#[derive(Debug)]
pub struct TransferTabOperation {
pub source_container: NodeId,
pub target_container: NodeId,
pub source_tab_index: usize,
pub target_insert_index: usize,
rollback_data: Option<TransferRollback>,
}
#[derive(Debug)]
struct TransferRollback {
tab_label: String,
tab_content: NodeId,
source_index: usize,
target_index: usize,
}
impl TransferTabOperation {
pub fn new(
source_container: NodeId,
target_container: NodeId,
source_tab_index: usize,
target_insert_index: usize,
) -> Self {
Self {
source_container,
target_container,
source_tab_index,
target_insert_index,
rollback_data: None,
}
}
}
impl DockOperation for TransferTabOperation {
fn execute(&mut self, tree: &mut UiTree) -> DockResult<()> {
let source_widget = tree
.get_widget(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_widget
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let target_widget = tree
.get_widget(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?;
let _ = target_widget
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
if self.source_tab_index >= source_tabs.tab_count() {
return Err(DockError::InvalidTabIndex);
}
if self.source_container == self.target_container {
return Err(DockError::SameContainerTransfer);
}
let (tab_label, tab_content) = {
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
source_tabs
.remove_tab(self.source_tab_index)
.ok_or(DockError::InvalidTabIndex)?
};
{
let target_mut = tree
.get_widget_mut(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?;
let target_tabs = target_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let insert_index = self.target_insert_index.min(target_tabs.tab_count());
target_tabs.insert_tab_at(insert_index, &tab_label, tab_content);
}
let insert_index = self.target_insert_index;
tree.remove_child(self.source_container, tab_content);
tree.add_child(self.target_container, tab_content);
self.rollback_data = Some(TransferRollback {
tab_label,
tab_content,
source_index: self.source_tab_index,
target_index: insert_index,
});
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
self.target_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
tab_content,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
Ok(())
}
fn rollback(&mut self, tree: &mut UiTree) -> DockResult<()> {
let rollback = self.rollback_data.take().ok_or(DockError::NoRollbackData)?;
{
let target_mut = tree
.get_widget_mut(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?;
let target_tabs = target_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
if rollback.target_index < target_tabs.tab_count() {
target_tabs.remove_tab(rollback.target_index);
}
}
{
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let insert_index = rollback.source_index.min(source_tabs.tab_count());
source_tabs.insert_tab_at(insert_index, &rollback.tab_label, rollback.tab_content);
}
tree.remove_child(self.target_container, rollback.tab_content);
tree.add_child(self.source_container, rollback.tab_content);
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
self.target_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
rollback.tab_content,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
Ok(())
}
}
#[derive(Debug)]
pub struct SplitContainerOperation {
pub source_container: NodeId,
pub target_container: NodeId,
pub source_tab_index: usize,
pub zone: DockZone,
rollback_data: Option<SplitRollback>,
}
#[derive(Debug)]
struct SplitRollback {
new_tabs_node: NodeId,
splitter_node: NodeId,
tab_label: String,
tab_content: NodeId,
target_parent: Option<NodeId>,
}
impl SplitContainerOperation {
pub fn new(
source_container: NodeId,
target_container: NodeId,
source_tab_index: usize,
zone: DockZone,
) -> Self {
Self {
source_container,
target_container,
source_tab_index,
zone,
rollback_data: None,
}
}
}
impl DockOperation for SplitContainerOperation {
fn execute(&mut self, tree: &mut UiTree) -> DockResult<()> {
if matches!(self.zone, DockZone::Center) {
return Err(DockError::EdgeZoneRequired);
}
let source_widget = tree
.get_widget(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_widget
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
if self.source_tab_index >= source_tabs.tab_count() {
return Err(DockError::InvalidTabIndex);
}
let _ = tree
.get_widget(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let target_parent = tree.get_node(self.target_container).and_then(|n| n.parent);
let target_index_in_parent = if let Some(parent_id) = target_parent {
tree.get_node(parent_id)
.map(|p| {
p.children
.iter()
.position(|&c| c == self.target_container)
.unwrap_or(0)
})
.unwrap_or(0)
} else {
0
};
let (tab_label, tab_content) = {
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
source_tabs
.remove_tab(self.source_tab_index)
.ok_or(DockError::InvalidTabIndex)?
};
tree.remove_child(self.source_container, tab_content);
let mut new_tabs = DockTabs::new();
new_tabs.add_tab(&tab_label, tab_content);
if let Some(source_widget) = tree.get_widget(self.source_container)
&& let Some(source_tabs) = source_widget.as_any().downcast_ref::<DockTabs>()
{
new_tabs.theme = source_tabs.theme.clone();
new_tabs.content_padding = source_tabs.content_padding;
}
if let Some(target_widget) = tree.get_widget(self.target_container) {
new_tabs.style = target_widget.style().clone();
}
let new_tabs_node = tree.add_widget(Box::new(new_tabs));
tree.add_child(new_tabs_node, tab_content);
let direction = self
.zone
.split_direction()
.expect("Edge zone must have a split direction");
let mut splitter = DockSplitter::new(direction);
if let Some(target_widget) = tree.get_widget(self.target_container) {
splitter.style = target_widget.style().clone();
}
if self.zone.is_before() {
splitter.children = vec![new_tabs_node, self.target_container];
} else {
splitter.children = vec![self.target_container, new_tabs_node];
}
let splitter_node = tree.add_widget(Box::new(splitter));
tree.add_child(splitter_node, new_tabs_node);
if let Some(parent_id) = target_parent {
tree.remove_child(parent_id, self.target_container);
}
tree.add_child(splitter_node, self.target_container);
if let Some(parent_id) = target_parent {
if let Some(parent_widget) = tree.get_widget_mut(parent_id)
&& let Some(children) = parent_widget.children_mut()
{
if let Some(pos) = children.iter().position(|&c| c == self.target_container) {
children[pos] = splitter_node;
} else {
let insert_pos = target_index_in_parent.min(children.len());
children.insert(insert_pos, splitter_node);
}
}
tree.add_child(parent_id, splitter_node);
if let Some(parent_widget) = tree.get_widget(parent_id) {
let widget_children: Vec<NodeId> = parent_widget.children().to_vec();
tree.set_children(parent_id, &widget_children);
}
} else {
tree.set_root(splitter_node);
}
self.rollback_data = Some(SplitRollback {
new_tabs_node,
splitter_node,
tab_label,
tab_content,
target_parent,
});
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(self.target_container, crate::dirty::DirtyFlags::LAYOUT);
tree.mark_dirty_flags(
new_tabs_node,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
splitter_node,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
tab_content,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
if let Some(parent_id) = target_parent {
tree.mark_dirty_flags(
parent_id,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
}
Ok(())
}
fn rollback(&mut self, tree: &mut UiTree) -> DockResult<()> {
let rollback = self.rollback_data.take().ok_or(DockError::NoRollbackData)?;
tree.remove_child(rollback.splitter_node, self.target_container);
if let Some(parent_id) = rollback.target_parent {
if let Some(parent_widget) = tree.get_widget_mut(parent_id)
&& let Some(children) = parent_widget.children_mut()
&& let Some(pos) = children.iter().position(|&c| c == rollback.splitter_node)
{
children[pos] = self.target_container;
}
tree.remove_child(parent_id, rollback.splitter_node);
tree.add_child(parent_id, self.target_container);
if let Some(parent_widget) = tree.get_widget(parent_id) {
let widget_children: Vec<NodeId> = parent_widget.children().to_vec();
tree.set_children(parent_id, &widget_children);
}
} else {
tree.set_root(self.target_container);
}
{
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let insert_index = self.source_tab_index.min(source_tabs.tab_count());
source_tabs.insert_tab_at(insert_index, &rollback.tab_label, rollback.tab_content);
}
tree.add_child(self.source_container, rollback.tab_content);
tree.remove_node(rollback.new_tabs_node);
tree.remove_node(rollback.splitter_node);
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(self.target_container, crate::dirty::DirtyFlags::LAYOUT);
tree.mark_dirty_flags(
rollback.tab_content,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
if let Some(parent_id) = rollback.target_parent {
tree.mark_dirty_flags(
parent_id,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
}
Ok(())
}
}
pub fn collapse_empty_container(tree: &mut UiTree, container_id: NodeId) -> DockResult<bool> {
let is_empty = {
let widget = tree
.get_widget(container_id)
.ok_or(DockError::NodeNotFound(container_id))?;
let tabs = widget
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
tabs.tab_count() == 0
};
if !is_empty {
return Ok(false);
}
let parent_id = match tree.get_node(container_id).and_then(|n| n.parent) {
Some(id) => id,
None => return Ok(false), };
let is_splitter = tree
.get_widget(parent_id)
.map(|w| w.as_any().downcast_ref::<DockSplitter>().is_some())
.unwrap_or(false);
if !is_splitter {
return Ok(false);
}
let splitter_children: Vec<NodeId> = tree
.get_widget(parent_id)
.map(|w| w.children().to_vec())
.unwrap_or_default();
let remaining_child = splitter_children
.iter()
.find(|&&c| c != container_id)
.copied()
.ok_or(DockError::NoRemainingSibling)?;
let splitter_style = tree.get_widget(parent_id).map(|w| w.style().clone());
if let Some(style) = splitter_style {
if let Some(remaining_widget) = tree.get_widget_mut(remaining_child) {
let child_style = remaining_widget.style_mut();
child_style.layout.size = style.layout.size;
child_style.layout.flex_grow = style.layout.flex_grow;
child_style.layout.flex_shrink = style.layout.flex_shrink;
child_style.constraints = style.constraints.clone();
}
tree.sync_taffy_style(remaining_child);
}
let grandparent_id = tree.get_node(parent_id).and_then(|n| n.parent);
tree.remove_child(parent_id, container_id);
tree.remove_child(parent_id, remaining_child);
if let Some(gp_id) = grandparent_id {
if let Some(gp_widget) = tree.get_widget_mut(gp_id)
&& let Some(children) = gp_widget.children_mut()
&& let Some(pos) = children.iter().position(|&c| c == parent_id)
{
children[pos] = remaining_child;
}
tree.remove_child(gp_id, parent_id);
tree.add_child(gp_id, remaining_child);
if let Some(gp_widget) = tree.get_widget(gp_id) {
let widget_children: Vec<NodeId> = gp_widget.children().to_vec();
tree.set_children(gp_id, &widget_children);
}
tree.mark_dirty_flags(
gp_id,
crate::dirty::DirtyFlags::LAYOUT
| crate::dirty::DirtyFlags::CHILDREN_ORDER
| crate::dirty::DirtyFlags::GEOMETRY,
);
} else {
tree.set_root(remaining_child);
}
tree.remove_node(container_id);
tree.remove_node(parent_id);
tree.mark_dirty_flags(
remaining_child,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
Ok(true)
}
#[derive(Debug)]
pub struct MergeTabGroupOperation {
pub source_container: NodeId,
pub target_container: NodeId,
pub target_insert_index: usize,
rollback_data: Option<MergeGroupRollback>,
}
#[derive(Debug)]
struct MergeGroupRollback {
tabs: Vec<(String, NodeId)>,
target_start_index: usize,
source_active_tab: usize,
}
impl MergeTabGroupOperation {
pub fn new(
source_container: NodeId,
target_container: NodeId,
target_insert_index: usize,
) -> Self {
Self {
source_container,
target_container,
target_insert_index,
rollback_data: None,
}
}
}
impl DockOperation for MergeTabGroupOperation {
fn execute(&mut self, tree: &mut UiTree) -> DockResult<()> {
let _ = tree
.get_widget(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let _ = tree
.get_widget(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
if self.source_container == self.target_container {
return Err(DockError::SameContainerTransfer);
}
let (tabs, source_active_tab) = {
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let active = source_tabs.active_tab;
let all_tabs = source_tabs.remove_all_tabs();
(all_tabs, active)
};
if tabs.is_empty() {
return Ok(()); }
let insert_idx = {
let target_mut = tree
.get_widget_mut(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?;
let target_tabs = target_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let idx = self.target_insert_index.min(target_tabs.tab_count());
target_tabs.insert_tabs_at(idx, &tabs);
idx
};
for (_, content) in &tabs {
tree.remove_child(self.source_container, *content);
tree.add_child(self.target_container, *content);
}
self.rollback_data = Some(MergeGroupRollback {
tabs,
target_start_index: insert_idx,
source_active_tab,
});
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
self.target_container,
crate::dirty::DirtyFlags::LAYOUT
| crate::dirty::DirtyFlags::CHILDREN_ORDER
| crate::dirty::DirtyFlags::GEOMETRY,
);
Ok(())
}
fn rollback(&mut self, tree: &mut UiTree) -> DockResult<()> {
let rollback = self.rollback_data.take().ok_or(DockError::NoRollbackData)?;
let tab_count = rollback.tabs.len();
for i in (0..tab_count).rev() {
let idx = rollback.target_start_index + i;
let target_mut = tree
.get_widget_mut(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?;
let target_tabs = target_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
target_tabs.remove_tab(idx);
}
{
let source_mut = tree
.get_widget_mut(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?;
let source_tabs = source_mut
.as_any_mut()
.downcast_mut::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
source_tabs.insert_tabs_at(0, &rollback.tabs);
source_tabs.active_tab = rollback
.source_active_tab
.min(source_tabs.tab_count().saturating_sub(1));
}
for (_, content) in &rollback.tabs {
tree.remove_child(self.target_container, *content);
tree.add_child(self.source_container, *content);
}
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
tree.mark_dirty_flags(
self.target_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
Ok(())
}
}
#[derive(Debug)]
pub struct MoveTabGroupOperation {
pub source_container: NodeId,
pub target_container: NodeId,
pub zone: DockZone,
rollback_data: Option<MoveGroupRollback>,
}
#[derive(Debug)]
struct MoveGroupRollback {
_splitter_node: NodeId,
_target_parent: Option<NodeId>,
_source_parent: Option<NodeId>,
_source_index_in_parent: usize,
}
impl MoveTabGroupOperation {
pub fn new(source_container: NodeId, target_container: NodeId, zone: DockZone) -> Self {
Self {
source_container,
target_container,
zone,
rollback_data: None,
}
}
}
impl DockOperation for MoveTabGroupOperation {
fn execute(&mut self, tree: &mut UiTree) -> DockResult<()> {
if matches!(self.zone, DockZone::Center) {
return Err(DockError::EdgeZoneRequired);
}
let _ = tree
.get_widget(self.source_container)
.ok_or(DockError::NodeNotFound(self.source_container))?
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let _ = tree
.get_widget(self.target_container)
.ok_or(DockError::NodeNotFound(self.target_container))?
.as_any()
.downcast_ref::<DockTabs>()
.ok_or(DockError::InvalidWidgetType)?;
let source_parent = tree.get_node(self.source_container).and_then(|n| n.parent);
let source_index_in_parent = if let Some(parent_id) = source_parent {
tree.get_node(parent_id)
.map(|p| {
p.children
.iter()
.position(|&c| c == self.source_container)
.unwrap_or(0)
})
.unwrap_or(0)
} else {
0
};
let target_parent = tree.get_node(self.target_container).and_then(|n| n.parent);
let target_index_in_parent = if let Some(parent_id) = target_parent {
tree.get_node(parent_id)
.map(|p| {
p.children
.iter()
.position(|&c| c == self.target_container)
.unwrap_or(0)
})
.unwrap_or(0)
} else {
0
};
if let Some(parent_id) = source_parent {
if let Some(parent_widget) = tree.get_widget_mut(parent_id)
&& let Some(children) = parent_widget.children_mut()
{
children.retain(|&c| c != self.source_container);
}
tree.remove_child(parent_id, self.source_container);
}
if let Some(parent_id) = source_parent {
let is_splitter = tree
.get_widget(parent_id)
.map(|w| w.as_any().downcast_ref::<DockSplitter>().is_some())
.unwrap_or(false);
if is_splitter {
let remaining_children: Vec<NodeId> = tree
.get_node(parent_id)
.map(|n| n.children.clone())
.unwrap_or_default();
if remaining_children.len() == 1 {
let remaining = remaining_children[0];
let splitter_style = tree.get_widget(parent_id).map(|w| w.style().clone());
if let Some(style) = splitter_style {
if let Some(remaining_widget) = tree.get_widget_mut(remaining) {
let child_style = remaining_widget.style_mut();
child_style.layout.size = style.layout.size;
child_style.layout.flex_grow = style.layout.flex_grow;
child_style.layout.flex_shrink = style.layout.flex_shrink;
child_style.constraints = style.constraints.clone();
}
tree.sync_taffy_style(remaining);
}
let grandparent = tree.get_node(parent_id).and_then(|n| n.parent);
tree.remove_child(parent_id, remaining);
if let Some(gp_id) = grandparent {
if let Some(gp_widget) = tree.get_widget_mut(gp_id)
&& let Some(children) = gp_widget.children_mut()
&& let Some(pos) = children.iter().position(|&c| c == parent_id)
{
children[pos] = remaining;
}
tree.remove_child(gp_id, parent_id);
tree.add_child(gp_id, remaining);
if let Some(gp_widget) = tree.get_widget(gp_id) {
let widget_children: Vec<NodeId> = gp_widget.children().to_vec();
tree.set_children(gp_id, &widget_children);
}
tree.mark_dirty_flags(
gp_id,
crate::dirty::DirtyFlags::LAYOUT
| crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
} else {
tree.set_root(remaining);
}
tree.remove_node(parent_id);
tree.mark_dirty_flags(
remaining,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
}
}
}
let direction = self
.zone
.split_direction()
.expect("Edge zone must have a split direction");
let mut splitter = DockSplitter::new(direction);
if let Some(target_widget) = tree.get_widget(self.target_container) {
splitter.style = target_widget.style().clone();
}
if let Some(target_widget) = tree.get_widget(self.target_container) {
let target_style = target_widget.style().clone();
if let Some(source_widget) = tree.get_widget_mut(self.source_container) {
let source_style = source_widget.style_mut();
source_style.layout.size = target_style.layout.size;
source_style.layout.flex_grow = target_style.layout.flex_grow;
source_style.layout.flex_shrink = target_style.layout.flex_shrink;
}
}
if self.zone.is_before() {
splitter.children = vec![self.source_container, self.target_container];
} else {
splitter.children = vec![self.target_container, self.source_container];
}
let splitter_node = tree.add_widget(Box::new(splitter));
tree.add_child(splitter_node, self.source_container);
let current_target_parent = tree.get_node(self.target_container).and_then(|n| n.parent);
if let Some(tp_id) = current_target_parent {
tree.remove_child(tp_id, self.target_container);
}
tree.add_child(splitter_node, self.target_container);
if let Some(tp_id) = current_target_parent {
if let Some(tp_widget) = tree.get_widget_mut(tp_id)
&& let Some(children) = tp_widget.children_mut()
{
if let Some(pos) = children.iter().position(|&c| c == self.target_container) {
children[pos] = splitter_node;
} else {
let insert_pos = target_index_in_parent.min(children.len());
children.insert(insert_pos, splitter_node);
}
}
tree.add_child(tp_id, splitter_node);
if let Some(tp_widget) = tree.get_widget(tp_id) {
let widget_children: Vec<NodeId> = tp_widget.children().to_vec();
tree.set_children(tp_id, &widget_children);
}
} else {
tree.set_root(splitter_node);
}
self.rollback_data = Some(MoveGroupRollback {
_splitter_node: splitter_node,
_target_parent: target_parent,
_source_parent: source_parent,
_source_index_in_parent: source_index_in_parent,
});
tree.mark_dirty_flags(
self.source_container,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::GEOMETRY,
);
tree.mark_dirty_flags(self.target_container, crate::dirty::DirtyFlags::LAYOUT);
tree.mark_dirty_flags(
splitter_node,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
if let Some(tp_id) = current_target_parent {
tree.mark_dirty_flags(
tp_id,
crate::dirty::DirtyFlags::LAYOUT | crate::dirty::DirtyFlags::CHILDREN_ORDER,
);
}
Ok(())
}
fn rollback(&mut self, _tree: &mut UiTree) -> DockResult<()> {
Err(DockError::NoRollbackData)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transfer_tab_error_cases() {
let mut tree = UiTree::new();
let mut op = TransferTabOperation::new(NodeId(0), NodeId(1), 0, 0);
assert!(matches!(
op.execute(&mut tree),
Err(DockError::NodeNotFound(_))
));
}
#[test]
fn test_rollback_without_execute() {
let mut tree = UiTree::new();
let mut op = TransferTabOperation::new(NodeId(0), NodeId(1), 0, 0);
assert!(matches!(
op.rollback(&mut tree),
Err(DockError::NoRollbackData)
));
}
}