From c27ad8ccc9c7929c635ea21326c51fc026b88601 Mon Sep 17 00:00:00 2001 From: BirDt_ Date: Tue, 26 Aug 2025 23:59:33 +0800 Subject: [PATCH] Add updating of node properties and node selection --- addons/godot_remote_control/plugin.gd | 69 +++++++++++++++++++-- godot-rc.el | 87 +++++++++++++++++++++------ 2 files changed, 134 insertions(+), 22 deletions(-) diff --git a/addons/godot_remote_control/plugin.gd b/addons/godot_remote_control/plugin.gd index 1a191c0..6370c03 100644 --- a/addons/godot_remote_control/plugin.gd +++ b/addons/godot_remote_control/plugin.gd @@ -13,7 +13,6 @@ func node_to_tree(node, root=false): n["type"] = node.get_class() n["index"] = node.get_index() n["path"] = "/" + node.get_path().get_concatenated_names() - print(n) var c = [] for i in node.get_children(): c.append(node_to_tree(i)) @@ -21,6 +20,47 @@ func node_to_tree(node, root=false): n["children"] = c return n +func node_to_props(node): + var n = {} + n["name"] = node.name + n["type"] = node.get_class() + n["path"] = "/" + node.get_path().get_concatenated_names() + n["props"] = [] + var props = node.get_property_list() + var i = 0 + # NOTE: I cannot begin to fathom the reasons for why this shit is done + # the way that it is, but this is bordering on an exercise + # in pure futility at this stage. + while i < len(props): + var p = props[i] + if p["usage"] & 128 == 128: + var cat = {} + cat["name"] = p["name"] + cat["props"] = [] + + if i == len(props) - 1: + break + + i += 1 + + while i < len(props): + p = props[i] + if p["usage"] & 128 == 128: + break + if p["usage"] & 4 == 4: + var c = {} + c["name"] = p["name"] + c["value"] = node.get(p["name"]) + cat["props"].append(c) + i += 1 + + n["props"].append(cat) + else: + print(p["name"], " ", p["usage"]) + break + n["props"].reverse() + return n + func parse_command(str: String, peer: StreamPeerTCP): var cmd = JSON.parse_string(str) print(cmd) @@ -28,20 +68,41 @@ func parse_command(str: String, peer: StreamPeerTCP): "open-scene": print("Opening") EditorInterface.open_scene_from_path(cmd["scene"]) + + "select-node": + var n = get_node(NodePath(cmd["path"])) + EditorInterface.edit_node(n) + "get-scene-tree": - var result = node_to_tree(EditorInterface.get_edited_scene_root(), true) + var result = node_to_props(EditorInterface.get_selection().get_selected_nodes()[0]) + result = JSON.stringify({"for-cmd": "get-selected-node", "value": result}) + peer.put_data(result.to_utf8_buffer()) + + # TODO: this is terrible + await get_tree().create_timer(0.05).timeout + + result = node_to_tree(EditorInterface.get_edited_scene_root(), true) result = JSON.stringify({"for-cmd": "get-scene-tree", "value": result}) peer.put_data(result.to_utf8_buffer()) + "set-node-name": var n = get_node(NodePath(cmd["path"])) var new_name = cmd["new-name"] print("setting ", n, "'s name as ", new_name) n.name = new_name + + "update-property": + var n = get_node(NodePath(cmd["path"])) + var prop = cmd["name"] + var val = cmd ["new-value"] + + n.set(prop, val) + _: print("Not a valid command: ", cmd["command"]) - if cmd["command"] != "get-scene-tree": - parse_command(JSON.stringify({"command": "get-scene-tree"}), peer) + if cmd["command"] != "get-scene-tree": + await parse_command(JSON.stringify({"command": "get-scene-tree"}), peer) func _enter_tree() -> void: server = TCPServer.new() diff --git a/godot-rc.el b/godot-rc.el index 2b99100..c5e7c1c 100644 --- a/godot-rc.el +++ b/godot-rc.el @@ -39,6 +39,9 @@ (defvar godot-rc-selected-scene nil "The currently selected scene - set automatically when visiting a .tscn file.") +(defvar godot-rc-selected-node nil + "The JSON representation of the selected node.") + (defvar godot-rc-process nil "The network process for the remote control connection.") @@ -56,8 +59,11 @@ (defun godot-rc-process-filter-function (proc resp) "Handle RESP responses from the Godot PROC." + (message (format "%s" proc)) (let ((response (json-parse-string resp :object-type 'plist :array-type 'list))) (cond + ((equal "get-selected-node" (plist-get response :for-cmd)) + (setq godot-rc-selected-node (plist-get response :value))) ((equal "get-scene-tree" (plist-get response :for-cmd)) (scene-tree-render (plist-get response :value)))))) @@ -87,63 +93,108 @@ ;; TODO: this needs to check if file-name is in the godot-rc-project-root and turn it into a res:// path (defun godot-rc-open-scene (file-name &optional quiet) "Open FILE-NAME as a scene in Godot." - (godot-rc-send-command `(:command "open-scene" :scene ,file-name :get-result ,(if quiet :false t))) - (setq godot-rc-selected-scene file-name)) + (when (not (equal file-name godot-rc-selected-scene)) + (godot-rc-send-command `(:command "open-scene" :scene ,file-name :get-result ,(if quiet :false t))) + (setq godot-rc-selected-scene file-name))) -(defun tscn-check-visibility (win) +(defun tscn-check-visibility (&optional win) "When WIN changes to a TSCN, tell Godot to open the scene." (let ((buf (current-buffer))) (when (equal (file-name-extension (buffer-file-name buf)) "tscn") (godot-rc-open-scene (buffer-file-name buf))))) +(add-hook 'find-file-hook #'tscn-check-visibility) (add-hook 'window-buffer-change-functions #'tscn-check-visibility) (add-hook 'window-selection-change-functions #'tscn-check-visibility) ;;; Scene-tree-mode and relevant commands +(defclass scene-tree-node-property-section (magit-section) + ((prop-name :initform nil)) + "A `magit-section' used by `scene-tree-mode'") + (defclass scene-tree-node-section (magit-section) - (;(keymap :initform 'org-roam-node-map) + ( ;(keymap :initform 'org-roam-node-map) (node :initform nil)) - "A `magit-section' used by `org-roam-mode' to outline NODE in its own heading.") + "A `magit-section' used by `scene-tree-mode'.") (defun insert-node (node depth) "Insert a NODE and it's children as magit sections." (magit-insert-section (scene-tree-node-section node) (magit-insert-heading (concat (make-string (* depth 2) ?\s) - (propertize (plist-get node :name) 'face 'font-lock-function-call-face) + (propertize (plist-get node :name) 'face `(:inherit font-lock-function-call-face + :underline ,(equal (plist-get node :path) + (plist-get godot-rc-selected-node :path)))) " : " (propertize (plist-get node :type) 'face 'font-lock-type-face) "\n")) (dolist (n (plist-get node :children)) (insert-node n (+ 1 depth))))) +(defun insert-node-property-category (category) + "Insert a node CATEGORY as a magit section." + (magit-insert-section (magit-section) + (magit-insert-heading (propertize (plist-get category :name) 'face 'font-lock-constant-face)) + (dolist (n (plist-get category :props)) + (insert-node-property n)))) + +(defun insert-node-property (property) + "Insert a node PROPERTY as a magit section." + (magit-insert-section (scene-tree-node-property-section (plist-get property :name)) + (magit-insert-heading (concat + " " + (propertize (plist-get property :name) 'face 'font-lock-variable-use-face) + " = " + (format "%s" (plist-get property :value)))))) + (defun scene-tree-render (tree) "Render the scene tree." (with-current-buffer godot-rc-scene-tree-buffer - (let ((inhibit-read-only t)) + (let ((l (line-number-at-pos)) + (inhibit-read-only t)) (erase-buffer) (magit-insert-section (magit-section "SceneTree") (magit-insert-heading (file-name-nondirectory godot-rc-selected-scene) "\n\n") (insert-node tree 0)) - (goto-char 0)))) + (insert "\n\n") + (magit-insert-section (magit-section "NodeProperties") + (magit-insert-heading (format "%s | Properties\n" + (plist-get godot-rc-selected-node :name))) -(defun scene-tree-rename-at-point (name) + (dolist (n (plist-get godot-rc-selected-node :props)) + (insert-node-property-category n))) + (goto-line l)))) + +(defun scene-tree-update-property-at-point (new-value) + "Update the property at point to be NEW-VALUE." + (interactive "xNew value: ") + (godot-rc-send-command + `(:command "update-property" + :path ,(plist-get godot-rc-selected-node :path) + :name ,(slot-value (magit-current-section) 'value) + :new-value ,new-value))) + +(defun scene-tree-interact-at-point () "Rename node at point." - (interactive "sNew name: ") - (let ((node-path (plist-get (slot-value (magit-current-section) 'value) :path))) - (godot-rc-send-command `(:command "set-node-name" :path ,node-path :new-name ,name)))) + (interactive) + (cond + ((scene-tree-node-section-p (magit-current-section)) + (let ((node-path (plist-get (slot-value (magit-current-section) 'value) :path))) + (godot-rc-send-command `(:command "select-node" :path ,node-path)))) + ((scene-tree-node-property-section-p (magit-current-section)) + (call-interactively 'scene-tree-update-property-at-point)))) (defvar scene-tree-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map magit-section-mode-map) - (define-key map [C-return] 'scene-tree-rename-at-point) - map - "Keymap for scene-tree-mode")) + (define-key map [return] 'scene-tree-interact-at-point) + map) + "Keymap for scene-tree-mode.") - (define-derived-mode scene-tree-mode magit-section-mode "Scene Tree" - "Mode for showing the scene tree." - :group 'godot-rc) +(define-derived-mode scene-tree-mode magit-section-mode "Scene Tree" + "Mode for showing the scene tree." + :group 'godot-rc) (provide 'godot-rc)