use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::UiStateTrait;
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::Ab;
use super::tile_builder::TileBuilder;
impl crate::ecs::world::World {
pub fn ui_tile_add_pane(
&mut self,
container: freecs::Entity,
title: &str,
) -> Option<(TileId, freecs::Entity)> {
let content_entity = self.spawn_tile_pane_entity(container);
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
else {
return None;
};
let pane = TileNode::Pane {
content_entity,
title: title.to_string(),
};
let pane_id = data.alloc(pane);
let mut current = data.root;
let target_tabs = loop {
match data.get(current) {
Some(TileNode::Tabs { .. }) => break Some(current),
Some(TileNode::Split { children, .. }) => current = children[0],
_ => break None,
}
};
if let Some(tabs_id) = target_tabs
&& let Some(TileNode::Tabs { panes, .. }) = data.get_mut(tabs_id)
{
panes.push(pane_id);
}
Some((pane_id, content_entity))
}
pub fn ui_tile_add_pane_to(
&mut self,
container: freecs::Entity,
tabs_id: TileId,
title: &str,
) -> Option<(TileId, freecs::Entity)> {
let (pane_id, content_entity) = self.ui_tile_add_pane(container, title)?;
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
else {
return None;
};
if let Some(parent_tabs_id) = data.find_parent_tabs(pane_id)
&& parent_tabs_id != tabs_id
&& let Some(TileNode::Tabs { panes, active }) = data.get_mut(parent_tabs_id)
{
panes.retain(|id| *id != pane_id);
if *active >= panes.len() && !panes.is_empty() {
*active = panes.len() - 1;
}
}
if let Some(TileNode::Tabs { panes, .. }) = data.get_mut(tabs_id)
&& !panes.contains(&pane_id)
{
panes.push(pane_id);
}
Some((pane_id, content_entity))
}
pub fn ui_tile_split(
&mut self,
container: freecs::Entity,
target: TileId,
direction: SplitDirection,
ratio: f32,
title: &str,
) -> Option<(TileId, freecs::Entity)> {
let (pane_id, content_entity) = self.ui_tile_add_pane(container, title)?;
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
else {
return None;
};
if let Some(parent_tabs_id) = data.find_parent_tabs(pane_id)
&& let Some(TileNode::Tabs { panes, .. }) = data.get_mut(parent_tabs_id)
{
panes.retain(|id| *id != pane_id);
}
let new_tabs = TileNode::Tabs {
panes: vec![pane_id],
active: 0,
};
let new_tabs_id = data.alloc(new_tabs);
let old_node = data.tiles[target.0].take()?;
let old_id = data.alloc(old_node);
let split = TileNode::Split {
direction,
ratio,
children: [old_id, new_tabs_id],
};
data.tiles[target.0] = Some(split);
Some((pane_id, content_entity))
}
pub fn ui_tile_remove(&mut self, container: freecs::Entity, tile_id: TileId) {
let content_to_hide = if let Some(UiWidgetState::TileContainer(data)) =
self.ui.get_ui_widget_state(container)
{
let pane_count = data
.tiles
.iter()
.filter(|t| matches!(t, Some(TileNode::Pane { .. })))
.count();
if pane_count < 2 {
return;
}
if let Some(TileNode::Pane { content_entity, .. }) = data.get(tile_id) {
Some(*content_entity)
} else {
None
}
} else {
return;
};
if let Some(content) = content_to_hide
&& let Some(node) = self.ui.get_ui_layout_node_mut(content)
{
node.visible = false;
}
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
else {
return;
};
if let Some(tabs_id) = data.find_parent_tabs(tile_id) {
if let Some(TileNode::Tabs { panes, active }) = data.get_mut(tabs_id) {
panes.retain(|id| *id != tile_id);
if *active >= panes.len() && !panes.is_empty() {
*active = panes.len() - 1;
}
}
data.free(tile_id);
let remaining = if let Some(TileNode::Tabs { panes, .. }) = data.get(tabs_id) {
panes.len()
} else {
0
};
if remaining == 0 {
if let Some((parent_split_id, child_index)) = data.find_parent_split(tabs_id) {
let sibling_index = 1 - child_index;
let sibling_id =
if let Some(TileNode::Split { children, .. }) = data.get(parent_split_id) {
children[sibling_index]
} else {
return;
};
let sibling_node = data.tiles[sibling_id.0].take();
data.tiles[parent_split_id.0] = sibling_node;
data.free(tabs_id);
data.free(sibling_id);
} else {
data.free(tabs_id);
}
}
}
}
pub fn ui_tile_pane_content(
&self,
container: freecs::Entity,
tile_id: TileId,
) -> Option<freecs::Entity> {
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state(container)
else {
return None;
};
if let Some(TileNode::Pane { content_entity, .. }) = data.get(tile_id) {
Some(*content_entity)
} else {
None
}
}
pub fn ui_tile_pane_title(&self, container: freecs::Entity, pane_id: TileId) -> Option<String> {
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state(container)
else {
return None;
};
if let Some(TileNode::Pane { title, .. }) = data.get(pane_id) {
Some(title.clone())
} else {
None
}
}
pub fn ui_tile_active_pane(&self, container: freecs::Entity, pane_id: TileId) -> bool {
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state(container)
else {
return false;
};
if let Some(tabs_id) = data.find_parent_tabs(pane_id)
&& let Some(TileNode::Tabs { panes, active }) = data.get(tabs_id)
&& panes.get(*active) == Some(&pane_id)
{
return true;
}
false
}
pub fn ui_tile_tab_activated(&self, container: freecs::Entity) -> Option<TileId> {
for event in &self.resources.retained_ui.frame_events {
if let crate::ecs::ui::resources::UiEvent::TileTabActivated {
container: event_container,
pane_id,
} = event
&& *event_container == container
{
return Some(*pane_id);
}
}
None
}
pub fn ui_tile_tab_closed(&self, container: freecs::Entity) -> Option<(TileId, String)> {
for event in &self.resources.retained_ui.frame_events {
if let crate::ecs::ui::resources::UiEvent::TileTabClosed {
container: event_container,
pane_id,
title,
} = event
&& *event_container == container
{
return Some((*pane_id, title.clone()));
}
}
None
}
pub fn ui_tile_splitter_moved(&self, container: freecs::Entity) -> Option<(TileId, f32)> {
for event in &self.resources.retained_ui.frame_events {
if let crate::ecs::ui::resources::UiEvent::TileSplitterMoved {
container: event_container,
split_id,
ratio,
} = event
&& *event_container == container
{
return Some((*split_id, *ratio));
}
}
None
}
pub fn ui_tile_set_active(&mut self, container: freecs::Entity, pane_id: TileId) {
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
else {
return;
};
if let Some(tabs_id) = data.find_parent_tabs(pane_id)
&& let Some(TileNode::Tabs { panes, active }) = data.get_mut(tabs_id)
&& let Some(index) = panes.iter().position(|id| *id == pane_id)
{
*active = index;
}
}
pub fn ui_tile_save_layout(
&self,
container: freecs::Entity,
) -> Option<crate::ecs::ui::components::TileLayout> {
use crate::ecs::ui::components::{TileLayout, TileLayoutNode};
let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state(container)
else {
return None;
};
let nodes = data
.tiles
.iter()
.map(|tile| {
tile.as_ref().map(|node| match node {
TileNode::Pane { title, .. } => TileLayoutNode::Pane {
title: title.clone(),
},
TileNode::Split {
direction,
ratio,
children,
} => TileLayoutNode::Split {
direction: *direction,
ratio: *ratio,
children: *children,
},
TileNode::Tabs { panes, active } => TileLayoutNode::Tabs {
panes: panes.clone(),
active: *active,
},
})
})
.collect();
Some(TileLayout {
nodes,
root: data.root,
})
}
pub fn ui_tile_load_layout(
&mut self,
container: freecs::Entity,
layout: &crate::ecs::ui::components::TileLayout,
) -> Vec<(TileId, freecs::Entity)> {
use crate::ecs::ui::components::TileLayoutNode;
let old_pane_entities: Vec<freecs::Entity> = if let Some(UiWidgetState::TileContainer(
data,
)) = self.ui.get_ui_widget_state(container)
{
data.tiles
.iter()
.filter_map(|tile| {
if let Some(TileNode::Pane { content_entity, .. }) = tile {
Some(*content_entity)
} else {
None
}
})
.collect()
} else {
return Vec::new();
};
for entity in &old_pane_entities {
if let Some(node) = self.ui.get_ui_layout_node_mut(*entity) {
node.visible = false;
}
}
let mut pane_mappings = Vec::new();
let mut new_tiles: Vec<Option<TileNode>> = Vec::with_capacity(layout.nodes.len());
for layout_node in &layout.nodes {
match layout_node {
Some(TileLayoutNode::Pane { title }) => {
let content_entity = self.spawn_tile_pane_entity(container);
let tile_id = TileId(new_tiles.len());
pane_mappings.push((tile_id, content_entity));
new_tiles.push(Some(TileNode::Pane {
content_entity,
title: title.clone(),
}));
}
Some(TileLayoutNode::Split {
direction,
ratio,
children,
}) => {
new_tiles.push(Some(TileNode::Split {
direction: *direction,
ratio: *ratio,
children: *children,
}));
}
Some(TileLayoutNode::Tabs { panes, active }) => {
new_tiles.push(Some(TileNode::Tabs {
panes: panes.clone(),
active: *active,
}));
}
None => {
new_tiles.push(None);
}
}
}
let mut next_free = Vec::new();
for (index, tile) in new_tiles.iter().enumerate() {
if tile.is_none() {
next_free.push(index);
}
}
let rects_len = new_tiles.len();
if let Some(UiWidgetState::TileContainer(data)) = self.ui.get_ui_widget_state_mut(container)
{
data.tiles = new_tiles;
data.root = layout.root;
data.next_free = next_free;
data.rects
.resize(rects_len, crate::ecs::ui::types::Rect::default());
data.dragging_splitter = None;
data.pending_tab_drag = None;
data.dragging_tab = None;
data.drop_preview = None;
data.hovered_close = None;
}
self.resources.children_cache_valid = false;
self.resources.retained_ui.layout_dirty = true;
pane_mappings
}
fn spawn_tile_pane_entity(&mut self, container: freecs::Entity) -> freecs::Entity {
use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection, FlowLayout};
use crate::ecs::ui::state::UiBase;
let theme = self.resources.retained_ui.theme_state.active_theme();
let panel_color = theme.panel_color;
let content_entity = self.spawn();
self.core
.add_components(content_entity, crate::ecs::world::PARENT);
self.ui.add_components(
content_entity,
crate::ecs::world::UI_LAYOUT_NODE
| crate::ecs::world::UI_NODE_COLOR
| crate::ecs::world::UI_NODE_CONTENT
| crate::ecs::world::UI_STATE_WEIGHTS,
);
if let Some(parent) = self.core.get_parent_mut(content_entity) {
*parent = crate::ecs::transform::components::Parent(Some(container));
}
if let Some(node) = self.ui.get_ui_layout_node_mut(content_entity) {
*node = crate::ecs::ui::components::UiLayoutNode::default();
node.layouts[UiBase::INDEX] = Some(crate::ecs::ui::layout_types::UiLayoutType::Window(
crate::ecs::ui::layout_types::WindowLayout {
position: Ab(Vec2::new(0.0, 0.0)).into(),
size: Ab(Vec2::new(100.0, 100.0)).into(),
anchor: Anchor::TopLeft,
},
));
node.depth = crate::ecs::ui::components::UiDepthMode::Add(0.0);
node.clip_content = true;
node.flow_layout = Some(FlowLayout {
direction: FlowDirection::Vertical,
padding: 4.0,
spacing: 4.0,
alignment: FlowAlignment::Start,
cross_alignment: FlowAlignment::Start,
wrap: false,
});
}
if let Some(color) = self.ui.get_ui_node_color_mut(content_entity) {
color.colors[UiBase::INDEX] = Some(panel_color);
}
if let Some(content) = self.ui.get_ui_node_content_mut(content_entity) {
*content = crate::ecs::ui::components::UiNodeContent::Rect {
corner_radius: 0.0,
border_width: 0.0,
border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
};
}
self.resources.children_cache_valid = false;
self.resources.retained_ui.layout_dirty = true;
content_entity
}
pub fn build_tiles(
&mut self,
container: freecs::Entity,
f: impl FnOnce(&mut TileBuilder<'_, '_>),
) {
let mut tree = UiTreeBuilder::from_parent(self, container);
tree.build_tiles(container, f);
}
}