Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions addons/gaea/editor/graph_editor/graph_edit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
class_name GaeaGraphEdit
extends GraphEdit

signal copy_to_clipboard_request()
signal paste_from_clipboard_request()

@export var main_editor: GaeaMainEditor
@export var bottom_note_label: RichTextLabel

Expand Down Expand Up @@ -53,6 +56,9 @@ func _ready() -> void:
EditorInterface.get_script_editor().editor_script_changed.connect(_on_editor_script_changed)
_add_toolbar_buttons()

copy_to_clipboard_request.connect(_on_copy_from_clipboard_request)
paste_from_clipboard_request.connect(_on_paste_from_clipboard_request)


#region Saving and Loading
func populate(new_graph: GaeaGraph) -> void:
Expand Down Expand Up @@ -315,10 +321,12 @@ func delete_nodes(nodes: Array[StringName]) -> void:
update_connections()


func get_selected() -> Array[Node]:
return get_children().filter(
func(child: Node) -> bool: return child is GraphElement and child.selected
)
func get_selected() -> Array[GraphElement]:
var selected: Array[GraphElement] = []
for child: Node in get_children():
if child is GraphElement and child.selected:
selected.append(child)
return selected


func get_selected_names() -> Array[StringName]:
Expand Down Expand Up @@ -694,7 +702,7 @@ func _paste_nodes(at_position: Vector2, data: GaeaNodesCopy = copy_buffer) -> vo
_load_connections.call_deferred(new_connections)


func _get_copy_data(nodes: Array) -> GaeaNodesCopy:
func _get_copy_data(nodes: Array[GraphElement]) -> GaeaNodesCopy:
var copy_data: GaeaNodesCopy = GaeaNodesCopy.new()
for selected in nodes:
if selected is GaeaGraphNode:
Expand Down Expand Up @@ -814,3 +822,16 @@ func _on_main_editor_visibility_changed() -> void:
set_connection_lines_thickness(GaeaEditorSettings.get_line_thickness())
set_minimap_opacity(GaeaEditorSettings.get_minimap_opacity())
#endregion


func _on_copy_from_clipboard_request() -> void:
var copy_data: GaeaNodesCopy = _get_copy_data(get_selected())
DisplayServer.clipboard_set(copy_data.serialize())


func _on_paste_from_clipboard_request() -> void:
var copy_data: Variant = GaeaNodesCopy.deserialize(DisplayServer.clipboard_get())
if copy_data is GaeaNodesCopy:
_paste_nodes(local_to_grid(get_local_mouse_position()), copy_data)
elif copy_data is String:
EditorInterface.get_editor_toaster().push_toast(copy_data, EditorToaster.SEVERITY_ERROR)
15 changes: 13 additions & 2 deletions addons/gaea/editor/graph_editor/node_context_menu.gd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ enum Action {
GROUP_IN_FRAME,
DETACH,
ENABLE_AUTO_SHRINK,
OPEN_IN_INSPECTOR
OPEN_IN_INSPECTOR,
COPY_TO_CLIPBOARD,
PASTE_FROM_CLIPBOARD,
}

@export var main_editor: GaeaMainEditor
Expand All @@ -40,9 +42,14 @@ func populate(selected: Array) -> void:
add_item("Delete", Action.DELETE)
add_item("Clear Copy Buffer", Action.CLEAR_BUFFER)

add_separator()
add_item("Copy to Clipboard", Action.COPY_TO_CLIPBOARD)
add_item("Paste from Clipboard", Action.PASTE_FROM_CLIPBOARD)

if not is_instance_valid(graph_edit.copy_buffer):
set_item_disabled(get_item_index(Action.PASTE), true)
set_item_disabled(get_item_index(Action.CLEAR_BUFFER), true)

if not selected.is_empty():
add_separator()
add_item("Group in New Frame", Action.GROUP_IN_FRAME)
Expand All @@ -57,6 +64,7 @@ func populate(selected: Array) -> void:
set_item_disabled(get_item_index(Action.COPY), true)
set_item_disabled(get_item_index(Action.CUT), true)
set_item_disabled(get_item_index(Action.DELETE), true)
set_item_disabled(get_item_index(Action.COPY_TO_CLIPBOARD), true)
return

