@tool extends Control const BORDER_HOVER_WIDTH : float = 4 const MIN_DOCK_SIZE : float = 16 ## Set the layout data. Child Layout will be set base on the order of the current array @export var branches : DataBranch @export var border_margin : bool = true @export var enable : bool = false: set(v): if v != enable: enable = v if branches != null: branches.clear_cache() update_layout() var _resize : bool = false var _resize_start_press : Vector2 var _resize_branch : Branch var _resize_range : Vector2 var _resize_range_start : float func has_dock(node : Control) -> bool: var path_child := get_path_to(node) if path_child.is_empty() or branches == null: return false for branch in branches.data: if branch.child == path_child: return true return false ## Make sure the [node_child] path is on the dirrect child of this node. [to_parent] can be this node or dirrect child of this node func dock(node_child : Control, to_parent : Control, front : bool = false, split_ratio : float = 0.5, vertical : bool = false): var path_child := get_path_to(node_child) var path_parent := get_path_to(to_parent) if path_child.is_empty() or path_parent.is_empty(): return branches.data.push_back(Branch.create(node_child.name, path_parent, path_child, split_ratio, vertical)) if front: swap(node_child, to_parent) func undock(node_child : Control) -> bool: var child_path := get_path_to(node_child) if child_path.is_empty(): return false var new_parent_branch : int = -1 var count : int = branches.data.size() for i in range(count): # Find as branch child var branch : Branch = branches.data[i] if branch.child == child_path: new_parent_branch = i break if new_parent_branch == -1: return false var i : int = 0 count = branches.data.size() var found : bool = false while i < count: # Find as branch parent var branch : Branch = branches.data[i] if branch.parent == child_path: branches.data[new_parent_branch].child = branch.child branches.data.remove_at(i) count = branches.data.size() found = true else: i += 1 if not found: branches.data.remove_at(new_parent_branch) return true ## Swap dock position func swap(node_a : Control, node_b : Control): var path_a := get_path_to(node_a) var path_b := get_path_to(node_b) if path_a.is_empty() or path_b.is_empty(): return var count : int = branches.data.size() for i in range(count): var branch : Branch = branches.data[i] if branch.parent == path_a: branch.parent = path_b elif branch.parent == path_b: branch.parent = path_a if branch.child == path_a: branch.child = path_b elif branch.child == path_b: branch.child = path_a func update_layout(): if size.x * size.y == 0 or not enable or branches == null: return var count : int = branches.data.size() for i in range(count): _update_anchor_branch(branches.data[i]) for i in range(count): # Update offset if child border overlap with node border var branch : Branch = branches.data[i] var child : Control = get_node_or_null(branch.child) if get_node_or_null(branch.parent) == self: child.position = Vector2.ZERO if child != null: var start_offset : Vector2 var end_offset : Vector2 if border_margin: if child.position.x == 0: start_offset.x = BORDER_HOVER_WIDTH * 0.5 if child.position.y == 0: start_offset.y = BORDER_HOVER_WIDTH * 0.5 if child.get_rect().end.x == 0: end_offset.x = BORDER_HOVER_WIDTH * 0.5 if child.get_rect().end.y == 0: end_offset.y = BORDER_HOVER_WIDTH * 0.5 child.offset_left = BORDER_HOVER_WIDTH * 0.5 + start_offset.x child.offset_top = BORDER_HOVER_WIDTH * 0.5 + start_offset.y child.offset_right = BORDER_HOVER_WIDTH * -0.5 - end_offset.x child.offset_bottom = BORDER_HOVER_WIDTH * -0.5 - end_offset.y func _init(): item_rect_changed.connect(func(): if branches != null: branches.clear_cache() update_layout() ) func _ready(): branches.clear_cache() update_layout() func _input(event): if not get_window().has_focus() or branches == null: return var can_resize : bool = false var can_resize_rect : Rect2 if not _resize: for branch in branches.data: var parent : Control = get_node_or_null(branch.parent) var child : Control = get_node_or_null(branch.child) if parent != self and parent != null and child != null and branch.parent_size == 0 and branch.child_size == 0: var parent_rect : Rect2 = branch.parent_rect.grow(BORDER_HOVER_WIDTH) var child_rect : Rect2 = branch.child_rect.grow(BORDER_HOVER_WIDTH) if parent_rect.intersection(child_rect).has_point(get_local_mouse_position()): can_resize = true _resize_branch = branch _resize_range_start = branch.split_ratio can_resize_rect = parent_rect.intersection(child_rect) _resize_range = branch.parent_rect.size + branch.child_rect.size if can_resize_rect.size.x > can_resize_rect.size.y: _resize_range.x = 0 else: _resize_range.y = 0 break if can_resize or _resize: if event and event is InputEventMouseButton: if event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: _resize = true _resize_start_press = get_local_mouse_position() if not event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: _resize = false if _resize and event and event is InputEventMouseMotion: var offset : Vector2 = get_local_mouse_position() - _resize_start_press if _resize_range.x == 0: offset.y += _resize_range_start * _resize_range.y _resize_branch.split_ratio = offset.y / _resize_range.y elif _resize_range.y == 0: offset.x += _resize_range_start * _resize_range.x _resize_branch.split_ratio = offset.x / _resize_range.x if can_resize: if can_resize_rect.size.x > can_resize_rect.size.y: mouse_default_cursor_shape = Control.CURSOR_VSPLIT else: mouse_default_cursor_shape = Control.CURSOR_HSPLIT elif not _resize: mouse_default_cursor_shape = Control.CURSOR_ARROW func _exit_tree(): if branches == null: return for branch in branches.data: if branch.value_changed.is_connected(update_layout): branch.value_changed.disconnect(update_layout) func _update_anchor_branch(branch : Branch): var parent := get_node_or_null(branch.parent) var child := get_node_or_null(branch.child) if parent == null: return if not branch.value_changed.is_connected(update_layout): branch.value_changed.connect(update_layout) var valid_child : bool = child != null and child.visible if valid_child: child.offset_left = 0 child.offset_top = 0 child.offset_right = 0 child.offset_bottom = 0 if parent == self and valid_child: child.anchor_left = 0 child.anchor_top = 0 child.anchor_right = 1 child.anchor_bottom = 1 elif valid_child: var anchor_start : Vector2 = Vector2(parent.anchor_left, parent.anchor_top) var anchor_end : Vector2 = Vector2(parent.anchor_right, parent.anchor_bottom) if branch.vertical: parent.anchor_left = anchor_start.x parent.anchor_top = anchor_start.y parent.anchor_right = anchor_end.x child.anchor_left = anchor_start.x child.anchor_right = anchor_end.x child.anchor_bottom = anchor_end.y var anchor_range : float = anchor_end.y - anchor_start.y if branch.parent_size != 0: parent.anchor_bottom = anchor_start.y + (branch.parent_size / size.y) elif branch.child_size != 0: parent.anchor_bottom = anchor_end.y - (branch.child_size / size.y) elif branch.child_rect.size.y != 0: parent.anchor_bottom = anchor_end.y - (branch.child_rect.size.y / size.y) else: parent.anchor_bottom = anchor_start.y + (anchor_range * branch.split_ratio) parent.anchor_bottom = clampf(parent.anchor_bottom, anchor_start.y + MIN_DOCK_SIZE / size.y, anchor_end.y - MIN_DOCK_SIZE / size.y) child.anchor_top = parent.anchor_bottom branch.set_split_ratio((parent.anchor_bottom - parent.anchor_top) / anchor_range) else: parent.anchor_left = anchor_start.x parent.anchor_top = anchor_start.y parent.anchor_bottom = anchor_end.y child.anchor_top = anchor_start.y child.anchor_right = anchor_end.x child.anchor_bottom = anchor_end.y var anchor_range : float = anchor_end.x - anchor_start.x if branch.parent_size != 0: parent.anchor_right = anchor_start.x + (branch.parent_size / size.x) elif branch.child_size != 0: parent.anchor_right = anchor_end.x - (branch.child_size / size.x) elif branch.child_rect.size.x != 0: parent.anchor_right = anchor_end.x - (branch.child_rect.size.x / size.x) else: parent.anchor_right = anchor_start.x + (anchor_range * branch.split_ratio) parent.anchor_right = clampf(parent.anchor_right, anchor_start.x + MIN_DOCK_SIZE / size.y, anchor_end.x - MIN_DOCK_SIZE / size.y) child.anchor_left = parent.anchor_right branch.set_split_ratio((parent.anchor_right- parent.anchor_left) / anchor_range) branch.parent_rect = parent.get_rect() branch.child_rect = child.get_rect()