use crate::Id;
use crate::dock_space::{
assert_finite_vec2, assert_nonzero_id, assert_positive_finite_vec2, validate_dock_node_flags,
};
use crate::internal::len_i32;
use crate::sys;
use crate::ui::Ui;
use std::ffi::CString;
use std::ffi::c_char;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::slice;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitDirection {
Left,
Right,
Up,
Down,
}
impl From<SplitDirection> for sys::ImGuiDir {
fn from(dir: SplitDirection) -> Self {
match dir {
SplitDirection::Left => sys::ImGuiDir_Left,
SplitDirection::Right => sys::ImGuiDir_Right,
SplitDirection::Up => sys::ImGuiDir_Up,
SplitDirection::Down => sys::ImGuiDir_Down,
}
}
}
pub struct DockBuilder;
fn assert_existing_dock_node(caller: &str, node_id: Id) {
assert_nonzero_id(caller, "node_id", node_id);
let ctx = unsafe { sys::igGetCurrentContext() };
assert!(!ctx.is_null(), "{caller} requires a current ImGui context");
let node = unsafe { sys::igDockBuilderGetNode(node_id.into()) };
assert!(!node.is_null(), "{caller} requires an existing dock node");
}
unsafe fn free_imgui_id_vector(vector: &mut sys::ImVector_ImGuiID) {
if !vector.Data.is_null() {
unsafe {
sys::igMemFree(vector.Data as *mut c_void);
}
vector.Size = 0;
vector.Capacity = 0;
vector.Data = std::ptr::null_mut();
}
}
pub struct DockNode<'ui> {
raw: *mut sys::ImGuiDockNode,
_phantom: PhantomData<&'ui Ui>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NodeRect {
pub min: [f32; 2],
pub max: [f32; 2],
}
impl<'ui> DockNode<'ui> {
pub fn is_central(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsCentralNode(self.raw) }
}
pub fn is_dock_space(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsDockSpace(self.raw) }
}
pub fn is_empty(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsEmpty(self.raw) }
}
pub fn is_split(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsSplitNode(self.raw) }
}
pub fn is_root(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsRootNode(self.raw) }
}
pub fn is_floating(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsFloatingNode(self.raw) }
}
pub fn is_hidden_tab_bar(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsHiddenTabBar(self.raw) }
}
pub fn is_no_tab_bar(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsNoTabBar(self.raw) }
}
pub fn is_leaf(&self) -> bool {
unsafe { sys::ImGuiDockNode_IsLeafNode(self.raw) }
}
pub fn depth(&self) -> i32 {
unsafe { sys::igDockNodeGetDepth(self.raw as *const sys::ImGuiDockNode) as i32 }
}
pub fn window_menu_button_id(&self) -> sys::ImGuiID {
unsafe { sys::igDockNodeGetWindowMenuButtonId(self.raw as *const sys::ImGuiDockNode) }
}
pub fn root<'a>(&self, _ui: &'a Ui) -> Option<DockNode<'a>> {
let ptr = unsafe { sys::igDockNodeGetRootNode(self.raw) };
if ptr.is_null() {
None
} else {
Some(DockNode {
raw: ptr,
_phantom: PhantomData,
})
}
}
pub fn is_in_hierarchy_of(&self, parent: &DockNode<'_>) -> bool {
unsafe { sys::igDockNodeIsInHierarchyOf(self.raw, parent.raw) }
}
pub fn rect(&self) -> NodeRect {
let r = unsafe { sys::ImGuiDockNode_Rect(self.raw) };
NodeRect {
min: [r.Min.x, r.Min.y],
max: [r.Max.x, r.Max.y],
}
}
}
impl DockBuilder {
pub fn node<'ui>(_ui: &'ui Ui, node_id: Id) -> Option<DockNode<'ui>> {
let ptr = unsafe { sys::igDockBuilderGetNode(node_id.into()) };
if ptr.is_null() {
None
} else {
Some(DockNode {
raw: ptr,
_phantom: PhantomData,
})
}
}
pub fn central_node<'ui>(_ui: &'ui Ui, dockspace_id: Id) -> Option<DockNode<'ui>> {
let ptr = unsafe { sys::igDockBuilderGetCentralNode(dockspace_id.into()) };
if ptr.is_null() {
None
} else {
Some(DockNode {
raw: ptr,
_phantom: PhantomData,
})
}
}
pub fn node_exists(ui: &Ui, node_id: Id) -> bool {
Self::node(ui, node_id).is_some()
}
#[doc(alias = "DockBuilderAddNode")]
pub fn add_node(node_id: Id, flags: crate::DockNodeFlags) -> Id {
validate_dock_node_flags("DockBuilder::add_node()", flags);
unsafe { Id::from(sys::igDockBuilderAddNode(node_id.into(), flags.bits())) }
}
#[doc(alias = "DockBuilderRemoveNode")]
pub fn remove_node(node_id: Id) {
let ctx = unsafe { sys::igGetCurrentContext() };
assert!(
!ctx.is_null(),
"DockBuilder::remove_node() requires a current ImGui context"
);
unsafe { sys::igDockBuilderRemoveNode(node_id.into()) }
}
#[doc(alias = "DockBuilderRemoveNodeDockedWindows")]
pub fn remove_node_docked_windows(node_id: Id, clear_settings_refs: bool) {
let ctx = unsafe { sys::igGetCurrentContext() };
assert!(
!ctx.is_null(),
"DockBuilder::remove_node_docked_windows() requires a current ImGui context"
);
unsafe { sys::igDockBuilderRemoveNodeDockedWindows(node_id.into(), clear_settings_refs) }
}
#[doc(alias = "DockBuilderRemoveNodeChildNodes")]
pub fn remove_node_child_nodes(node_id: Id) {
let ctx = unsafe { sys::igGetCurrentContext() };
assert!(
!ctx.is_null(),
"DockBuilder::remove_node_child_nodes() requires a current ImGui context"
);
unsafe { sys::igDockBuilderRemoveNodeChildNodes(node_id.into()) }
}
#[doc(alias = "DockBuilderSetNodePos")]
pub fn set_node_pos(node_id: Id, pos: [f32; 2]) {
assert_finite_vec2("DockBuilder::set_node_pos()", "pos", pos);
unsafe {
let pos_vec = sys::ImVec2 {
x: pos[0],
y: pos[1],
};
sys::igDockBuilderSetNodePos(node_id.into(), pos_vec)
}
}
#[doc(alias = "DockBuilderSetNodeSize")]
pub fn set_node_size(node_id: Id, size: [f32; 2]) {
assert_positive_finite_vec2("DockBuilder::set_node_size()", "size", size);
unsafe {
let size_vec = sys::ImVec2 {
x: size[0],
y: size[1],
};
sys::igDockBuilderSetNodeSize(node_id.into(), size_vec)
}
}
#[doc(alias = "DockBuilderSplitNode")]
pub fn split_node(
node_id: Id,
split_dir: SplitDirection,
size_ratio_for_node_at_dir: f32,
) -> (Id, Id) {
assert!(
size_ratio_for_node_at_dir.is_finite(),
"DockBuilder::split_node() size_ratio_for_node_at_dir must be finite"
);
assert!(
(0.0..=1.0).contains(&size_ratio_for_node_at_dir),
"DockBuilder::split_node() size_ratio_for_node_at_dir must be between 0.0 and 1.0"
);
assert_existing_dock_node("DockBuilder::split_node()", node_id);
unsafe {
let mut id_at_dir: sys::ImGuiID = 0;
let mut id_at_opposite: sys::ImGuiID = 0;
let _ = sys::igDockBuilderSplitNode(
node_id.into(),
split_dir.into(),
size_ratio_for_node_at_dir,
&mut id_at_dir,
&mut id_at_opposite,
);
(Id::from(id_at_dir), Id::from(id_at_opposite))
}
}
#[doc(alias = "DockBuilderDockWindow")]
pub fn dock_window(window_name: &str, node_id: Id) {
let ctx = unsafe { sys::igGetCurrentContext() };
assert!(
!ctx.is_null(),
"DockBuilder::dock_window() requires a current ImGui context"
);
let window_name_ptr = crate::string::tls_scratch_txt(window_name);
unsafe { sys::igDockBuilderDockWindow(window_name_ptr, node_id.into()) }
}
#[doc(alias = "DockBuilderCopyDockSpace")]
pub fn copy_dock_space(src_dockspace_id: Id, dst_dockspace_id: Id) {
assert_existing_dock_node("DockBuilder::copy_dock_space()", src_dockspace_id);
assert_nonzero_id(
"DockBuilder::copy_dock_space()",
"dst_dockspace_id",
dst_dockspace_id,
);
let mut empty_remaps = sys::ImVector_const_charPtr::default();
unsafe {
sys::igDockBuilderCopyDockSpace(
src_dockspace_id.into(),
dst_dockspace_id.into(),
&mut empty_remaps,
)
}
}
#[doc(alias = "DockBuilderCopyNode")]
pub fn copy_node(src_node_id: Id, dst_node_id: Id) {
assert_existing_dock_node("DockBuilder::copy_node()", src_node_id);
assert_nonzero_id("DockBuilder::copy_node()", "dst_node_id", dst_node_id);
let mut out = sys::ImVector_ImGuiID::default();
unsafe {
sys::igDockBuilderCopyNode(src_node_id.into(), dst_node_id.into(), &mut out);
free_imgui_id_vector(&mut out);
}
}
#[doc(alias = "DockBuilderCopyWindowSettings")]
pub fn copy_window_settings(src_name: &str, dst_name: &str) {
let (src_ptr, dst_ptr) = crate::string::tls_scratch_txt_two(src_name, dst_name);
unsafe { sys::igDockBuilderCopyWindowSettings(src_ptr, dst_ptr) }
}
#[doc(alias = "DockBuilderCopyDockSpace")]
pub fn copy_dock_space_with_window_remap(
src_dockspace_id: Id,
dst_dockspace_id: Id,
window_remaps: &[(&str, &str)],
) {
assert!(
window_remaps.len() <= (i32::MAX as usize) / 2,
"DockBuilder::copy_dock_space_with_window_remap() supports at most i32::MAX remap strings"
);
let mut cstrings: Vec<CString> = Vec::with_capacity(window_remaps.len() * 2);
for (src, dst) in window_remaps {
let src = CString::new(*src).unwrap_or_else(|_| {
panic!(
"DockBuilder::copy_dock_space_with_window_remap() source window name contains an interior NUL byte"
)
});
let dst = CString::new(*dst).unwrap_or_else(|_| {
panic!(
"DockBuilder::copy_dock_space_with_window_remap() destination window name contains an interior NUL byte"
)
});
cstrings.push(src);
cstrings.push(dst);
}
if cstrings.is_empty() {
Self::copy_dock_space(src_dockspace_id, dst_dockspace_id);
return;
}
assert_existing_dock_node(
"DockBuilder::copy_dock_space_with_window_remap()",
src_dockspace_id,
);
assert_nonzero_id(
"DockBuilder::copy_dock_space_with_window_remap()",
"dst_dockspace_id",
dst_dockspace_id,
);
let ptrs: Vec<*const c_char> = cstrings.iter().map(|s| s.as_ptr()).collect();
let mut boxed: Box<[*const c_char]> = ptrs.into_boxed_slice();
let boxed_len_i32 = len_i32(
"DockBuilder::copy_dock_space_with_window_remap()",
"remap strings",
boxed.len(),
);
let mut vec_in = sys::ImVector_const_charPtr {
Size: boxed_len_i32,
Capacity: boxed_len_i32,
Data: boxed.as_mut_ptr(),
};
unsafe {
sys::igDockBuilderCopyDockSpace(
src_dockspace_id.into(),
dst_dockspace_id.into(),
&mut vec_in,
);
}
drop(boxed);
drop(cstrings);
}
#[doc(alias = "DockBuilderCopyNode")]
pub fn copy_node_with_remap_out(src_node_id: Id, dst_node_id: Id) -> Vec<(Id, Id)> {
assert_existing_dock_node("DockBuilder::copy_node_with_remap_out()", src_node_id);
assert_nonzero_id(
"DockBuilder::copy_node_with_remap_out()",
"dst_node_id",
dst_node_id,
);
let mut out = sys::ImVector_ImGuiID::default();
unsafe {
sys::igDockBuilderCopyNode(src_node_id.into(), dst_node_id.into(), &mut out);
}
let mut result: Vec<(Id, Id)> = Vec::new();
unsafe {
if !out.Data.is_null() {
if out.Size > 0 {
let len = match usize::try_from(out.Size) {
Ok(len) => len,
Err(_) => {
free_imgui_id_vector(&mut out);
return result;
}
};
let slice_ids = slice::from_raw_parts(out.Data, len);
for pair in slice_ids.chunks_exact(2) {
result.push((Id::from(pair[0]), Id::from(pair[1])));
}
}
free_imgui_id_vector(&mut out);
}
}
result
}
#[doc(alias = "DockBuilderFinish")]
pub fn finish(node_id: Id) {
unsafe { sys::igDockBuilderFinish(node_id.into()) }
}
}
#[cfg(test)]
mod tests {
use super::DockBuilder;
use crate::Id;
#[test]
fn copy_dock_space_with_window_remap_rejects_interior_nul_names() {
assert!(
std::panic::catch_unwind(|| {
DockBuilder::copy_dock_space_with_window_remap(
Id::from(1),
Id::from(2),
&[("bad\0src", "dst")],
);
})
.is_err()
);
assert!(
std::panic::catch_unwind(|| {
DockBuilder::copy_dock_space_with_window_remap(
Id::from(1),
Id::from(2),
&[("src", "bad\0dst")],
);
})
.is_err()
);
}
}