if selected.front() is GaeaGraphFrame and selected.size() == 1:
Expand Down Expand Up @@ -128,7 +136,10 @@ func _on_id_pressed(id: int) -> void:
Action.GROUP_IN_FRAME:
var selected: Array[StringName] = graph_edit.get_selected_names()
_group_nodes_in_frame(selected)

Action.COPY_TO_CLIPBOARD:
graph_edit.copy_to_clipboard_request.emit()
Action.PASTE_FROM_CLIPBOARD:
graph_edit.paste_from_clipboard_request.emit()
Action.DETACH:
var selected: Array = graph_edit.get_selected()
for node: GraphElement in selected:
Expand Down
88 changes: 87 additions & 1 deletion addons/gaea/resources/gaea_nodes_copy.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ var _connections: Array[Dictionary] : get = get_connections
var _origin: Vector2 = Vector2(INF, INF) : get = get_origin


func _init(origin = Vector2(INF, INF)) -> void:
_origin = origin


func get_nodes_info() -> Dictionary[int, Dictionary]:
return _nodes

Expand Down Expand Up @@ -44,7 +48,13 @@ func add_frame(current_id: int, position: Vector2, data: Dictionary) -> void:


func add_connections(connections: Array[Dictionary]) -> void:
_connections.append_array(connections)
for connection in connections:
add_connection(connection)


func add_connection(connection: Dictionary) -> void:
if not _connections.has(connection):
_connections.append(connection)


func get_node_type(id: int) -> GaeaGraph.NodeType:
Expand All @@ -61,3 +71,79 @@ func get_node_data(id: int) -> Dictionary:

func get_node_position(id: int) -> Vector2:
return _nodes.get(id, {}).get(&"position", get_origin())


func serialize() -> String:
var nodes_data: Dictionary[int, Array] = {}
var connections: Array[String] = []

for node_id: int in _nodes.keys():
var node_properties: Dictionary = _nodes.get(node_id, {})
nodes_data.set(node_id, [
node_properties.get(&"type"),
node_properties.get(&"data"),
])

for connection in _connections:
if nodes_data.has(connection.from_node) and nodes_data.has(connection.to_node):
connections.append("%s-%s-%s-%s" % [
connection.get("from_node"),
connection.get("from_port"),
connection.get("to_node"),
connection.get("to_port"),
])

return JSON.stringify({
"origin": _origin,
"nodes": nodes_data,
"connections": connections
}, "", false)


## Deserialize a previously serialized GaeaNodesCopy,
## return a GaeaNodesCopy object or a string as error message.
static func deserialize(serialized: String) -> Variant:
var data = JSON.parse_string(serialized)

if (
not data is Dictionary
or not data.get("origin") is Vector2
or not data.get("nodes") is Dictionary
or not data.get("connections") is Array
):
return "Invalid data provided: the data could not be parsed"

var origin: Vector2 = data[0]
var deserialized: GaeaNodesCopy = GaeaNodesCopy.new(origin)

var nodes_data: Dictionary = data.get("nodes")
for node_id in nodes_data.keys():
var node_data = nodes_data.get(node_id)
if typeof(node_id) != TYPE_INT or typeof(node_data) != TYPE_ARRAY or not node_data[1] is Dictionary:
return "Invalid data provided: the data could not be parsed"
match node_data[0]:
GaeaGraph.NodeType.NODE:
var uid: String = node_data[1].get(&"uid")
var resource: GaeaNodeResource
if GaeaNodeResource.is_valid_node_resource(uid).is_empty():
resource = load(uid).new()
else:
resource = GaeaNodeInvalidScript.new()
resource.load_save_data(node_data[1])
deserialized.add_node(node_id, resource, node_data[1].get(&"position", origin), node_data[1])
GaeaGraph.NodeType.FRAME:
deserialized.add_frame(node_id, node_data[1].get(&"position", origin), node_data[1])
_:
return "Invalid data provided: the data could not be parsed"

var connections: Array = nodes_data.get("connections")
@warning_ignore("integer_division")
for connection_string: String in connections:
var split_string := connection_string.split("-")
deserialized.add_connection({
"from_node": int(split_string[0]),
"from_port": int(split_string[1]),
"to_node": int(split_string[2]),
"to_port": int(split_string[3])
})
return deserialized
Loading