lib group storage rework (#162)

saving decks that use lib groups will no longer save the whole file path to that library (at the expense of the structure needing to be the same)

also some ui/ux improvements:
- more menus in sidebar remember their collapsed state between deck/node switches
- adding a lib group will name the group node appropriately
- save dialog properly remembers the most recent path when invoked via ctrl+s and the deck hasn't been saved before

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/162
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2024-05-26 14:13:42 +00:00 committed by yagich
parent 32bda994f6
commit f720efcc72
7 changed files with 85 additions and 59 deletions

View file

@ -175,6 +175,7 @@ func add_lib_group_node(type: String) -> DeckNode:
lib.group_output_node = node._id lib.group_output_node = node._id
continue continue
group_node.init_io() group_node.init_io()
group_node.rename(NodeDB.get_library_descriptor(type).name)
return group_node return group_node
@ -696,7 +697,7 @@ static func from_dict(data: Dictionary, path: String = "") -> Deck:
group = DeckHolder.add_group_from_dict(group_data, group_id, group_instance_id, deck.id) group = DeckHolder.add_group_from_dict(group_data, group_id, group_instance_id, deck.id)
else: else:
group = DeckHolder.add_lib_instance( group = DeckHolder.add_lib_instance(
node.group_id.get_file().trim_suffix(".deck"), node.group_id.get_file(),
deck.id, deck.id,
node.group_instance_id node.group_instance_id
) )

View file

@ -153,8 +153,8 @@ static func add_lib_instance(type: String, to_deck: String, instance_id: String
var nd: NodeDB.NodeDescriptor = NodeDB.libraries[type] var nd: NodeDB.NodeDescriptor = NodeDB.libraries[type]
var path := nd.script_path var path := nd.script_path
var deck_data: Dictionary var deck_data: Dictionary
if lib_groups.has(path): if lib_groups.has(type):
deck_data = (lib_groups[path].values()[0] as Deck).to_dict() deck_data = (lib_groups[type].values()[0] as Deck).to_dict()
else: else:
if not NodeDB.libraries.has(type): if not NodeDB.libraries.has(type):
return null return null
@ -165,17 +165,17 @@ static func add_lib_instance(type: String, to_deck: String, instance_id: String
deck_data = JSON.parse_string(f.get_as_text()) deck_data = JSON.parse_string(f.get_as_text())
var instances := lib_groups.get(path, {}) as Dictionary var instances := lib_groups.get(type, {}) as Dictionary
var deck := Deck.from_dict(deck_data) var deck := Deck.from_dict(deck_data)
deck.id = path deck.id = type
deck.instance_id = instance_id deck.instance_id = instance_id
deck.is_group = true deck.is_group = true
deck.is_library = true deck.is_library = true
deck._belonging_to = to_deck deck._belonging_to = to_deck
instances[instance_id] = deck instances[instance_id] = deck
lib_groups[path] = instances lib_groups[type] = instances
print_verbose("DeckHolder: added lib group %s::%s, id %s" % [deck.id, deck.instance_id, deck.get_instance_id()]) print_verbose("DeckHolder: added lib group %s::%s, id %s" % [deck.id, deck.instance_id, deck.get_instance_id()])
return deck return deck
@ -296,20 +296,20 @@ static func pre_exit_cleanup() -> void:
static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void: static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void:
for deck_id: String in decks: for deck_id: String in decks:
if decks[deck_id] is Deck: if decks[deck_id] is Deck:
var deck: Deck = (decks[deck_id] as Deck) var deck: Deck = decks[deck_id]
if not is_instance_valid(deck): if not is_instance_valid(deck):
continue continue
deck.send_event(event_name, event_data) deck.send_event(event_name, event_data)
else: else:
for deck_instance_id: String in decks[deck_id]: for deck_instance_id: String in decks[deck_id]:
var deck: Deck = (decks[deck_id][deck_instance_id] as Deck) var deck: Deck = decks[deck_id][deck_instance_id]
if not is_instance_valid(deck): if not is_instance_valid(deck):
continue continue
deck.send_event(event_name, event_data) deck.send_event(event_name, event_data)
for lib_group_id: String in lib_groups: for lib_group_id: String in lib_groups:
for lib_instance_id: String in lib_groups[lib_group_id]: for lib_instance_id: String in lib_groups[lib_group_id]:
var deck: Deck = (lib_groups[lib_group_id][lib_instance_id] as Deck) var deck: Deck = lib_groups[lib_group_id][lib_instance_id]
if not is_instance_valid(deck): if not is_instance_valid(deck):
continue continue
deck.send_event(event_name, event_data) deck.send_event(event_name, event_data)

View file

@ -89,7 +89,7 @@ static func create_lib_descriptors(path: String) -> void:
if not deck.deck.has("library"): if not deck.deck.has("library"):
current_file = dir.get_next() current_file = dir.get_next()
continue continue
var type := current_file.trim_suffix(".deck") var type := current_file
if nodes.has(type): if nodes.has(type):
DeckHolder.logger.toast_error("Library group '%s' collides with a node with the same type." % type) DeckHolder.logger.toast_error("Library group '%s' collides with a node with the same type." % type)
current_file = dir.get_next() current_file = dir.get_next()
@ -113,7 +113,8 @@ static func create_lib_descriptors(path: String) -> void:
true, true,
true, true,
) )
libraries[descriptor.type] = descriptor descriptor.added_by_library = path
libraries[type] = descriptor
current_file = dir.get_next() current_file = dir.get_next()
@ -200,6 +201,10 @@ static func is_library(type: String) -> bool:
return libraries.has(type) return libraries.has(type)
static func get_library_descriptor(type: String) -> NodeDescriptor:
return libraries.get(type, null) as NodeDescriptor
## Returns a capitalized category string. ## Returns a capitalized category string.
static func get_category_capitalization(category: String) -> String: static func get_category_capitalization(category: String) -> String:
return CATEGORY_CAPITALIZATION.get(category, category.capitalize()) return CATEGORY_CAPITALIZATION.get(category, category.capitalize())
@ -220,8 +225,10 @@ class NodeDescriptor:
var category: String var category: String
## Whether this [DeckNode] reference will appear when searching. See [member DeckNode.appears_in_search]. ## Whether this [DeckNode] reference will appear when searching. See [member DeckNode.appears_in_search].
var appears_in_search: bool var appears_in_search: bool
## Whether this describes a library group.
var is_library: bool var is_library: bool
## If [member is_library] is [code]true[/code], this is the index that adds this library.
var added_by_library: String
## Stores the path to this node's script for later instantiation. ## Stores the path to this node's script for later instantiation.
var script_path: String var script_path: String
@ -258,6 +265,7 @@ class NodeDescriptor:
"category": category, "category": category,
"appears_in_search": appears_in_search, "appears_in_search": appears_in_search,
"is_library": is_library, "is_library": is_library,
"added_by_library": added_by_library,
} }
return d return d
@ -274,5 +282,5 @@ class NodeDescriptor:
data.get("appears_in_search", false), data.get("appears_in_search", false),
data.get("is_library", false) data.get("is_library", false)
) )
nd.added_by_library = data.get("added_by_library", "")
return nd return nd

