Add a behavior tree to the enemy
Add the Beehave addon. Make the enemy do the same thing as before but with a behavior tree.
This commit is contained in:
parent
09f6925a00
commit
1aed988149
92 changed files with 4025 additions and 25 deletions
91
addons/beehave/debug/debugger.gd
Normal file
91
addons/beehave/debug/debugger.gd
Normal file
|
@ -0,0 +1,91 @@
|
|||
@tool
|
||||
extends EditorDebuggerPlugin
|
||||
|
||||
const DebuggerTab := preload("debugger_tab.gd")
|
||||
var debugger_tab := DebuggerTab.new()
|
||||
var floating_window: Window
|
||||
var session: EditorDebuggerSession
|
||||
|
||||
|
||||
func _has_capture(prefix: String) -> bool:
|
||||
return prefix == "beehave"
|
||||
|
||||
|
||||
func _capture(message: String, data: Array, session_id: int) -> bool:
|
||||
# in case the behavior tree has invalid setup this might be null
|
||||
if debugger_tab == null:
|
||||
return false
|
||||
|
||||
if message == "beehave:register_tree":
|
||||
debugger_tab.register_tree(data[0])
|
||||
return true
|
||||
if message == "beehave:unregister_tree":
|
||||
debugger_tab.unregister_tree(data[0])
|
||||
return true
|
||||
if message == "beehave:process_tick":
|
||||
debugger_tab.graph.process_tick(data[0], data[1])
|
||||
return true
|
||||
if message == "beehave:process_begin":
|
||||
debugger_tab.graph.process_begin(data[0])
|
||||
return true
|
||||
if message == "beehave:process_end":
|
||||
debugger_tab.graph.process_end(data[0])
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _setup_session(session_id: int) -> void:
|
||||
session = get_session(session_id)
|
||||
session.started.connect(debugger_tab.start)
|
||||
session.stopped.connect(debugger_tab.stop)
|
||||
|
||||
debugger_tab.name = "🐝 Beehave"
|
||||
debugger_tab.make_floating.connect(_on_make_floating)
|
||||
debugger_tab.session = session
|
||||
session.add_session_tab(debugger_tab)
|
||||
|
||||
|
||||
func _on_make_floating() -> void:
|
||||
var plugin := BeehaveUtils.get_plugin()
|
||||
if not plugin:
|
||||
return
|
||||
if floating_window:
|
||||
_on_window_close_requested()
|
||||
return
|
||||
|
||||
var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale()
|
||||
var editor_interface: EditorInterface = plugin.get_editor_interface()
|
||||
var editor_main_screen = editor_interface.get_editor_main_screen()
|
||||
debugger_tab.get_parent().remove_child(debugger_tab)
|
||||
|
||||
floating_window = Window.new()
|
||||
|
||||
var panel := Panel.new()
|
||||
panel.add_theme_stylebox_override("panel", editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles"))
|
||||
panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
floating_window.add_child(panel)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_child(debugger_tab)
|
||||
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
margin.add_theme_constant_override("margin_right", border_size.x)
|
||||
margin.add_theme_constant_override("margin_left", border_size.x)
|
||||
margin.add_theme_constant_override("margin_top", border_size.y)
|
||||
margin.add_theme_constant_override("margin_bottom", border_size.y)
|
||||
panel.add_child(margin)
|
||||
|
||||
floating_window.title = "🐝 Beehave"
|
||||
floating_window.wrap_controls = true
|
||||
floating_window.min_size = Vector2i(600, 350)
|
||||
floating_window.size = debugger_tab.size
|
||||
floating_window.position = editor_main_screen.global_position
|
||||
floating_window.transient = true
|
||||
floating_window.close_requested.connect(_on_window_close_requested)
|
||||
editor_interface.get_base_control().add_child(floating_window)
|
||||
|
||||
|
||||
func _on_window_close_requested() -> void:
|
||||
debugger_tab.get_parent().remove_child(debugger_tab)
|
||||
session.add_session_tab(debugger_tab)
|
||||
floating_window.queue_free()
|
||||
floating_window = null
|
31
addons/beehave/debug/debugger_messages.gd
Normal file
31
addons/beehave/debug/debugger_messages.gd
Normal file
|
@ -0,0 +1,31 @@
|
|||
class_name BeehaveDebuggerMessages
|
||||
|
||||
|
||||
static func can_send_message() -> bool:
|
||||
return not Engine.is_editor_hint() and OS.has_feature("editor")
|
||||
|
||||
|
||||
static func register_tree(beehave_tree: Dictionary) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:register_tree", [beehave_tree])
|
||||
|
||||
|
||||
static func unregister_tree(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:unregister_tree", [instance_id])
|
||||
|
||||
|
||||
static func process_tick(instance_id: int, status: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_tick", [instance_id, status])
|
||||
|
||||
|
||||
static func process_begin(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_begin", [instance_id])
|
||||
|
||||
|
||||
static func process_end(instance_id: int) -> void:
|
||||
if can_send_message():
|
||||
EngineDebugger.send_message("beehave:process_end", [instance_id])
|
||||
|
107
addons/beehave/debug/debugger_tab.gd
Normal file
107
addons/beehave/debug/debugger_tab.gd
Normal file
|
@ -0,0 +1,107 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
signal make_floating()
|
||||
|
||||
const BeehaveGraphEdit := preload("graph_edit.gd")
|
||||
const TREE_ICON := preload("../icons/tree.svg")
|
||||
|
||||
var container: HSplitContainer
|
||||
var item_list: ItemList
|
||||
var graph: BeehaveGraphEdit
|
||||
var message: Label
|
||||
|
||||
var active_trees: Dictionary
|
||||
var active_tree_id: int = -1
|
||||
var session: EditorDebuggerSession
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
container = HSplitContainer.new()
|
||||
add_child(container)
|
||||
|
||||
item_list = ItemList.new()
|
||||
item_list.custom_minimum_size = Vector2(200, 0)
|
||||
item_list.item_selected.connect(_on_item_selected)
|
||||
container.add_child(item_list)
|
||||
|
||||
graph = BeehaveGraphEdit.new()
|
||||
container.add_child(graph)
|
||||
|
||||
message = Label.new()
|
||||
message.text = "Run Project for debugging"
|
||||
message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
message.set_anchors_preset(Control.PRESET_CENTER)
|
||||
add_child(message)
|
||||
|
||||
var button := Button.new()
|
||||
button.flat = true
|
||||
button.icon = get_theme_icon(&"ExternalLink", &"EditorIcons")
|
||||
button.pressed.connect(func(): make_floating.emit())
|
||||
button.tooltip_text = "Make floating"
|
||||
button.focus_mode = Control.FOCUS_NONE
|
||||
graph.get_zoom_hbox().add_child(button)
|
||||
|
||||
var toggle_button := Button.new()
|
||||
toggle_button.flat = true
|
||||
toggle_button.icon = get_theme_icon(&"Back", &"EditorIcons")
|
||||
toggle_button.pressed.connect(_on_toggle_button_pressed.bind(toggle_button))
|
||||
toggle_button.tooltip_text = "Toggle Panel"
|
||||
toggle_button.focus_mode = Control.FOCUS_NONE
|
||||
graph.get_zoom_hbox().add_child(toggle_button)
|
||||
graph.get_zoom_hbox().move_child(toggle_button, 0)
|
||||
|
||||
stop()
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
|
||||
func start() -> void:
|
||||
container.visible = true
|
||||
message.visible = false
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
container.visible = false
|
||||
message.visible = true
|
||||
|
||||
active_trees.clear()
|
||||
item_list.clear()
|
||||
graph.beehave_tree = {}
|
||||
|
||||
|
||||
func register_tree(data: Dictionary) -> void:
|
||||
var idx := item_list.add_item(data.name, TREE_ICON)
|
||||
item_list.set_item_tooltip(idx, data.path)
|
||||
item_list.set_item_metadata(idx, data.id)
|
||||
active_trees[data.id] = data
|
||||
|
||||
|
||||
func unregister_tree(instance_id: int) -> void:
|
||||
var id := str(instance_id)
|
||||
for i in item_list.item_count:
|
||||
if item_list.get_item_metadata(i) == id:
|
||||
item_list.remove_item(i)
|
||||
break
|
||||
|
||||
active_trees.erase(id)
|
||||
|
||||
if graph.beehave_tree.get("id", "") == id:
|
||||
graph.beehave_tree = {}
|
||||
|
||||
|
||||
func _on_toggle_button_pressed(toggle_button: Button) -> void:
|
||||
item_list.visible = !item_list.visible
|
||||
toggle_button.icon = get_theme_icon(&"Back" if item_list.visible else &"Forward", &"EditorIcons")
|
||||
|
||||
|
||||
func _on_item_selected(idx: int) -> void:
|
||||
var id: StringName = item_list.get_item_metadata(idx)
|
||||
graph.beehave_tree = active_trees.get(id, {})
|
||||
|
||||
active_tree_id = id.to_int()
|
||||
session.send_message("beehave:activate_tree", [active_tree_id])
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
session.send_message("beehave:visibility_changed", [visible and is_visible_in_tree()])
|
33
addons/beehave/debug/frames.gd
Normal file
33
addons/beehave/debug/frames.gd
Normal file
|
@ -0,0 +1,33 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
const SUCCESS_COLOR := Color("#009944c8")
|
||||
const NORMAL_COLOR := Color("#15181e")
|
||||
const FAILURE_COLOR := Color("#cf000f80")
|
||||
const RUNNING_COLOR := Color("#ffcc00c8")
|
||||
|
||||
var empty: StyleBoxEmpty
|
||||
var normal: StyleBoxFlat
|
||||
var success: StyleBoxFlat
|
||||
var failure: StyleBoxFlat
|
||||
var running: StyleBoxFlat
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var plugin := BeehaveUtils.get_plugin()
|
||||
if not plugin:
|
||||
return
|
||||
|
||||
var editor_scale := BeehaveUtils.get_editor_scale()
|
||||
|
||||
empty = StyleBoxEmpty.new()
|
||||
|
||||
normal = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"frame", &"GraphNode").duplicate()
|
||||
|
||||
success = plugin.get_editor_interface().get_base_control().get_theme_stylebox(&"selected_frame", &"GraphNode").duplicate()
|
||||
failure = success.duplicate()
|
||||
running = success.duplicate()
|
||||
|
||||
success.border_color = SUCCESS_COLOR
|
||||
failure.border_color = FAILURE_COLOR
|
||||
running.border_color = RUNNING_COLOR
|
38
addons/beehave/debug/global_debugger.gd
Normal file
38
addons/beehave/debug/global_debugger.gd
Normal file
|
@ -0,0 +1,38 @@
|
|||
extends Node
|
||||
|
||||
var _registered_trees: Dictionary
|
||||
var _active_tree: BeehaveTree
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
EngineDebugger.register_message_capture("beehave", _on_debug_message)
|
||||
|
||||
|
||||
func _on_debug_message(message: String, data: Array) -> bool:
|
||||
if message == "activate_tree":
|
||||
_set_active_tree(data[0])
|
||||
return true
|
||||
if message == "visibility_changed":
|
||||
if _active_tree:
|
||||
_active_tree._can_send_message = data[0]
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _set_active_tree(tree_id: int) -> void:
|
||||
var tree: BeehaveTree = _registered_trees.get(tree_id, null)
|
||||
if not tree:
|
||||
return
|
||||
|
||||
if _active_tree:
|
||||
_active_tree._can_send_message = false
|
||||
_active_tree = tree
|
||||
_active_tree._can_send_message = true
|
||||
|
||||
|
||||
func register_tree(tree: BeehaveTree) -> void:
|
||||
_registered_trees[tree.get_instance_id()] = tree
|
||||
|
||||
|
||||
func unregister_tree(tree: BeehaveTree) -> void:
|
||||
_registered_trees.erase(tree.get_instance_id())
|
259
addons/beehave/debug/graph_edit.gd
Normal file
259
addons/beehave/debug/graph_edit.gd
Normal file
|
@ -0,0 +1,259 @@
|
|||
@tool
|
||||
extends GraphEdit
|
||||
|
||||
const BeehaveGraphNode := preload("graph_node.gd")
|
||||
|
||||
const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg")
|
||||
const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg")
|
||||
|
||||
const PROGRESS_SHIFT: int = 50
|
||||
const INACTIVE_COLOR: Color = Color("#898989aa")
|
||||
const ACTIVE_COLOR: Color = Color("#ffcc00c8")
|
||||
const SUCCESS_COLOR: Color = Color("#009944c8")
|
||||
|
||||
|
||||
var updating_graph: bool = false
|
||||
var arraging_nodes: bool = false
|
||||
var beehave_tree: Dictionary:
|
||||
set(value):
|
||||
if beehave_tree == value:
|
||||
return
|
||||
beehave_tree = value
|
||||
active_nodes.clear()
|
||||
_update_graph()
|
||||
|
||||
var horizontal_layout: bool = false:
|
||||
set(value):
|
||||
if updating_graph or arraging_nodes:
|
||||
return
|
||||
if horizontal_layout == value:
|
||||
return
|
||||
horizontal_layout = value
|
||||
_update_layout_button()
|
||||
_update_graph()
|
||||
|
||||
|
||||
var active_nodes: Array[String]
|
||||
var progress: int = 0
|
||||
var layout_button: Button
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(100, 300)
|
||||
arrange_nodes_button_hidden = true
|
||||
minimap_enabled = false
|
||||
|
||||
layout_button = Button.new()
|
||||
layout_button.flat = true
|
||||
layout_button.focus_mode = Control.FOCUS_NONE
|
||||
layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout)
|
||||
get_zoom_hbox().add_child(layout_button)
|
||||
_update_layout_button()
|
||||
|
||||
|
||||
func _update_graph() -> void:
|
||||
if updating_graph:
|
||||
return
|
||||
|
||||
updating_graph = true
|
||||
|
||||
clear_connections()
|
||||
|
||||
for child in get_children():
|
||||
remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
if not beehave_tree.is_empty():
|
||||
_add_nodes(beehave_tree)
|
||||
_connect_nodes(beehave_tree)
|
||||
_arrange_nodes.call_deferred(beehave_tree)
|
||||
|
||||
updating_graph = false
|
||||
|
||||
|
||||
func _add_nodes(node: Dictionary) -> void:
|
||||
if node.is_empty():
|
||||
return
|
||||
var gnode := BeehaveGraphNode.new(horizontal_layout)
|
||||
add_child(gnode)
|
||||
gnode.title_text = node.name
|
||||
gnode.name = node.id
|
||||
gnode.icon = _get_icon(node.type.back())
|
||||
|
||||
if node.type.has(&"BeehaveTree"):
|
||||
gnode.set_slots(false, true)
|
||||
elif node.type.has(&"Leaf"):
|
||||
gnode.set_slots(true, false)
|
||||
elif node.type.has(&"Composite") or node.type.has(&"Decorator"):
|
||||
gnode.set_slots(true, true)
|
||||
|
||||
for child in node.get("children", []):
|
||||
_add_nodes(child)
|
||||
|
||||
|
||||
func _connect_nodes(node: Dictionary) -> void:
|
||||
for child in node.get("children", []):
|
||||
connect_node(node.id, 0, child.id, 0)
|
||||
_connect_nodes(child)
|
||||
|
||||
|
||||
func _arrange_nodes(node: Dictionary) -> void:
|
||||
if arraging_nodes:
|
||||
return
|
||||
|
||||
arraging_nodes = true
|
||||
|
||||
var tree_node := _create_tree_nodes(node)
|
||||
tree_node.update_positions(horizontal_layout)
|
||||
_place_nodes(tree_node)
|
||||
|
||||
arraging_nodes = false
|
||||
|
||||
|
||||
func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode:
|
||||
var tree_node := TreeNode.new(get_node(node.id), root)
|
||||
for child in node.get("children", []):
|
||||
var child_node := _create_tree_nodes(child, tree_node)
|
||||
tree_node.children.push_back(child_node)
|
||||
return tree_node
|
||||
|
||||
|
||||
func _place_nodes(node: TreeNode) -> void:
|
||||
node.item.position_offset = Vector2(node.x, node.y)
|
||||
for child in node.children:
|
||||
_place_nodes(child)
|
||||
|
||||
|
||||
func _get_icon(type: StringName) -> Texture2D:
|
||||
var classes := ProjectSettings.get_global_class_list()
|
||||
for c in classes:
|
||||
if c["class"] == type:
|
||||
var icon_path := c.get("icon", String())
|
||||
if not icon_path.is_empty():
|
||||
return load(icon_path)
|
||||
return null
|
||||
|
||||
|
||||
func get_status(status: int) -> String:
|
||||
if status == 0:
|
||||
return "SUCCESS"
|
||||
elif status == 1:
|
||||
return "FAILURE"
|
||||
return "RUNNING"
|
||||
|
||||
|
||||
func process_begin(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in get_children():
|
||||
child.set_meta("status", -1)
|
||||
|
||||
|
||||
func process_tick(instance_id: int, status: int) -> void:
|
||||
var node := get_node_or_null(str(instance_id))
|
||||
if node:
|
||||
node.text = "Status: %s" % get_status(status)
|
||||
node.set_status(status)
|
||||
node.set_meta("status", status)
|
||||
if status == 0 or status == 2:
|
||||
if not active_nodes.has(node.name):
|
||||
active_nodes.push_back(node.name)
|
||||
|
||||
|
||||
func process_end(instance_id: int) -> void:
|
||||
if not _is_same_tree(instance_id):
|
||||
return
|
||||
|
||||
for child in get_children():
|
||||
var status := child.get_meta("status", -1)
|
||||
match status:
|
||||
0:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(SUCCESS_COLOR)
|
||||
1:
|
||||
active_nodes.erase(child.name)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
2:
|
||||
child.set_color(ACTIVE_COLOR)
|
||||
_:
|
||||
child.text = " "
|
||||
child.set_status(status)
|
||||
child.set_color(INACTIVE_COLOR)
|
||||
|
||||
|
||||
func _is_same_tree(instance_id: int) -> bool:
|
||||
return str(instance_id) == beehave_tree.get("id", "")
|
||||
|
||||
|
||||
func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array:
|
||||
var points: PackedVector2Array
|
||||
|
||||
from_position = from_position.round()
|
||||
to_position = to_position.round()
|
||||
|
||||
points.push_back(from_position)
|
||||
|
||||
var mid_position := ((to_position + from_position) / 2).round()
|
||||
if horizontal_layout:
|
||||
points.push_back(Vector2(mid_position.x, from_position.y))
|
||||
points.push_back(Vector2(mid_position.x, to_position.y))
|
||||
else:
|
||||
points.push_back(Vector2(from_position.x, mid_position.y))
|
||||
points.push_back(Vector2(to_position.x, mid_position.y))
|
||||
|
||||
points.push_back(to_position)
|
||||
|
||||
return points
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not active_nodes.is_empty():
|
||||
progress += 10 if delta >= 0.05 else 1
|
||||
if progress >= 1000:
|
||||
progress = 0
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if active_nodes.is_empty():
|
||||
return
|
||||
|
||||
var circle_size: float = max(3, 6 * zoom)
|
||||
var progress_shift: float = PROGRESS_SHIFT * zoom
|
||||
|
||||
var connections := get_connection_list()
|
||||
for c in connections:
|
||||
if not c.from in active_nodes or not c.to in active_nodes:
|
||||
continue
|
||||
var from := get_node(String(c.from))
|
||||
var to := get_node(String(c.to))
|
||||
|
||||
if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0:
|
||||
return
|
||||
|
||||
var line := _get_connection_line(from.position + from.get_connection_output_position(c.from_port), to.position + to.get_connection_input_position(c.to_port))
|
||||
|
||||
var curve = Curve2D.new()
|
||||
for l in line:
|
||||
curve.add_point(l)
|
||||
|
||||
var max_steps := int(curve.get_baked_length())
|
||||
var current_shift := progress % max_steps
|
||||
var p := curve.sample_baked(current_shift)
|
||||
draw_circle(p, circle_size, ACTIVE_COLOR)
|
||||
|
||||
var shift := current_shift - progress_shift
|
||||
while shift >= 0:
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift -= progress_shift
|
||||
|
||||
shift = current_shift + progress_shift
|
||||
while shift <= curve.get_baked_length():
|
||||
draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR)
|
||||
shift += progress_shift
|
||||
|
||||
|
||||
func _update_layout_button() -> void:
|
||||
layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON
|
||||
layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout"
|
145
addons/beehave/debug/graph_node.gd
Normal file
145
addons/beehave/debug/graph_node.gd
Normal file
|
@ -0,0 +1,145 @@
|
|||
@tool
|
||||
extends GraphNode
|
||||
|
||||
const DEFAULT_COLOR := Color("#dad4cb")
|
||||
|
||||
const PORT_TOP_ICON := preload("icons/port_top.svg")
|
||||
const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg")
|
||||
const PORT_LEFT_ICON := preload("icons/port_left.svg")
|
||||
const PORT_RIGHT_ICON := preload("icons/port_right.svg")
|
||||
|
||||
|
||||
@export var title_text: String:
|
||||
set(value):
|
||||
title_text = value
|
||||
if title_label:
|
||||
title_label.text = value
|
||||
|
||||
@export var text: String:
|
||||
set(value):
|
||||
text = value
|
||||
if label:
|
||||
label.text = " " if text.is_empty() else text
|
||||
|
||||
@export var icon: Texture2D:
|
||||
set(value):
|
||||
icon = value
|
||||
if icon_rect:
|
||||
icon_rect.texture = value
|
||||
|
||||
var layout_size: float:
|
||||
get:
|
||||
return size.y if horizontal else size.x
|
||||
|
||||
|
||||
var panel: PanelContainer
|
||||
var icon_rect: TextureRect
|
||||
var title_label: Label
|
||||
var container: VBoxContainer
|
||||
var label: Label
|
||||
|
||||
var frames: RefCounted = BeehaveUtils.get_frames()
|
||||
var horizontal: bool = false
|
||||
|
||||
|
||||
func _init(horizontal: bool = false) -> void:
|
||||
self.horizontal = horizontal
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale()
|
||||
draggable = false
|
||||
|
||||
add_theme_stylebox_override("frame", frames.empty)
|
||||
add_theme_stylebox_override("selected_frame", frames.empty)
|
||||
add_theme_color_override("close_color", Color.TRANSPARENT)
|
||||
add_theme_icon_override("close", ImageTexture.new())
|
||||
|
||||
# For top port
|
||||
add_child(Control.new())
|
||||
|
||||
panel = PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
panel.add_theme_stylebox_override("panel", frames.normal)
|
||||
add_child(panel)
|
||||
|
||||
var vbox_container := VBoxContainer.new()
|
||||
panel.add_child(vbox_container)
|
||||
|
||||
var title_size := 24 * BeehaveUtils.get_editor_scale()
|
||||
var margin_container := MarginContainer.new()
|
||||
margin_container.add_theme_constant_override("margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale())
|
||||
margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
vbox_container.add_child(margin_container)
|
||||
|
||||
var title_container := HBoxContainer.new()
|
||||
title_container.add_child(Control.new())
|
||||
title_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
margin_container.add_child(title_container)
|
||||
|
||||
icon_rect = TextureRect.new()
|
||||
icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
title_container.add_child(icon_rect)
|
||||
|
||||
title_label = Label.new()
|
||||
title_label.add_theme_color_override("font_color", DEFAULT_COLOR)
|
||||
title_label.add_theme_font_override("font", get_theme_font("title_font"))
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_label.text = title_text
|
||||
title_container.add_child(title_label)
|
||||
|
||||
title_container.add_child(Control.new())
|
||||
|
||||
container = VBoxContainer.new()
|
||||
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
panel.add_child(container)
|
||||
|
||||
label = Label.new()
|
||||
label.text = " " if text.is_empty() else text
|
||||
container.add_child(label)
|
||||
|
||||
# For bottom port
|
||||
add_child(Control.new())
|
||||
|
||||
minimum_size_changed.connect(_on_size_changed)
|
||||
_on_size_changed.call_deferred()
|
||||
|
||||
|
||||
func set_status(status: int) -> void:
|
||||
panel.add_theme_stylebox_override("panel", _get_stylebox(status))
|
||||
|
||||
|
||||
func _get_stylebox(status: int) -> StyleBox:
|
||||
match status:
|
||||
0: return frames.success
|
||||
1: return frames.failure
|
||||
2: return frames.running
|
||||
_: return frames.normal
|
||||
|
||||
|
||||
func set_slots(left_enabled: bool, right_enabled: bool) -> void:
|
||||
if horizontal:
|
||||
set_slot(1, left_enabled, 0, Color.WHITE, right_enabled, 0, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON)
|
||||
else:
|
||||
set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null)
|
||||
set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON)
|
||||
|
||||
|
||||
func set_color(color: Color) -> void:
|
||||
set_input_color(color)
|
||||
set_output_color(color)
|
||||
|
||||
|
||||
func set_input_color(color: Color) -> void:
|
||||
set_slot_color_left(1 if horizontal else 0, color)
|
||||
|
||||
|
||||
func set_output_color(color: Color) -> void:
|
||||
set_slot_color_right(1 if horizontal else 2, color)
|
||||
|
||||
|
||||
func _on_size_changed():
|
||||
add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0))
|
1
addons/beehave/debug/icons/horizontal_layout.svg
Normal file
1
addons/beehave/debug/icons/horizontal_layout.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="matrix(0 -1 1 0 0 16)"><rect height="6" rx="1" stroke-width=".6" width="6" y="10"/><rect height="6" rx="1" stroke-width=".780723" width="6" x="5"/><rect height="6" rx="1" stroke-width=".780723" width="6" x="10" y="10"/><path d="m7 5h2v4h-2z" stroke-width=".768491"/><rect height="4" rx="1" ry="0" stroke-width=".768491" width="2" x="12" y="7"/><rect height="5" rx="1" stroke-width=".859" width="2" x="2" y="7"/><path d="m3 7h10v2h-10z" stroke-width="1.09113"/></g></svg>
|
After Width: | Height: | Size: 562 B |
37
addons/beehave/debug/icons/horizontal_layout.svg.import
Normal file
37
addons/beehave/debug/icons/horizontal_layout.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b4xw25mue1ktm"
|
||||
path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/horizontal_layout.svg"
|
||||
dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/beehave/debug/icons/port_bottom.svg
Normal file
1
addons/beehave/debug/icons/port_bottom.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m10 4a5 5 0 0 1 -2.5000001 4.3301271 5 5 0 0 1 -5-.0000002 5 5 0 0 1 -2.4999999-4.3301269" fill="#fff" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 222 B |
37
addons/beehave/debug/icons/port_bottom.svg.import
Normal file
37
addons/beehave/debug/icons/port_bottom.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bdivs6ajl6yo8"
|
||||
path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_bottom.svg"
|
||||
dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/beehave/debug/icons/port_left.svg
Normal file
1
addons/beehave/debug/icons/port_left.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m5 0a5 5 0 0 0 -4.33012712 2.5000001 5 5 0 0 0 .0000002 5 5 5 0 0 0 4.33012692 2.4999999" fill="#fff" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 221 B |
37
addons/beehave/debug/icons/port_left.svg.import
Normal file
37
addons/beehave/debug/icons/port_left.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cppwynbk7b67r"
|
||||
path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_left.svg"
|
||||
dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/beehave/debug/icons/port_right.svg
Normal file
1
addons/beehave/debug/icons/port_right.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m4.5 10a5 5 0 0 0 4.3301271-2.5000002 5 5 0 0 0 -.0000002-4.9999999 5 5 0 0 0 -4.3301269-2.4999999" fill="#fff" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 231 B |
37
addons/beehave/debug/icons/port_right.svg.import
Normal file
37
addons/beehave/debug/icons/port_right.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5tp0xrmqa42x"
|
||||
path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_right.svg"
|
||||
dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/beehave/debug/icons/port_top.svg
Normal file
1
addons/beehave/debug/icons/port_top.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m10-6a5 5 0 0 1 -2.5000001 4.3301271 5 5 0 0 1 -5-.0000002 5 5 0 0 1 -2.4999999-4.3301269" fill="#fff" fill-rule="evenodd" transform="scale(1 -1)"/></svg>
|
After Width: | Height: | Size: 246 B |
37
addons/beehave/debug/icons/port_top.svg.import
Normal file
37
addons/beehave/debug/icons/port_top.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bky8qt47hmp65"
|
||||
path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/port_top.svg"
|
||||
dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/beehave/debug/icons/vertical_layout.svg
Normal file
1
addons/beehave/debug/icons/vertical_layout.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><rect height="6" rx="1" stroke-width=".6" width="6" y="10"/><rect height="6" rx="1" stroke-width=".780723" width="6" x="5"/><rect height="6" rx="1" stroke-width=".780723" width="6" x="10" y="10"/><path d="m7 5h2v4h-2z" stroke-width=".768491"/><rect height="4" rx="1" ry="0" stroke-width=".768491" width="2" x="12" y="7"/><rect height="5" rx="1" stroke-width=".859" width="2" x="2" y="7"/><path d="m3 7h10v2h-10z" stroke-width="1.09113"/></g></svg>
|
After Width: | Height: | Size: 528 B |
37
addons/beehave/debug/icons/vertical_layout.svg.import
Normal file
37
addons/beehave/debug/icons/vertical_layout.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://clb3m3iitw3hw"
|
||||
path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/beehave/debug/icons/vertical_layout.svg"
|
||||
dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
255
addons/beehave/debug/tree_node.gd
Normal file
255
addons/beehave/debug/tree_node.gd
Normal file
|
@ -0,0 +1,255 @@
|
|||
class_name TreeNode
|
||||
extends RefCounted
|
||||
|
||||
# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/
|
||||
|
||||
const SIBLING_DISTANCE: float = 20.0
|
||||
const LEVEL_DISTANCE: float = 40.0
|
||||
|
||||
var x: float
|
||||
var y: float
|
||||
var mod: float
|
||||
var parent: TreeNode
|
||||
var children: Array[TreeNode]
|
||||
|
||||
var item: GraphNode
|
||||
|
||||
|
||||
func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void:
|
||||
parent = p_parent
|
||||
item = p_item
|
||||
|
||||
|
||||
func is_leaf() -> bool:
|
||||
return children.is_empty()
|
||||
|
||||
|
||||
func is_most_left() -> bool:
|
||||
if not parent:
|
||||
return true
|
||||
return parent.children.front() == self
|
||||
|
||||
|
||||
func is_most_right() -> bool:
|
||||
if not parent:
|
||||
return true
|
||||
return parent.children.back() == self
|
||||
|
||||
|
||||
func get_previous_sibling() -> TreeNode:
|
||||
if not parent or is_most_left():
|
||||
return null
|
||||
return parent.children[parent.children.find(self) - 1]
|
||||
|
||||
|
||||
func get_next_sibling() -> TreeNode:
|
||||
if not parent or is_most_right():
|
||||
return null
|
||||
return parent.children[parent.children.find(self) + 1]
|
||||
|
||||
|
||||
func get_most_left_sibling() -> TreeNode:
|
||||
if not parent:
|
||||
return null
|
||||
|
||||
if is_most_left():
|
||||
return self
|
||||
|
||||
return parent.children.front()
|
||||
|
||||
|
||||
func get_most_left_child() -> TreeNode:
|
||||
if children.is_empty():
|
||||
return null
|
||||
return children.front()
|
||||
|
||||
|
||||
func get_most_right_child() -> TreeNode:
|
||||
if children.is_empty():
|
||||
return null
|
||||
return children.back()
|
||||
|
||||
|
||||
func update_positions(horizontally: bool = false) -> void:
|
||||
_initialize_nodes(self, 0)
|
||||
_calculate_initial_x(self)
|
||||
|
||||
_check_all_children_on_screen(self)
|
||||
_calculate_final_positions(self, 0)
|
||||
|
||||
if horizontally:
|
||||
_swap_x_y(self)
|
||||
_calculate_x(self, 0)
|
||||
else:
|
||||
_calculate_y(self, 0)
|
||||
|
||||
|
||||
func _initialize_nodes(node: TreeNode, depth: int) -> void:
|
||||
node.x = -1
|
||||
node.y = depth
|
||||
node.mod = 0
|
||||
|
||||
for child in node.children:
|
||||
_initialize_nodes(child, depth + 1)
|
||||
|
||||
|
||||
func _calculate_initial_x(node: TreeNode) -> void:
|
||||
for child in node.children:
|
||||
_calculate_initial_x(child)
|
||||
if node.is_leaf():
|
||||
if not node.is_most_left():
|
||||
node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE
|
||||
else:
|
||||
node.x = 0
|
||||
else:
|
||||
var mid: float
|
||||
if node.children.size() == 1:
|
||||
var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2
|
||||
mid = node.children.front().x + offset
|
||||
else:
|
||||
var left_child := node.get_most_left_child()
|
||||
var right_child := node.get_most_right_child()
|
||||
mid = (left_child.x + right_child.x + right_child.item.layout_size - node.item.layout_size) / 2
|
||||
|
||||
if node.is_most_left():
|
||||
node.x = mid
|
||||
else:
|
||||
node.x = node.get_previous_sibling().x + node.get_previous_sibling().item.layout_size + SIBLING_DISTANCE
|
||||
node.mod = node.x - mid
|
||||
|
||||
if not node.is_leaf() and not node.is_most_left():
|
||||
_check_for_conflicts(node)
|
||||
|
||||
|
||||
func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void:
|
||||
node.x += mod_sum
|
||||
mod_sum += node.mod
|
||||
|
||||
for child in node.children:
|
||||
_calculate_final_positions(child, mod_sum)
|
||||
|
||||
|
||||
func _check_all_children_on_screen(node: TreeNode) -> void:
|
||||
var node_contour: Dictionary = {}
|
||||
_get_left_contour(node, 0, node_contour)
|
||||
|
||||
var shift_amount: float = 0
|
||||
for y in node_contour.keys():
|
||||
if node_contour[y] + shift_amount < 0:
|
||||
shift_amount = (node_contour[y] * -1)
|
||||
|
||||
if shift_amount > 0:
|
||||
node.x += shift_amount
|
||||
node.mod += shift_amount
|
||||
|
||||
|
||||
func _check_for_conflicts(node: TreeNode) -> void:
|
||||
var min_distance := SIBLING_DISTANCE
|
||||
var shift_value: float = 0
|
||||
var shift_sibling: TreeNode = null
|
||||
|
||||
var node_contour: Dictionary = {}# { int, float }
|
||||
_get_left_contour(node, 0, node_contour)
|
||||
|
||||
var sibling := node.get_most_left_sibling()
|
||||
while sibling != null and sibling != node:
|
||||
var sibling_contour: Dictionary = {}
|
||||
_get_right_contour(sibling, 0, sibling_contour)
|
||||
|
||||
for level in range(node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1):
|
||||
var distance: float = node_contour[level] - sibling_contour[level]
|
||||
if distance + shift_value < min_distance:
|
||||
shift_value = min_distance - distance
|
||||
shift_sibling = sibling
|
||||
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
if shift_value > 0:
|
||||
node.x += shift_value
|
||||
node.mod += shift_value
|
||||
_center_nodes_between(shift_sibling, node)
|
||||
|
||||
|
||||
func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void:
|
||||
var left_index := left_node.parent.children.find(left_node)
|
||||
var right_index := left_node.parent.children.find(right_node)
|
||||
|
||||
var num_nodes_between: int = (right_index - left_index) - 1
|
||||
if num_nodes_between > 0:
|
||||
# The extra distance that needs to be split into num_nodes_between + 1
|
||||
# in order to find the new node spacing so that nodes are equally spaced
|
||||
var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size
|
||||
# Subtract sizes on nodes in between
|
||||
for i in range(left_index + 1, right_index):
|
||||
distance_to_allocate -= left_node.parent.children[i].item.layout_size
|
||||
# Divide space equally
|
||||
var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1)
|
||||
|
||||
var prev_node := left_node
|
||||
var middle_node := left_node.get_next_sibling()
|
||||
while middle_node != right_node:
|
||||
var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes
|
||||
var offset := desire_x - middle_node.x
|
||||
middle_node.x += offset
|
||||
middle_node.mod += offset
|
||||
prev_node = middle_node
|
||||
middle_node = middle_node.get_next_sibling()
|
||||
|
||||
|
||||
func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
|
||||
var node_left: float = node.x + mod_sum
|
||||
var depth := int(node.y)
|
||||
if not values.has(depth):
|
||||
values[depth] = node_left
|
||||
else:
|
||||
values[depth] = min(values[depth], node_left)
|
||||
|
||||
mod_sum += node.mod
|
||||
for child in node.children:
|
||||
_get_left_contour(child, mod_sum, values)
|
||||
|
||||
|
||||
func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void:
|
||||
var node_right: float = node.x + mod_sum + node.item.layout_size
|
||||
var depth := int(node.y)
|
||||
if not values.has(depth):
|
||||
values[depth] = node_right
|
||||
else:
|
||||
values[depth] = max(values[depth], node_right)
|
||||
|
||||
mod_sum += node.mod
|
||||
for child in node.children:
|
||||
_get_right_contour(child, mod_sum, values)
|
||||
|
||||
|
||||
func _swap_x_y(node: TreeNode) -> void:
|
||||
for child in node.children:
|
||||
_swap_x_y(child)
|
||||
|
||||
var temp := node.x
|
||||
node.x = node.y
|
||||
node.y = temp
|
||||
|
||||
|
||||
func _calculate_x(node: TreeNode, offset: int) -> void:
|
||||
node.x = offset
|
||||
var sibling := node.get_most_left_sibling()
|
||||
var max_size: int = node.item.size.x
|
||||
while sibling != null:
|
||||
max_size = max(sibling.item.size.x, max_size)
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
for child in node.children:
|
||||
_calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
|
||||
|
||||
|
||||
func _calculate_y(node: TreeNode, offset: int) -> void:
|
||||
node.y = offset
|
||||
var sibling := node.get_most_left_sibling()
|
||||
var max_size: int = node.item.size.y
|
||||
while sibling != null:
|
||||
max_size = max(sibling.item.size.y, max_size)
|
||||
sibling = sibling.get_next_sibling()
|
||||
|
||||
for child in node.children:
|
||||
_calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale())
|
Loading…
Add table
Add a link
Reference in a new issue