miggor-StreamGraph/graph_node_renderer/sidebar/sidebar.gd
Lera Elvoé 8156e4769f group descriptors (#156)
closes #93

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/156
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
2024-05-20 04:32:12 +00:00

506 lines
15 KiB
GDScript

# (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
static var collapsed_menus: Dictionary # Dictionary[String -> id, bool -> collapsed]
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()
#)
static func create_menu(title: String, id: String, default_collapsed: bool = false) -> AccordionMenu:
var res := AccordionMenu.new()
res.set_title(title)
res.collapsed = collapsed_menus.get(id, default_collapsed)
res.menu_collapsed.connect(
func(p_is_visible: bool) -> void:
collapsed_menus[id] = p_is_visible
)
return res
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]
var group_descriptors_inspector: GroupDescriptorsInspector
func add_inspector(text: String, control: Control = null) -> void:
nodes.append(Inspector.new(text, control))
static 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(DeckInspector.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 := DeckInspector.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))
group_descriptors_inspector = GroupDescriptorsInspector.new(deck)
nodes.append(lib_menu)
nodes.append(group_descriptors_inspector.menu)
class GroupDescriptorsInspector:
var menu: AccordionMenu
var inputs_menu: AccordionMenu
var outputs_menu: AccordionMenu
var deck: Deck
func _init(p_deck: Deck) -> void:
deck = p_deck
menu = AccordionMenu.new()
menu.set_title("Group Inputs/Outputs")
inputs_menu = Sidebar.create_menu("Inputs", "group_inputs", true)
outputs_menu = Sidebar.create_menu("Outputs", "group_outputs", true)
deck.node_added.connect(_on_deck_node_added)
create_inputs()
create_outputs()
menu.add_child(inputs_menu)
menu.add_child(outputs_menu)
func _on_deck_node_added(node: DeckNode) -> void:
if node.node_type == "group_input":
Util.safe_connect(node.ports_updated, refresh_inputs.bind(node))
create_inputs(node)
if node.node_type == "group_output":
Util.safe_connect(node.ports_updated, refresh_outputs.bind(node))
create_outputs(node)
func refresh_inputs(node: DeckNode) -> void:
# oh boy
var root := menu.get_tree().get_root()
var fo := root.gui_get_focus_owner()
if inputs_menu.is_ancestor_of(fo):
return
create_inputs(node)
func refresh_outputs(node: DeckNode) -> void:
# oh boy
var root := menu.get_tree().get_root()
var fo := root.gui_get_focus_owner()
if outputs_menu.is_ancestor_of(fo):
return
create_outputs(node)
func create_inputs(node: DeckNode = null) -> void:
create_io(true, node)
func create_outputs(node: DeckNode = null) -> void:
create_io(false, node)
func create_io(input: bool = true, node: DeckNode = null) -> void:
var io_menu := inputs_menu if input else outputs_menu
for i in io_menu.get_children(false):
i.queue_free()
if node == null:
if input and deck.get_node(deck.group_input_node) == null:
var l := DeckInspector.create_label("No input node")
io_menu.add_child(l)
return
if not input and deck.get_node(deck.group_output_node) == null:
var l := DeckInspector.create_label("No output node")
io_menu.add_child(l)
return
if input:
node = deck.get_node(deck.group_input_node)
else:
node = deck.get_node(deck.group_output_node)
var refresh_func := refresh_inputs if input else refresh_outputs
Util.safe_connect(node.ports_updated, refresh_func.bind(node))
var ports := node.get_output_ports() if input else node.get_input_ports()
var get_port := func(index: int) -> Deck.GroupPort:
if input:
return deck.group_descriptors.get_input_port(index)
else:
return deck.group_descriptors.get_output_port(index)
for i in ports.size():
var port_override: Deck.GroupPort = get_port.call(i)
var menu_title := "Input %s" if input else "Output %s"
var menu_id := "group_input_%s" if input else "group_output_%s"
var _menu := Sidebar.create_menu(menu_title % i, menu_id % i, true)
#input_menu.set_title("Input %s" % i)
var port_label_field := LineEdit.new()
#port_label_field.placeholder_text = ports[i].label
port_label_field.placeholder_text = menu_title % i
port_label_field.text = port_override.label
port_label_field.text_changed.connect(
func(new_text: String) -> void:
port_override.label = new_text
)
_menu.add_child(Inspector.new("Label:", port_label_field))
var type_combo := OptionButton.new()
for type in DeckType.Types.size():
type_combo.add_item(DeckType.type_str(type).capitalize())
type_combo.selected = port_override.type
type_combo.item_selected.connect(
func(idx: int) -> void:
port_override.type = idx as DeckType.Types
)
_menu.add_child(Inspector.new("Type:", type_combo))
var usage_combo := OptionButton.new()
for usage in Port.UsageType.size():
usage_combo.add_item(Port.UsageType.keys()[usage].capitalize())
usage_combo.selected = port_override.usage_type
usage_combo.item_selected.connect(
func(idx: int) -> void:
port_override.usage_type = idx as Port.UsageType
)
_menu.add_child(Inspector.new("Usage:", usage_combo))
var descriptor_field := LineEdit.new()
descriptor_field.placeholder_text = "Descriptor (advanced)"
descriptor_field.tooltip_text = "Advanced use only.\nSeparate arguments with colon (:).\nPress Enter to confirm."
descriptor_field.text = port_override.descriptor
descriptor_field.text_submitted.connect(
func(new_text: String) -> void:
port_override.descriptor = new_text
)
_menu.add_child(Inspector.new("Descriptor:", descriptor_field))
io_menu.add_child(_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 _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 := DeckInspector.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 := DeckInspector.create_label("Name: %s" % port.label)
acc.add_child(label_label)
var type_label := DeckInspector.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 := DeckInspector.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 := DeckInspector.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)