View file

@ -69,37 +69,41 @@ func make_unique() -> Deck:
func setup_connections() -> void: func setup_connections() -> void:
input_node.ports_updated.connect(recalculate_ports) if input_node != null:
output_node.ports_updated.connect(recalculate_ports) input_node.ports_updated.connect(recalculate_ports)
if output_node != null:
output_node.ports_updated.connect(recalculate_ports)
func recalculate_ports() -> void: func recalculate_ports() -> void:
var _values := extra_port_values.duplicate() var _values := extra_port_values.duplicate()
ports.clear() ports.clear()
for output_port: Port in output_node.get_input_ports().slice(0, output_node.get_input_ports().size() - 1): if output_node != null:
var port := add_output_port( for output_port: Port in output_node.get_input_ports().slice(0, output_node.get_input_ports().size() - 1):
output_port.type, var port := add_output_port(
output_port.label, output_port.type,
output_port.descriptor, output_port.label,
output_port.usage_type, output_port.descriptor,
) output_port.usage_type,
if _values.size() - 1 < port.index: )
continue if _values.size() - 1 < port.index:
continue
port.set_value(_values[port.index])
port.set_value(_values[port.index])
for input_port: Port in input_node.get_output_ports().slice(0, input_node.get_output_ports().size() - 1): if input_node != null:
var port := add_input_port( for input_port: Port in input_node.get_output_ports().slice(0, input_node.get_output_ports().size() - 1):
input_port.type, var port := add_input_port(
input_port.label, input_port.type,
input_port.descriptor, input_port.label,
input_port.usage_type, input_port.descriptor,
) input_port.usage_type,
if _values.size() - 1 < port.index: )
continue if _values.size() - 1 < port.index:
continue
port.set_value(_values[port.index])
port.set_value(_values[port.index])
extra_ports.clear() extra_ports.clear()
extra_port_values.clear() extra_port_values.clear()

