# (c) 2023-present Eroax # (c) 2023-present Yagich # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) extends PanelContainer class_name Sidebar @onready var deck_menu: AccordionMenu = %Deck @onready var node_menu: AccordionMenu = %Node @onready var node_list_menu: AccordionMenu = %"Node List" const SYSTEM_CODE_FONT = preload("res://graph_node_renderer/system_code_font.tres") var edited_deck_id: String var edited_node_id: String var deck_inspector: DeckInspector var node_inspector: NodeInspector signal go_to_node_requested(node_id: String) func _ready() -> void: set_edited_deck() func set_edited_deck(id: String = "") -> void: if edited_deck_id == id and not id.is_empty(): return edited_deck_id = id set_edited_node("") for i in deck_menu.get_children(): i.queue_free() if id.is_empty(): var label := Label.new() label.autowrap_mode = TextServer.AUTOWRAP_WORD label.text = "There is no open Deck. Open or create one to edit its properties." deck_menu.add_child(label) deck_inspector = null return deck_inspector = DeckInspector.new(id) for i in deck_inspector.nodes: deck_menu.add_child(i) func refresh_node_list(_unused = null) -> void: for i in node_list_menu.get_children(): i.queue_free() for node_id: String in DeckHolder.get_deck(edited_deck_id).nodes: var node := DeckHolder.get_deck(edited_deck_id).get_node(node_id) var b := Button.new() b.flat = true b.text = "\"%s\"" % node.name b.pressed.connect( func(): go_to_node_requested.emit(node._id) ) b.size_flags_horizontal = Control.SIZE_EXPAND_FILL b.tooltip_text = "Click to focus this node in the graph" b.alignment = HORIZONTAL_ALIGNMENT_LEFT b.clip_text = true var cb := Button.new() cb.text = "Copy ID" cb.pressed.connect( func(): DisplayServer.clipboard_set(node._id) ) #Util.safe_connect(node.renamed, #func(new_name: String): # TODO: bad. if the node (deck node or button, whichever) # gets removed the captures get invalidated. # maybe dont use lambda here #b.text = "\"%s\"" % node.name #) #Util.safe_connect(node.renamed, _set_button_text.bind(b)) # this is probably bad too, but its the only one that worked so far. Util.safe_connect(node.renamed, refresh_node_list) var hb := HBoxContainer.new() hb.add_child(b) hb.add_child(cb) node_list_menu.add_child(hb) #func _set_button_text(new_text: String, button: Button) -> void: # this was also bad. "Cannot convert argument 2 from Object to Object" 🙃 #button.text = "\"%s\"" % new_text func set_edited_node(id: String = "") -> void: if edited_node_id == id and not id.is_empty(): return edited_node_id = id for i in node_menu.get_children(): #i.queue_free() i.free() if id.is_empty(): var label := Label.new() label.autowrap_mode = TextServer.AUTOWRAP_WORD label.text = "There is no Node selected (or multiple are selected). Select a single Node to edit its properties." node_menu.add_child(label) node_menu.draw_tree = false node_inspector = null return #await get_tree().process_frame node_menu.draw_tree = true node_inspector = NodeInspector.new(edited_deck_id, id) node_inspector.go_to_node_requested.connect( func(): go_to_node_requested.emit(edited_node_id) ) for i in node_inspector.nodes: node_menu.add_child(i) DeckHolder.get_deck(edited_deck_id).get_node(id).about_to_free.connect( func(): set_edited_node() ) #DeckHolder.get_deck(edited_deck_id).node_removed.connect( #func(node: DeckNode): #if node._id == edited_node_id: #set_edited_node() #) class Inspector extends HBoxContainer: func _init(text: String, editor: Control = null) -> void: var _label = Label.new() _label.text = text _label.size_flags_horizontal = Control.SIZE_EXPAND_FILL add_child(_label) if editor: editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL add_child(editor) class DeckInspector: var ref: WeakRef var nodes: Array[Control] func add_inspector(text: String, control: Control = null) -> void: nodes.append(Inspector.new(text, control)) func create_label(text: String) -> Label: var l := Label.new() l.text = text l.size_flags_horizontal = Control.SIZE_EXPAND_FILL return l func create_hb_label(text: String, control: Control) -> HBoxContainer: var hb := HBoxContainer.new() control.size_flags_horizontal = Control.SIZE_EXPAND_FILL hb.add_child(create_label(text)) hb.add_child(control) return hb func _init(id: String) -> void: ref = weakref(DeckHolder.get_deck(id)) var deck: Deck = ref.get_ref() as Deck var lib_menu := AccordionMenu.new() lib_menu.set_title("Library Group") var lib_group_text: String if deck.is_library: lib_group_text = "This deck is open as a library group. You may not edit it here.\nTo edit it, you have to open the file it's in." elif not deck.is_group: lib_group_text = "You may save this deck as a Library Group to reuse it in future decks.\nYou may edit how it will appear." else: lib_group_text = "This deck is a group." var l := create_label(lib_group_text) l.autowrap_mode = TextServer.AUTOWRAP_WORD l.custom_minimum_size.x = 40 lib_menu.add_child(l) lib_menu.set_ignore_child_lines(0) var name_field := LineEdit.new() name_field.placeholder_text = "Name" name_field.text = deck.lib_name name_field.text_changed.connect( func(new_text: String): deck.lib_name = new_text ) name_field.editable = not deck.is_group lib_menu.add_child(create_hb_label("Library name:", name_field)) var desc_field := TextEdit.new() desc_field.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY desc_field.placeholder_text = "Description" desc_field.text = deck.lib_description desc_field.text_changed.connect( func(): deck.lib_description = desc_field.text ) desc_field.editable = not deck.is_group desc_field.custom_minimum_size.y = 100 desc_field.custom_minimum_size.x = 100 lib_menu.add_child(create_hb_label("Library description:", desc_field)) nodes.append(lib_menu) class NodeInspector: var ref: WeakRef var nodes: Array[Control] var _name_field: LineEdit signal go_to_node_requested() var DESCRIPTOR_SCENES := { "button": load("res://graph_node_renderer/descriptors/button_descriptor.tscn"), "field": load("res://graph_node_renderer/descriptors/field_descriptor.tscn"), "singlechoice": load("res://graph_node_renderer/descriptors/single_choice_descriptor.tscn"), "codeblock": load("res://graph_node_renderer/descriptors/codeblock_descriptor.tscn"), "checkbox": load("res://graph_node_renderer/descriptors/check_box_descriptor.tscn"), "spinbox": load("res://graph_node_renderer/descriptors/spin_box_descriptor.tscn"), "label": load("res://graph_node_renderer/descriptors/label_descriptor.tscn"), } func add_inspector(text: String, control: Control = null) -> void: nodes.append(Inspector.new(text, control)) func create_label(text: String) -> Label: var l := Label.new() l.text = text l.size_flags_horizontal = Control.SIZE_EXPAND_FILL return l func _name_field_rename(new_name: String) -> void: if _name_field.has_focus(): return _name_field.text = new_name func _init(deck_id: String, id: String) -> void: ref = weakref(DeckHolder.get_deck(deck_id).get_node(id)) var node: DeckNode = ref.get_ref() as DeckNode var type_label := create_label(node.node_type) var type_label_settings := LabelSettings.new() type_label_settings.font = SYSTEM_CODE_FONT type_label_settings.font_size = 14 type_label.label_settings = type_label_settings var copy_button := Button.new() copy_button.text = "Copy" copy_button.pressed.connect( func(): DisplayServer.clipboard_set(node.node_type) ) var hb := HBoxContainer.new() hb.add_child(type_label) hb.add_child(copy_button) add_inspector("Node Type:", hb) _name_field = LineEdit.new() _name_field.placeholder_text = "Node name" _name_field.text = node.name _name_field.text_changed.connect(node.rename) _name_field.editable = not node._belonging_to.is_library node.renamed.connect(_name_field_rename) add_inspector("Node name:", _name_field) var focus_button := Button.new() focus_button.text = "Focus node" focus_button.pressed.connect( func(): go_to_node_requested.emit() ) focus_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL nodes.append(focus_button) add_port_menu(node.get_input_ports(), node) add_port_menu(node.get_output_ports(), node) func add_port_menu(ports: Array[Port], node: DeckNode) -> void: if ports.is_empty(): return var ports_menu := AccordionMenu.new() ports_menu.draw_background = true var is_output := ports[0].port_type == DeckNode.PortType.OUTPUT ports_menu.set_title("Output Ports" if is_output else "Input Ports") for port in ports: var acc := AccordionMenu.new() acc.draw_background = true acc.set_title("Port %s" % port.index_of_type) var label_label := create_label("Name: %s" % port.label) acc.add_child(label_label) var type_label := create_label("Type: %s" % DeckType.type_str(port.type).to_lower()) acc.add_child(type_label) var usage_is_both := port.usage_type == Port.UsageType.BOTH var usage_text = "Both (Value Request or Trigger)" if usage_is_both else Port.UsageType.keys()[port.usage_type].capitalize() var usage_label := create_label("Usage: %s" % usage_text) acc.add_child(usage_label) var descriptor_split := port.descriptor.split(":") if DESCRIPTOR_SCENES.has(descriptor_split[0]): var port_hb := HBoxContainer.new() var value_label := create_label("Value:") port_hb.add_child(value_label) var desc: DescriptorContainer = DESCRIPTOR_SCENES[descriptor_split[0]].instantiate() desc.ready.connect( func(): desc.set_up_from_port(port, node, false) ) port_hb.add_child(desc) acc.add_child(port_hb) ports_menu.add_child(acc) ports_menu.collapsed = true nodes.append(ports_menu)