mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
534a7fd356
This way, Pixelorama remembers the changes users make to the UI, without them having to go to Window, Manage Layouts and clicking on Edit, which was an unintuitive and slow way. Needs testing because bugs may linger. This commit also adds a new `save_on_change` variable and a `save` method on layout.gd of the dockable_container plugin. Perhaps I should also add this upstream as well. I also need to add a way to create new layouts based off the default ones (which are permanently stored within Pixelorama's pck file and cannot be modified), so users will always have the option to resort to the default layouts.
258 lines
7 KiB
GDScript
258 lines
7 KiB
GDScript
@tool
|
|
class_name DockableLayout
|
|
extends Resource
|
|
## DockableLayout Resource definition, holding the root DockableLayoutNode and hidden tabs.
|
|
##
|
|
## DockableLayoutSplit are binary trees with nested DockableLayoutSplit subtrees
|
|
## and DockableLayoutPanel leaves. Both of them inherit from DockableLayoutNode to help with
|
|
## type annotation and define common functionality.
|
|
##
|
|
## Hidden tabs are marked in the `hidden_tabs` Dictionary by name.
|
|
|
|
enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
|
|
|
@export var root: DockableLayoutNode = DockableLayoutPanel.new():
|
|
get:
|
|
return _root
|
|
set(value):
|
|
set_root(value)
|
|
@export var hidden_tabs := {}:
|
|
get:
|
|
return _hidden_tabs
|
|
set(value):
|
|
if value != _hidden_tabs:
|
|
_hidden_tabs = value
|
|
changed.emit()
|
|
@export var save_on_change := false:
|
|
set(value):
|
|
save_on_change = value
|
|
if value:
|
|
if not changed.is_connected(save):
|
|
changed.connect(save)
|
|
else:
|
|
if changed.is_connected(save):
|
|
changed.disconnect(save)
|
|
|
|
var _changed_signal_queued := false
|
|
var _first_leaf: DockableLayoutPanel
|
|
var _hidden_tabs: Dictionary
|
|
var _leaf_by_node_name: Dictionary
|
|
var _root: DockableLayoutNode = DockableLayoutPanel.new()
|
|
|
|
|
|
func _init() -> void:
|
|
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()
|
|
|
|
|
|
func get_root() -> DockableLayoutNode:
|
|
return _root
|
|
|
|
|
|
func clone() -> DockableLayout:
|
|
return duplicate(true)
|
|
|
|
|
|
func get_names() -> PackedStringArray:
|
|
return _root.get_names()
|
|
|
|
|
|
func save(path := resource_path) -> void:
|
|
if path.is_empty():
|
|
return
|
|
ResourceSaver.save(self, path)
|
|
|
|
|
|
## Add missing nodes on first leaf and remove nodes outside indices from leaves.
|
|
##
|
|
## _leaf_by_node_name = {
|
|
## (string keys) = respective Leaf that holds the node name,
|
|
## }
|
|
func update_nodes(names: PackedStringArray) -> void:
|
|
_leaf_by_node_name.clear()
|
|
_first_leaf = null
|
|
var empty_leaves: Array[DockableLayoutPanel] = []
|
|
_ensure_names_in_node(_root, names, empty_leaves) # Changes _leaf_by_node_name and empty_leaves
|
|
for l in empty_leaves:
|
|
_remove_leaf(l)
|
|
if not _first_leaf:
|
|
_first_leaf = DockableLayoutPanel.new()
|
|
set_root(_first_leaf)
|
|
for n in names:
|
|
if not _leaf_by_node_name.has(n):
|
|
_first_leaf.push_name(n)
|
|
_leaf_by_node_name[n] = _first_leaf
|
|
_on_root_changed()
|
|
|
|
|
|
func move_node_to_leaf(node: Node, leaf: DockableLayoutPanel, relative_position: int) -> void:
|
|
var node_name := node.name
|
|
var previous_leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
|
if previous_leaf:
|
|
previous_leaf.remove_node(node)
|
|
if previous_leaf.is_empty():
|
|
_remove_leaf(previous_leaf)
|
|
|
|
leaf.insert_node(relative_position, node)
|
|
_leaf_by_node_name[node_name] = leaf
|
|
_on_root_changed()
|
|
|
|
|
|
func get_leaf_for_node(node: Node) -> DockableLayoutPanel:
|
|
return _leaf_by_node_name.get(node.name)
|
|
|
|
|
|
func split_leaf_with_node(leaf: DockableLayoutPanel, node: Node, margin: int) -> void:
|
|
var root_branch := leaf.parent
|
|
var new_leaf := DockableLayoutPanel.new()
|
|
var new_branch := DockableLayoutSplit.new()
|
|
if margin == MARGIN_LEFT or margin == MARGIN_RIGHT:
|
|
new_branch.direction = DockableLayoutSplit.Direction.HORIZONTAL
|
|
else:
|
|
new_branch.direction = DockableLayoutSplit.Direction.VERTICAL
|
|
if margin == MARGIN_LEFT or margin == MARGIN_TOP:
|
|
new_branch.first = new_leaf
|
|
new_branch.second = leaf
|
|
else:
|
|
new_branch.first = leaf
|
|
new_branch.second = new_leaf
|
|
if _root == leaf:
|
|
set_root(new_branch, false)
|
|
elif root_branch:
|
|
if leaf == root_branch.first:
|
|
root_branch.first = new_branch
|
|
else:
|
|
root_branch.second = new_branch
|
|
|
|
move_node_to_leaf(node, new_leaf, 0)
|
|
|
|
|
|
func add_node(node: Node) -> void:
|
|
var node_name := node.name
|
|
if _leaf_by_node_name.has(node_name):
|
|
return
|
|
_first_leaf.push_name(node_name)
|
|
_leaf_by_node_name[node_name] = _first_leaf
|
|
_on_root_changed()
|
|
|
|
|
|
func remove_node(node: Node) -> void:
|
|
var node_name := node.name
|
|
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
|
if not leaf:
|
|
return
|
|
leaf.remove_node(node)
|
|
_leaf_by_node_name.erase(node_name)
|
|
if leaf.is_empty():
|
|
_remove_leaf(leaf)
|
|
_on_root_changed()
|
|
|
|
|
|
func rename_node(previous_name: String, new_name: String) -> void:
|
|
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(previous_name)
|
|
if not leaf:
|
|
return
|
|
leaf.rename_node(previous_name, new_name)
|
|
_leaf_by_node_name.erase(previous_name)
|
|
_leaf_by_node_name[new_name] = leaf
|
|
_on_root_changed()
|
|
|
|
|
|
func set_tab_hidden(name: String, hidden: bool) -> void:
|
|
if not _leaf_by_node_name.has(name):
|
|
return
|
|
if hidden:
|
|
_hidden_tabs[name] = true
|
|
else:
|
|
_hidden_tabs.erase(name)
|
|
_on_root_changed()
|
|
|
|
|
|
func is_tab_hidden(name: String) -> bool:
|
|
return _hidden_tabs.get(name, false)
|
|
|
|
|
|
func set_node_hidden(node: Node, hidden: bool) -> void:
|
|
set_tab_hidden(node.name, hidden)
|
|
|
|
|
|
func is_node_hidden(node: Node) -> bool:
|
|
return is_tab_hidden(node.name)
|
|
|
|
|
|
func _on_root_changed() -> void:
|
|
if _changed_signal_queued:
|
|
return
|
|
_changed_signal_queued = true
|
|
set_deferred("_changed_signal_queued", false)
|
|
emit_changed.call_deferred()
|
|
|
|
|
|
func _ensure_names_in_node(
|
|
node: DockableLayoutNode, names: PackedStringArray, empty_leaves: Array[DockableLayoutPanel]
|
|
) -> void:
|
|
if node is DockableLayoutPanel:
|
|
node.update_nodes(names, _leaf_by_node_name) # This changes _leaf_by_node_name
|
|
if node.is_empty():
|
|
empty_leaves.append(node)
|
|
if not _first_leaf:
|
|
_first_leaf = node
|
|
elif node is DockableLayoutSplit:
|
|
_ensure_names_in_node(node.first, names, empty_leaves)
|
|
_ensure_names_in_node(node.second, names, empty_leaves)
|
|
else:
|
|
assert(false, "Invalid Resource, should be branch or leaf, found %s" % node)
|
|
|
|
|
|
func _remove_leaf(leaf: DockableLayoutPanel) -> void:
|
|
assert(leaf.is_empty(), "FIXME: trying to remove_at a leaf with nodes")
|
|
if _root == leaf:
|
|
return
|
|
var collapsed_branch := leaf.parent
|
|
assert(collapsed_branch is DockableLayoutSplit, "FIXME: leaf is not a child of branch")
|
|
var kept_branch: DockableLayoutNode = (
|
|
collapsed_branch.first if leaf == collapsed_branch.second else collapsed_branch.second
|
|
)
|
|
var root_branch := collapsed_branch.parent #HERE
|
|
if collapsed_branch == _root:
|
|
set_root(kept_branch, true)
|
|
elif root_branch:
|
|
if collapsed_branch == root_branch.first:
|
|
root_branch.first = kept_branch
|
|
else:
|
|
root_branch.second = kept_branch
|
|
|
|
|
|
func _print_tree() -> void:
|
|
print("TREE")
|
|
_print_tree_step(_root, 0, 0)
|
|
print("")
|
|
|
|
|
|
func _print_tree_step(tree_or_leaf: DockableLayoutNode, level: int, idx: int) -> void:
|
|
if tree_or_leaf is DockableLayoutPanel:
|
|
print(" |".repeat(level), "- (%d) = " % idx, tree_or_leaf.names)
|
|
elif tree_or_leaf is DockableLayoutSplit:
|
|
print(
|
|
" |".repeat(level),
|
|
"-+ (%d) = " % idx,
|
|
tree_or_leaf.direction,
|
|
" ",
|
|
tree_or_leaf.percent
|
|
)
|
|
_print_tree_step(tree_or_leaf.first, level + 1, 1)
|
|
_print_tree_step(tree_or_leaf.second, level + 1, 2)
|