mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
8156e4769f
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>
506 lines
15 KiB
GDScript
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)
|