From 42984ebd11fbd9784b8c816abd05df3ed6088e7b Mon Sep 17 00:00:00 2001 From: Snaiel <56014779+Snaiel@users.noreply.github.com> Date: Thu, 27 Jun 2024 03:55:29 +0800 Subject: [PATCH] Fix 4.2 Debugger Visuals (#345) * fix debugger visuals * additional cleanup * check font and queue_free titlebar child fixes failed debugger_test.gd case and orphans --- addons/beehave/debug/debugger_tab.gd | 11 +- addons/beehave/debug/new_frames.gd | 69 ++++ addons/beehave/debug/new_graph_edit.gd | 296 ++++++++++++++++++ addons/beehave/debug/new_graph_node.gd | 155 +++++++++ .../debug/{frames.gd => old_frames.gd} | 0 .../{graph_edit.gd => old_graph_edit.gd} | 41 +-- .../{graph_node.gd => old_graph_node.gd} | 0 addons/beehave/plugin.gd | 5 +- 8 files changed, 541 insertions(+), 36 deletions(-) create mode 100644 addons/beehave/debug/new_frames.gd create mode 100644 addons/beehave/debug/new_graph_edit.gd create mode 100644 addons/beehave/debug/new_graph_node.gd rename addons/beehave/debug/{frames.gd => old_frames.gd} (100%) rename addons/beehave/debug/{graph_edit.gd => old_graph_edit.gd} (88%) rename addons/beehave/debug/{graph_node.gd => old_graph_node.gd} (100%) diff --git a/addons/beehave/debug/debugger_tab.gd b/addons/beehave/debug/debugger_tab.gd index a023b2aa..758d8b60 100644 --- a/addons/beehave/debug/debugger_tab.gd +++ b/addons/beehave/debug/debugger_tab.gd @@ -5,12 +5,14 @@ const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") signal make_floating -const BeehaveGraphEdit := preload("graph_edit.gd") +const OldBeehaveGraphEdit := preload("old_graph_edit.gd") +const NewBeehaveGraphEdit := preload("new_graph_edit.gd") + const TREE_ICON := preload("../icons/tree.svg") +var graph var container: HSplitContainer var item_list: ItemList -var graph: BeehaveGraphEdit var message: Label var active_trees: Dictionary @@ -26,8 +28,11 @@ func _ready() -> void: item_list.custom_minimum_size = Vector2(200, 0) item_list.item_selected.connect(_on_item_selected) container.add_child(item_list) + if Engine.get_version_info().minor >= 2: + graph = NewBeehaveGraphEdit.new(BeehaveUtils.get_frames()) + else: + graph = OldBeehaveGraphEdit.new(BeehaveUtils.get_frames()) - graph = BeehaveGraphEdit.new(BeehaveUtils.get_frames()) container.add_child(graph) message = Label.new() diff --git a/addons/beehave/debug/new_frames.gd b/addons/beehave/debug/new_frames.gd new file mode 100644 index 00000000..4b739fdd --- /dev/null +++ b/addons/beehave/debug/new_frames.gd @@ -0,0 +1,69 @@ +@tool +extends RefCounted + + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + + +const SUCCESS_COLOR := Color("#07783a") +const NORMAL_COLOR := Color("#15181e") +const FAILURE_COLOR := Color("#82010b") +const RUNNING_COLOR := Color("#c29c06") + +var panel_normal: StyleBoxFlat +var panel_success: StyleBoxFlat +var panel_failure: StyleBoxFlat +var panel_running: StyleBoxFlat + +var titlebar_normal: StyleBoxFlat +var titlebar_success: StyleBoxFlat +var titlebar_failure: StyleBoxFlat +var titlebar_running: StyleBoxFlat + + +func _init() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + + + titlebar_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"titlebar", &"GraphNode")\ + .duplicate() + ) + titlebar_success = titlebar_normal.duplicate() + titlebar_failure = titlebar_normal.duplicate() + titlebar_running = titlebar_normal.duplicate() + + titlebar_success.bg_color = SUCCESS_COLOR + titlebar_failure.bg_color = FAILURE_COLOR + titlebar_running.bg_color = RUNNING_COLOR + + titlebar_success.border_color = SUCCESS_COLOR + titlebar_failure.border_color = FAILURE_COLOR + titlebar_running.border_color = RUNNING_COLOR + + + panel_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel", &"GraphNode") + .duplicate() + ) + panel_success = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel_selected", &"GraphNode") + .duplicate() + ) + panel_failure = panel_success.duplicate() + panel_running = panel_success.duplicate() + + panel_success.border_color = SUCCESS_COLOR + panel_failure.border_color = FAILURE_COLOR + panel_running.border_color = RUNNING_COLOR diff --git a/addons/beehave/debug/new_graph_edit.gd b/addons/beehave/debug/new_graph_edit.gd new file mode 100644 index 00000000..71161ab7 --- /dev/null +++ b/addons/beehave/debug/new_graph_edit.gd @@ -0,0 +1,296 @@ +@tool +extends GraphEdit + +const BeehaveGraphNode := preload("new_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("#898989") +const ACTIVE_COLOR: Color = Color("#c29c06") +const SUCCESS_COLOR: Color = Color("#07783a") + + +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 frames:RefCounted +var active_nodes: Array[String] +var progress: int = 0 +var layout_button: Button + + +func _init(frames:RefCounted) -> void: + self.frames = frames + + +func _ready() -> void: + custom_minimum_size = Vector2(100, 300) + set("show_arrange_button", 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_menu_container().add_child(layout_button) + _update_layout_button() + + +func _update_graph() -> void: + if updating_graph: + return + + updating_graph = true + + clear_connections() + + for child in _get_child_nodes(): + 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(frames, 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_menu_container() -> Control: + return call("get_menu_hbox") + + +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_child_nodes(): + 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_child_nodes(): + 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_child_nodes() -> Array[Node]: + return get_children().filter(func(child): return child is BeehaveGraphNode) + + +func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + for child in _get_child_nodes(): + for port in child.get_input_port_count(): + if not (child.position_offset + child.get_input_port_position(port)).is_equal_approx(to_position): + continue + to_position = child.position_offset + child.get_custom_input_port_position(horizontal_layout) + for port in child.get_output_port_count(): + if not (child.position_offset + child.get_output_port_position(port)).is_equal_approx(from_position): + continue + from_position = child.position_offset + child.get_custom_output_port_position(horizontal_layout) + return _get_elbow_connection_line(from_position, to_position) + + +func _get_elbow_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + var points: PackedVector2Array + + 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: + var from_node: StringName + var to_node: StringName + + from_node = c.from_node + to_node = c.to_node + + if not from_node in active_nodes or not c.to_node in active_nodes: + continue + + var from := get_node(String(from_node)) + var to := get_node(String(to_node)) + + if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: + return + + var output_port_position: Vector2 + var input_port_position: Vector2 + + var scale_factor: float = from.get_rect().size.x / from.size.x + + var line := _get_elbow_connection_line( + from.position + from.get_custom_output_port_position(horizontal_layout) * scale_factor, + to.position + to.get_custom_input_port_position(horizontal_layout) * scale_factor + ) + + 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" diff --git a/addons/beehave/debug/new_graph_node.gd b/addons/beehave/debug/new_graph_node.gd new file mode 100644 index 00000000..70f06dc2 --- /dev/null +++ b/addons/beehave/debug/new_graph_node.gd @@ -0,0 +1,155 @@ +@tool +extends GraphNode + + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +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 icon_rect: TextureRect +var title_label: Label +var label: Label +var titlebar_hbox: HBoxContainer + +var frames: RefCounted +var horizontal: bool = false + + +func _init(frames:RefCounted, horizontal: bool = false) -> void: + self.frames = frames + self.horizontal = horizontal + + +func _ready() -> void: + custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() + draggable = false + + add_theme_color_override("close_color", Color.TRANSPARENT) + add_theme_icon_override("close", ImageTexture.new()) + + # For top port + var top_port: Control = Control.new() + add_child(top_port) + + icon_rect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + + titlebar_hbox = get_titlebar_hbox() + titlebar_hbox.get_child(0).queue_free() + titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN + titlebar_hbox.add_child(icon_rect) + + title_label = Label.new() + title_label.add_theme_color_override("font_color", Color.WHITE) + var title_font: Font = get_theme_font("title_font").duplicate() + if title_font is FontVariation: + title_font.variation_embolden = 1 + elif title_font is FontFile: + title_font.font_weight = 700 + title_label.add_theme_font_override("font", title_font) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text = title_text + titlebar_hbox.add_child(title_label) + + label = Label.new() + label.text = " " if text.is_empty() else text + add_child(label) + + # For bottom port + add_child(Control.new()) + + minimum_size_changed.connect(_on_size_changed) + _on_size_changed.call_deferred() + + +func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void: + if horizontal: + if is_slot_enabled_left(1): + draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-4, -5), color) + if is_slot_enabled_right(1): + draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-5, -4.5), color) + else: + if slot_index == 0 and is_slot_enabled_left(0): + draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-4.5, -7), color) + elif slot_index == 1: + draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-4.5, -5), color) + + +func get_custom_input_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(0, size.y / 2) + else: + return Vector2(size.x/2, 0) + + +func get_custom_output_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(size.x, size.y / 2) + else: + return Vector2(size.x / 2, size.y) + + +func set_status(status: int) -> void: + match status: + 0: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success) + 1: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure) + 2: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running) + _: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal) + + +func set_slots(left_enabled: bool, right_enabled: bool) -> void: + if horizontal: + set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON) + else: + set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null) + set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, 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 _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void: + add_theme_stylebox_override("panel", panel_stylebox) + add_theme_stylebox_override("titlebar", titlebar_stylebox) + + +func _on_size_changed(): + add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x)) diff --git a/addons/beehave/debug/frames.gd b/addons/beehave/debug/old_frames.gd similarity index 100% rename from addons/beehave/debug/frames.gd rename to addons/beehave/debug/old_frames.gd diff --git a/addons/beehave/debug/graph_edit.gd b/addons/beehave/debug/old_graph_edit.gd similarity index 88% rename from addons/beehave/debug/graph_edit.gd rename to addons/beehave/debug/old_graph_edit.gd index 98e5097e..bae64c8a 100644 --- a/addons/beehave/debug/graph_edit.gd +++ b/addons/beehave/debug/old_graph_edit.gd @@ -1,7 +1,7 @@ @tool extends GraphEdit -const BeehaveGraphNode := preload("graph_node.gd") +const BeehaveGraphNode := preload("old_graph_node.gd") const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") @@ -43,11 +43,7 @@ func _init(frames: RefCounted) -> void: func _ready() -> void: custom_minimum_size = Vector2(100, 300) - # Godot 4.2+ - if "show_arrange_button" in self: - set("show_arrange_button", true) - else: - set("arrange_nodes_button_hidden", true) + set("arrange_nodes_button_hidden", true) minimap_enabled = false layout_button = Button.new() layout_button.flat = true @@ -141,12 +137,7 @@ func _get_icon(type: StringName) -> Texture2D: func get_menu_container() -> Control: - # Godot 4.0+ - if has_method("get_zoom_hbox"): - return call("get_zoom_hbox") - - # Godot 4.2+ - return call("get_menu_hbox") + return call("get_zoom_hbox") func get_status(status: int) -> String: @@ -246,14 +237,8 @@ func _draw() -> void: var from_node: StringName var to_node: StringName - # Godot 4.0+ - if c.has("from"): - from_node = c.from - to_node = c.to - # Godot 4.2+ - else: - from_node = c.from_node - to_node = c.to_node + from_node = c.from + to_node = c.to if not from_node in active_nodes or not c.to_node in active_nodes: continue @@ -267,18 +252,10 @@ func _draw() -> void: var output_port_position: Vector2 var input_port_position: Vector2 - # Godot 4.0+ - if from.has_method("get_connection_output_position"): - output_port_position = ( - from.position + from.call("get_connection_output_position", c.from_port) - ) - input_port_position = to.position + to.call("get_connection_input_position", c.to_port) - # Godot 4.2+ - else: - output_port_position = ( - from.position + from.call("get_output_port_position", c.from_port) - ) - input_port_position = to.position + to.call("get_input_port_position", c.to_port) + output_port_position = ( + from.position + from.call("get_connection_output_position", c.from_port) + ) + input_port_position = to.position + to.call("get_connection_input_position", c.to_port) var line := _get_connection_line(output_port_position, input_port_position) diff --git a/addons/beehave/debug/graph_node.gd b/addons/beehave/debug/old_graph_node.gd similarity index 100% rename from addons/beehave/debug/graph_node.gd rename to addons/beehave/debug/old_graph_node.gd diff --git a/addons/beehave/plugin.gd b/addons/beehave/plugin.gd index 1e43ea7e..cda3b0d8 100644 --- a/addons/beehave/plugin.gd +++ b/addons/beehave/plugin.gd @@ -15,7 +15,10 @@ func _init(): func _enter_tree() -> void: editor_debugger = BeehaveEditorDebugger.new() - frames = preload("debug/frames.gd").new() + if Engine.get_version_info().minor >= 2: + frames = preload("debug/new_frames.gd").new() + else: + frames = preload("debug/old_frames.gd").new() add_debugger_plugin(editor_debugger)