diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index b95072c..0b90e8b 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -110,6 +110,7 @@ func add_empty_deck() -> void: tab_container.add_content(inst, "") tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) + inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst)) ## Closes the current tab in [member tab_container] func close_current_tab() -> void: @@ -143,6 +144,8 @@ func _on_file_dialog_save_file(path: String) -> void: deck.save_path = path tab_container.set_tab_title(tab_container.get_current_tab(), path.get_file()) + var renderer: DeckRendererGraphEdit = tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit + renderer.dirty = false # TODO: put this into DeckHolder instead var json := JSON.stringify(deck.to_dict(), "\t") var f := FileAccess.open(path, FileAccess.WRITE) @@ -170,6 +173,7 @@ func open_deck_at_path(path: String) -> void: tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) inst.initialize_from_deck() inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) + inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst)) add_recent_file(path) ## Gets the currently active [Deck] from [member tab_container] @@ -188,6 +192,7 @@ func save_active_deck() -> void: var f := FileAccess.open(get_active_deck().save_path, FileAccess.WRITE) f.store_string(json) add_recent_file(get_active_deck().save_path) + (tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit).dirty = false ## Disconnects the [FileDialog] signals if they are already connected. func disconnect_file_dialog_signals() -> void: @@ -266,6 +271,18 @@ func _on_debug_decks_viewer_item_pressed(deck_id: String, instance_id: String) - inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) +func _on_deck_renderer_dirty_state_changed(renderer: DeckRendererGraphEdit) -> void: + var idx: int = range(tab_container.get_tab_count()).filter( + func(x: int): + return tab_container.get_content(x) == renderer + )[0] + var title := tab_container.get_tab_title(idx).trim_suffix("(*)") + if renderer.dirty: + tab_container.set_tab_title(idx, "%s(*)" % title) + else: + tab_container.set_tab_title(idx, title.trim_suffix("(*)")) + + func add_recent_file(path: String) -> void: var item := recent_files.find(path) if item == -1: diff --git a/graph_node_renderer/deck_node_renderer_graph_node.gd b/graph_node_renderer/deck_node_renderer_graph_node.gd index b2dc98b..27f5383 100644 --- a/graph_node_renderer/deck_node_renderer_graph_node.gd +++ b/graph_node_renderer/deck_node_renderer_graph_node.gd @@ -28,6 +28,7 @@ func _on_position_offset_changed() -> void: node.position.x = position_offset.x node.position.y = position_offset.y node.position_updated.emit(node.position) + (get_parent() as DeckRendererGraphEdit).dirty = true ## Connected to [member node]s [signal position_updated] to keep parity with the ## data position. @@ -36,6 +37,7 @@ func _on_node_position_updated(new_position: Dictionary) -> void: position_offset.x = new_position.x position_offset.y = new_position.y position_offset_changed.connect(_on_position_offset_changed) + (get_parent() as DeckRendererGraphEdit).dirty = true ## Connected to [member node]s [signal port_added] handles setting up the specified diff --git a/graph_node_renderer/deck_renderer_graph_edit.gd b/graph_node_renderer/deck_renderer_graph_edit.gd index fa75e69..0c37915 100644 --- a/graph_node_renderer/deck_renderer_graph_edit.gd +++ b/graph_node_renderer/deck_renderer_graph_edit.gd @@ -30,6 +30,14 @@ var deck: Deck: ## Emits when Group creation is requested. Ex. Hitting the "group_nodes" Hotkey. signal group_enter_requested(group_id: String) +var dirty: bool = false: + set(v): + dirty = v + dirty_state_changed.emit() +var is_group: bool = false + +signal dirty_state_changed + ## Sets up the [member search_popup_panel] with an instance of [member ADD_NODE_SCENE] ## stored in [member add_node_menu]. And sets its size of [member search_popup_panel] to ## [member add_node_popup_size] @@ -70,6 +78,7 @@ func attempt_connection(from_node_name: StringName, from_port: int, to_node_name to_node_renderer.name, to_port ) + dirty = true ## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s ## involved, utilizes [NodeDB] for accessing them. @@ -88,6 +97,7 @@ func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name to_node_renderer.name, to_port ) + dirty = true ## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode]. ## Or [code]null[/code] if none is found. @@ -109,6 +119,8 @@ func initialize_from_deck() -> void: scroll_offset = deck.get_meta("offset", Vector2()) + is_group = deck.is_group + dirty = false for node_id in deck.nodes: var node_renderer: DeckNodeRendererGraphNode = NODE_SCENE.instantiate() node_renderer.node = deck.nodes[node_id] @@ -152,6 +164,7 @@ func _on_deck_node_added(node: DeckNode) -> void: inst.node = node add_child(inst) inst.position_offset = inst.node.position_as_vector2() + dirty = true ## Connected to [signal Deck.node_added], used to remove the specified @@ -163,6 +176,7 @@ func _on_deck_node_removed(node: DeckNode) -> void: renderer.queue_free() break + dirty = true ## Utility function that gets all [DeckNodeRenderGraphNode]s that are selected ## See [member GraphNode.selected] @@ -212,6 +226,7 @@ func _on_rename_popup_rename_confirmed(new_name: String) -> void: var node: DeckNodeRendererGraphNode = get_selected_nodes()[0] node.title = new_name node.node.rename(new_name) + dirty = true func _on_rename_popup_closed() -> void: @@ -249,11 +264,15 @@ func _on_delete_nodes_request(nodes: Array[StringName]) -> void: ) clear_connections() + + if node_ids.is_empty(): + return for node_id in node_ids: deck.remove_node(node_id, true) refresh_connections() + dirty = true func _on_copy_nodes_request() -> void: @@ -282,6 +301,7 @@ func _on_paste_nodes_request() -> void: deck.paste_nodes_from_json(clip, node_pos) refresh_connections() + dirty = true func _on_duplicate_nodes_request() -> void: @@ -300,6 +320,7 @@ func _on_duplicate_nodes_request() -> void: deck.duplicate_nodes(selected_ids) refresh_connections() + dirty = true class RenamePopup extends Popup: diff --git a/graph_node_renderer/tab_container_custom.gd b/graph_node_renderer/tab_container_custom.gd index 48c75d8..f8a7727 100644 --- a/graph_node_renderer/tab_container_custom.gd +++ b/graph_node_renderer/tab_container_custom.gd @@ -71,6 +71,11 @@ func add_content(c: Node, tab_title: String) -> void: func set_tab_title(tab_idx: int, title: String) -> void: tab_bar.set_tab_title(tab_idx, title) + +func get_tab_title(tab_idx: int) -> String: + return tab_bar.get_tab_title(tab_idx) + + ## Returns the number of tabs. func get_tab_count() -> int: return tab_bar.tab_count