Skip to content

Refactor layout node classes to be RefCounted instead of sub-resources #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
root = true

[*]
indent_style = spaces
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
Expand Down
6 changes: 6 additions & 0 deletions addons/dockable_container/dockable_container.gd
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ func _get_split(idx: int) -> SplitHandle:
return _split_container.get_child(idx)
var split := SplitHandle.new()
_split_container.add_child(split)
split.layout_changed.connect(_on_split_layout_changed)
return split


Expand Down Expand Up @@ -444,3 +445,8 @@ func _on_child_renamed(child: Node) -> void:
_children_names[child] = child.name
_children_names[child.name] = child
_layout.rename_node(old_name, child.name)


## Handler for `SplitHandle.layout_changed`, makes layout emit `changed` signal
func _on_split_layout_changed() -> void:
_layout.emit_changed()
5 changes: 2 additions & 3 deletions addons/dockable_container/dockable_panel.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@tool
extends TabContainer

signal tab_layout_changed(tab)
signal tab_layout_changed(tab: int)

var leaf: DockableLayoutPanel:
get:
Expand Down Expand Up @@ -80,7 +80,6 @@ func _on_tab_changed(tab: int) -> void:
var control := get_tab_control(tab)
if not control:
return
var tab_name := control.name
var name_index_in_leaf := _leaf.find_name(tab_name)
var name_index_in_leaf := _leaf.find_child(control)
if name_index_in_leaf != tab: # NOTE: this handles added tabs (index == -1)
tab_layout_changed.emit(tab)
31 changes: 23 additions & 8 deletions addons/dockable_container/layout.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,23 @@ extends Resource

enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }

@export var root: DockableLayoutNode = DockableLayoutPanel.new():
@export var serialized_data: Dictionary:
get:
return {
root = _root.to_dict(),
hidden_tabs = _hidden_tabs,
}
set(value):
_hidden_tabs = value.get("hidden_tabs", {})
var new_root = DockableLayoutNode.from_dict(value.get("root", {}))
set_root(new_root)

var root: DockableLayoutNode = DockableLayoutPanel.new():
get:
return _root
set(value):
set_root(value)
@export var hidden_tabs := {}:
var hidden_tabs := {}:
get:
return _hidden_tabs
set(value):
Expand All @@ -32,19 +43,17 @@ var _root: DockableLayoutNode = DockableLayoutPanel.new()


func _init() -> void:
resource_name = "Layout"
if not resource_name:
resource_name = "Layout"


func set_root(value: DockableLayoutNode, should_emit_changed := true) -> void:
if not value:
value = DockableLayoutPanel.new()
if _root == value:
return
if _root and _root.changed.is_connected(_on_root_changed):
_root.changed.disconnect(_on_root_changed)
_root = value
_root.parent = null
_root.changed.connect(_on_root_changed)
if should_emit_changed:
_on_root_changed()

Expand All @@ -54,7 +63,11 @@ func get_root() -> DockableLayoutNode:


func clone() -> DockableLayout:
return duplicate(true)
return duplicate()


func is_empty() -> bool:
return _root.is_empty()