View file

@ -40,17 +40,17 @@ func add_category(category_name: String) -> void:
## Add an item to a category. ## Add an item to a category.
func add_category_item(category: String, item: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void: func add_category_item(category: String, item: String, type: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void:
var c: Category = categories[category] var c: Category = categories[category]
c.add_item(item, tooltip, favorite, library) c.add_item(item, tooltip, type, favorite, library)
## Wrapper around [method add_category_item] and [method add_category]. Adds an item to a [param category], creating the category if it doesn't exist yet. ## Wrapper around [method add_category_item] and [method add_category]. Adds an item to a [param category], creating the category if it doesn't exist yet.
func add_item(category: String, item: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void: func add_item(category: String, item: String, type: String, tooltip: String = "", favorite: bool = false, library: bool = false) -> void:
if not categories.has(category): if not categories.has(category):
add_category(category) add_category(category)
add_category_item(category, item, tooltip, favorite, library) add_category_item(category, item, type, tooltip, favorite, library)
## Get a [AddNodeMenu.Category] node by its' identifier. ## Get a [AddNodeMenu.Category] node by its' identifier.
@ -74,7 +74,7 @@ func search(text: String) -> void:
return return
for nd in search_results: for nd in search_results:
add_item(nd.category, nd.name, nd.description, NodeDB.is_node_favorite(nd.type), nd.is_library) add_item(nd.category, nd.name, nd.type, nd.description, NodeDB.is_node_favorite(nd.type), nd.is_library)
var c := get_category(nd.category) var c := get_category(nd.category)
c.set_item_metadata(c.get_item_count() - 1, "type", nd.type) c.set_item_metadata(c.get_item_count() - 1, "type", nd.type)
c.set_item_metadata(c.get_item_count() - 1, "node_descriptor", weakref(nd)) c.set_item_metadata(c.get_item_count() - 1, "node_descriptor", weakref(nd))
@ -231,8 +231,8 @@ class Category extends VBoxContainer:
## Add an item to the category. ## Add an item to the category.
func add_item(p_name: String, p_tooltip: String, p_favorite: bool = false, p_library: bool = false) -> void: func add_item(p_name: String, p_tooltip: String, p_type: String, p_favorite: bool = false, p_library: bool = false) -> void:
var item := CategoryItem.new(p_name, p_tooltip, p_favorite, p_library) var item := CategoryItem.new(p_name, p_tooltip, p_favorite, p_library, p_type)
item.favorite_toggled.connect( item.favorite_toggled.connect(
func(toggled: bool): func(toggled: bool):
item_favorite_button_toggled.emit(item.get_index(), toggled) item_favorite_button_toggled.emit(item.get_index(), toggled)
@ -321,7 +321,7 @@ class CategoryItem extends HBoxContainer:
signal favorite_toggled(toggled: bool) signal favorite_toggled(toggled: bool)
func _init(p_name: String, p_tooltip: String, p_favorite: bool, p_library: bool) -> void: func _init(p_name: String, p_tooltip: String, p_favorite: bool, p_library: bool, p_type: String) -> void:
fav_button = Button.new() fav_button = Button.new()
fav_button.icon = FAVORITE_ICON if p_favorite else NON_FAVORITE_ICON fav_button.icon = FAVORITE_ICON if p_favorite else NON_FAVORITE_ICON
fav_button.toggle_mode = true fav_button.toggle_mode = true
@ -358,11 +358,14 @@ class CategoryItem extends HBoxContainer:
inner_hb.add_child(name_button) inner_hb.add_child(name_button)
if p_library: if p_library:
var nd := NodeDB.get_library_descriptor(p_type)
group_texture_rect = TextureRect.new() group_texture_rect = TextureRect.new()
group_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED group_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
group_texture_rect.custom_minimum_size = Vector2(12, 12) group_texture_rect.custom_minimum_size = Vector2(12, 12)
group_texture_rect.texture = GROUP_ICON group_texture_rect.texture = GROUP_ICON
group_texture_rect.tooltip_text = "This is a library group. It will create a group node." group_texture_rect.tooltip_text = "This is a library group. It will create a group node."
group_texture_rect.tooltip_text += "\nAdded by %s" % nd.added_by_library
inner_hb.add_child(group_texture_rect) inner_hb.add_child(group_texture_rect)
var m := MarginContainer.new() var m := MarginContainer.new()
m.add_theme_constant_override(&"margin_right", 6) m.add_theme_constant_override(&"margin_right", 6)

View file

@ -324,7 +324,7 @@ func close_tab(tab: int) -> void:
func open_save_dialog(path: String) -> void: func open_save_dialog(path: String) -> void:
file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
file_dialog.title = "Save a Deck" file_dialog.title = "Save a Deck"
file_dialog.current_path = path file_dialog.current_path = path if not path.is_empty() else recent_path
_deck_to_save = weakref(get_active_deck()) _deck_to_save = weakref(get_active_deck())
file_dialog.popup_centered() file_dialog.popup_centered()
file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT) file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT)
@ -451,7 +451,8 @@ func save_tab(tab: int) -> void:
var renderer := tab_container.get_content(tab) as DeckRendererGraphEdit var renderer := tab_container.get_content(tab) as DeckRendererGraphEdit
var deck := renderer.deck var deck := renderer.deck
if deck.save_path.is_empty(): if deck.save_path.is_empty():
open_save_dialog("res://") #open_save_dialog("res://") # what the fuck?
open_save_dialog(tab_container.get_tab_metadata(tab, "path", recent_path))
else: else:
var json := JSON.stringify(deck.to_dict(), "\t") var json := JSON.stringify(deck.to_dict(), "\t")
var f := FileAccess.open(deck.save_path, FileAccess.WRITE) var f := FileAccess.open(deck.save_path, FileAccess.WRITE)
@ -485,7 +486,12 @@ func _on_deck_renderer_group_enter_requested(group_id: String) -> void:
var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate() var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate()
deck_renderer.deck = group_deck deck_renderer.deck = group_deck
deck_renderer.initialize_from_deck() deck_renderer.initialize_from_deck()
var tab := tab_container.add_content(deck_renderer, "(g) %s" % group_id.left(8)) var title := ""
if group_deck.is_library:
title = "(L) %s" % group_deck.id
else:
title = "(G) %s" % group_id.left(8)
var tab := tab_container.add_content(deck_renderer, title)
tab_container.set_tab_metadata(tab, "id", group_id) tab_container.set_tab_metadata(tab, "id", group_id)
tab_container.set_tab_metadata(tab, "group", true) tab_container.set_tab_metadata(tab, "group", true)
deck_renderer.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) deck_renderer.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)

View file

@ -136,6 +136,8 @@ func set_edited_node(id: String = "") -> void:
static func create_menu(title: String, id: String, default_collapsed: bool = false) -> AccordionMenu: static func create_menu(title: String, id: String, default_collapsed: bool = false) -> AccordionMenu:
if id.is_empty():
id = title
var res := AccordionMenu.new() var res := AccordionMenu.new()
res.set_title(title) res.set_title(title)
res.collapsed = collapsed_menus.get(id, default_collapsed) res.collapsed = collapsed_menus.get(id, default_collapsed)
@ -190,14 +192,15 @@ class DeckInspector:
ref = weakref(DeckHolder.get_deck(id)) ref = weakref(DeckHolder.get_deck(id))
var deck: Deck = ref.get_ref() as Deck var deck: Deck = ref.get_ref() as Deck
var lib_menu := AccordionMenu.new() #var lib_menu := AccordionMenu.new()
lib_menu.set_title("Library Group") #lib_menu.set_title("Library Group")
var lib_menu := Sidebar.create_menu("Library Group", "lib_group", true)
var lib_group_text: String var lib_group_text: String
if deck.is_library: 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." 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: 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." 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 in search results."
else: else:
lib_group_text = "This deck is a group." lib_group_text = "This deck is a group."
@ -247,8 +250,9 @@ class GroupDescriptorsInspector:
func _init(p_deck: Deck) -> void: func _init(p_deck: Deck) -> void:
deck = p_deck deck = p_deck
menu = AccordionMenu.new() #menu = AccordionMenu.new()
menu.set_title("Group Inputs/Outputs") #menu.set_title("Group Inputs/Outputs")
menu = Sidebar.create_menu("Group Inputs/Outputs", "group_io", true)
inputs_menu = Sidebar.create_menu("Inputs", "group_inputs", true) inputs_menu = Sidebar.create_menu("Inputs", "group_inputs", true)
outputs_menu = Sidebar.create_menu("Outputs", "group_outputs", true) outputs_menu = Sidebar.create_menu("Outputs", "group_outputs", true)
@ -471,14 +475,15 @@ class NodeInspector:
func add_port_menu(ports: Array[Port], node: DeckNode) -> void: func add_port_menu(ports: Array[Port], node: DeckNode) -> void:
if ports.is_empty(): if ports.is_empty():
return return
var ports_menu := AccordionMenu.new()
ports_menu.draw_background = true
var is_output := ports[0].port_type == DeckNode.PortType.OUTPUT var is_output := ports[0].port_type == DeckNode.PortType.OUTPUT
ports_menu.set_title("Output Ports" if is_output else "Input Ports") var ports_menu := Sidebar.create_menu("Output Ports" if is_output else "Input Ports", "", false)
ports_menu.draw_background = true
for port in ports: for port in ports:
var acc := AccordionMenu.new() #var acc := AccordionMenu.new()
var id = "output_%s_menu" if is_output else "input_%s_menu"
var acc := Sidebar.create_menu("Port %s" % port.index_of_type, id % port.index_of_type, true)
acc.draw_background = true acc.draw_background = true
acc.set_title("Port %s" % port.index_of_type) #acc.set_title("Port %s" % port.index_of_type)
var label_label := DeckInspector.create_label("Name: %s" % port.label) var label_label := DeckInspector.create_label("Name: %s" % port.label)
acc.add_child(label_label) acc.add_child(label_label)
@ -505,5 +510,4 @@ class NodeInspector:
acc.add_child(port_hb) acc.add_child(port_hb)
ports_menu.add_child(acc) ports_menu.add_child(acc)
ports_menu.collapsed = true
nodes.append(ports_menu) nodes.append(ports_menu)