func get_names() -> PackedStringArray:
Expand Down Expand Up @@ -203,7 +216,7 @@ func _ensure_names_in_node(


func _remove_leaf(leaf: DockableLayoutPanel) -> void:
assert(leaf.is_empty(), "FIXME: trying to remove_at a leaf with nodes")
assert(leaf.is_empty(), "FIXME: trying to remove a leaf with nodes")
if _root == leaf:
return
var collapsed_branch := leaf.parent
Expand All @@ -219,6 +232,8 @@ func _remove_leaf(leaf: DockableLayoutPanel) -> void:
root_branch.first = kept_branch
else:
root_branch.second = kept_branch
collapsed_branch.parent = null
leaf.parent = null


func _print_tree() -> void:
Expand Down
24 changes: 16 additions & 8 deletions addons/dockable_container/layout_node.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@tool
class_name DockableLayoutNode
extends Resource
extends RefCounted
## Base class for DockableLayout tree nodes

var parent: DockableLayoutSplit:
Expand All @@ -12,13 +12,6 @@ var parent: DockableLayoutSplit:
var _parent_ref := WeakRef.new()


func emit_tree_changed() -> void:
var node := self
while node:
node.emit_changed()
node = node.parent


## Returns whether there are any nodes
func is_empty() -> bool:
return true
Expand All @@ -27,3 +20,18 @@ func is_empty() -> bool:
## Returns all tab names in this node
func get_names() -> PackedStringArray:
return PackedStringArray()


## Serialize layout node to Dictionary
func to_dict() -> Dictionary:
return {}


## Deserialize Dictionary to layout node
static func from_dict(dict: Dictionary) -> DockableLayoutNode:
if dict.has("names"):
return DockableLayoutPanel.from_dict(dict)
elif dict.has_all(["first", "second"]):
return DockableLayoutSplit.from_dict(dict)
else:
return null
40 changes: 18 additions & 22 deletions addons/dockable_container/layout_panel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,36 @@ class_name DockableLayoutPanel
extends DockableLayoutNode
## DockableLayout leaf nodes, defining tabs

@export var names: PackedStringArray:
var names: PackedStringArray:
get:
return get_names()
set(value):
_names = value
emit_tree_changed()
@export var current_tab: int:
var current_tab: int:
get:
return int(clamp(_current_tab, 0, _names.size() - 1))
set(value):
if value != _current_tab:
_current_tab = value
emit_tree_changed()
_current_tab = int(clamp(value, 0, _names.size() - 1))

var _names := PackedStringArray()
var _current_tab := 0


func _init() -> void:
resource_name = "Tabs"


## Returns all tab names in this node
func get_names() -> PackedStringArray:
return _names


func push_name(name: String) -> void:
_names.append(name)
emit_tree_changed()


func insert_node(position: int, node: Node) -> void:
_names.insert(position, node.name)
emit_tree_changed()


func find_name(node_name: String) -> int:
for i in _names.size():
if _names[i] == node_name:
return i
return -1
return _names.find(node_name)


func find_child(node: Node) -> int:
Expand All @@ -55,7 +43,6 @@ func remove_node(node: Node) -> void:
var i := find_child(node)
if i >= 0:
_names.remove_at(i)
emit_tree_changed()
else:
push_warning("Remove failed, node '%s' was not found" % node)

Expand All @@ -64,7 +51,6 @@ func rename_node(previous_name: String, new_name: String) -> void:
var i := find_name(previous_name)
if i >= 0:
_names.set(i, new_name)
emit_tree_changed()
else:
push_warning("Rename failed, name '%s' was not found" % previous_name)

Expand All @@ -76,14 +62,24 @@ func is_empty() -> bool:

func update_nodes(node_names: PackedStringArray, data: Dictionary) -> void:
var i := 0
var removed_any := false
while i < _names.size():
var current := _names[i]
if not current in node_names or data.has(current):
_names.remove_at(i)
removed_any = true
else:
data[current] = self
i += 1
if removed_any:
emit_tree_changed()


func to_dict() -> Dictionary:
return {
names = _names.duplicate(),
current_tab = _current_tab,
}


static func from_dict(dict: Dictionary) -> DockableLayoutNode:
var panel := DockableLayoutPanel.new()
panel.names = dict["names"].duplicate()
panel.current_tab = dict.get("current_tab", 0)
return panel
34 changes: 22 additions & 12 deletions addons/dockable_container/layout_split.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ extends DockableLayoutNode

enum Direction { HORIZONTAL, VERTICAL }

@export var direction := Direction.HORIZONTAL:
var direction := Direction.HORIZONTAL:
get:
return get_direction()
set(value):
set_direction(value)
@export_range(0, 1) var percent := 0.5:
var percent := 0.5:
get = get_percent,
set = set_percent
@export var first: DockableLayoutNode = DockableLayoutPanel.new():
var first: DockableLayoutNode = DockableLayoutPanel.new():
get:
return get_first()
set(value):
set_first(value)
@export var second: DockableLayoutNode = DockableLayoutPanel.new():
var second: DockableLayoutNode = DockableLayoutPanel.new():
get:
return get_second()
set(value):
Expand All @@ -30,17 +30,12 @@ var _first: DockableLayoutNode
var _second: DockableLayoutNode


func _init() -> void:
resource_name = "Split"


func set_first(value: DockableLayoutNode) -> void:
if value == null:
_first = DockableLayoutPanel.new()
else:
_first = value
_first.parent = self
emit_tree_changed()


func get_first() -> DockableLayoutNode:
Expand All @@ -53,7 +48,6 @@ func set_second(value: DockableLayoutNode) -> void:
else:
_second = value
_second.parent = self
emit_tree_changed()


func get_second() -> DockableLayoutNode:
Expand All @@ -63,7 +57,6 @@ func get_second() -> DockableLayoutNode:
func set_direction(value: Direction) -> void:
if value != _direction:
_direction = value
emit_tree_changed()


func get_direction() -> Direction:
Expand All @@ -74,7 +67,6 @@ func set_percent(value: float) -> void:
var clamped_value := clampf(value, 0, 1)
if not is_equal_approx(_percent, clamped_value):
_percent = clamped_value
emit_tree_changed()


func get_percent() -> float:
Expand All @@ -98,3 +90,21 @@ func is_horizontal() -> bool:

func is_vertical() -> bool:
return _direction == Direction.VERTICAL


func to_dict() -> Dictionary:
return {
direction = direction,
percent = percent,
first = first.to_dict(),
second = second.to_dict(),
}


static func from_dict(dict: Dictionary) -> DockableLayoutNode:
var split = DockableLayoutSplit.new()
split.direction = dict.get("direction", Direction.HORIZONTAL)
split.percent = dict.get("percent", 0.5)
split.first = DockableLayoutNode.from_dict(dict["first"])
split.second = DockableLayoutNode.from_dict(dict["second"])
return split
6 changes: 4 additions & 2 deletions addons/dockable_container/samples/TestScene.gd
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ func _on_save_pressed() -> void:


func _on_load_pressed() -> void:
var res = load(SAVED_LAYOUT_PATH)
if res:
if not ResourceLoader.exists(SAVED_LAYOUT_PATH):
return
var res = load(SAVED_LAYOUT_PATH) as DockableLayout
if res and not res.is_empty():
_container.set_layout(res.clone())
else:
print("Error")
Expand Down
Loading