Merge pull request #99680 from YeldhamDev/multi_remote_selection

Allow to select multiple remote nodes at runtime
This commit is contained in:
Thaddeus Crews 2025-03-11 14:00:56 -05:00
commit 20651f1162
No known key found for this signature in database
GPG key ID: 62181B86FE9E5D84
22 changed files with 1441 additions and 527 deletions

View file

@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/ /**************************************************************************/
#ifdef TOOLS_ENABLED #ifdef DEBUG_ENABLED
#include "math_fieldwise.h" #include "math_fieldwise.h"
@ -242,4 +242,4 @@ Variant fieldwise_assign(const Variant &p_target, const Variant &p_source, const
/* clang-format on */ /* clang-format on */
} }
#endif // TOOLS_ENABLED #endif // DEBUG_ENABLED

View file

@ -30,10 +30,10 @@
#pragma once #pragma once
#ifdef TOOLS_ENABLED #ifdef DEBUG_ENABLED
#include "core/variant/variant.h" #include "core/variant/variant.h"
Variant fieldwise_assign(const Variant &p_target, const Variant &p_source, const String &p_field); Variant fieldwise_assign(const Variant &p_target, const Variant &p_source, const String &p_field);
#endif // TOOLS_ENABLED #endif // DEBUG_ENABLED

View file

@ -191,6 +191,10 @@
<member name="debugger/auto_switch_to_stack_trace" type="bool" setter="" getter=""> <member name="debugger/auto_switch_to_stack_trace" type="bool" setter="" getter="">
If [code]true[/code], automatically switches to the [b]Stack Trace[/b] panel when the debugger hits a breakpoint or steps. If [code]true[/code], automatically switches to the [b]Stack Trace[/b] panel when the debugger hits a breakpoint or steps.
</member> </member>
<member name="debugger/max_node_selection" type="int" setter="" getter="">
The limit of how many remote nodes can be selected at once.
[b]Warning:[/b] Increasing this value is not recommended, as selecting too many can make the editing and inspection of remote properties unreliable.
</member>
<member name="debugger/profile_native_calls" type="bool" setter="" getter=""> <member name="debugger/profile_native_calls" type="bool" setter="" getter="">
If [code]true[/code], enables collection of profiling data from non-GDScript Godot functions, such as engine class methods. Enabling this slows execution while profiling further. If [code]true[/code], enables collection of profiling data from non-GDScript Godot functions, such as engine class methods. Enabling this slows execution while profiling further.
</member> </member>

View file

@ -843,7 +843,9 @@ bool DebugAdapterProtocol::request_remote_object(const ObjectID &p_object_id) {
return false; return false;
} }
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_object(p_object_id); TypedArray<uint64_t> arr;
arr.append(p_object_id);
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_objects(arr);
object_pending_set.insert(p_object_id); object_pending_set.insert(p_object_id);
return true; return true;

View file

@ -33,29 +33,57 @@
#include "core/debugger/debugger_marshalls.h" #include "core/debugger/debugger_marshalls.h"
#include "core/io/marshalls.h" #include "core/io/marshalls.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/inspector_dock.h"
#include "scene/debugger/scene_debugger.h" #include "scene/debugger/scene_debugger.h"
bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p_value) { bool EditorDebuggerRemoteObjects::_set(const StringName &p_name, const Variant &p_value) {
if (!prop_values.has(p_name) || String(p_name).begins_with("Constants/")) { return _set_impl(p_name, p_value, "");
}
bool EditorDebuggerRemoteObjects::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) {
String name = p_name;
if (name.begins_with("Metadata/")) {
name = name.replace_first("Metadata/", "metadata/");
}
if (!prop_values.has(name) || String(name).begins_with("Constants/")) {
return false; return false;
} }
prop_values[p_name] = p_value; Dictionary &values = prop_values[p_name];
emit_signal(SNAME("value_edited"), remote_object_id, p_name, p_value); Dictionary old_values = values.duplicate();
for (const uint64_t key : values.keys()) {
values.set(key, p_value);
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
const int size = remote_object_ids.size();
ur->create_action(size == 1 ? vformat(TTR("Set %s"), name) : vformat(TTR("Set %s on %d objects"), name, size), UndoRedo::MERGE_ENDS);
ur->add_do_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, values, p_field);
ur->add_undo_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, old_values, p_field);
ur->commit_action();
return true; return true;
} }
bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) const { bool EditorDebuggerRemoteObjects::_get(const StringName &p_name, Variant &r_ret) const {
if (!prop_values.has(p_name)) { String name = p_name;
if (name.begins_with("Metadata/")) {
name = name.replace_first("Metadata/", "metadata/");
}
if (!prop_values.has(name)) {
return false; return false;
} }
r_ret = prop_values[p_name]; r_ret = prop_values[p_name][remote_object_ids[0]];
return true; return true;
} }
void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const { void EditorDebuggerRemoteObjects::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->clear(); // Sorry, no want category. p_list->clear(); // Sorry, don't want any categories.
for (const PropertyInfo &prop : prop_list) { for (const PropertyInfo &prop : prop_list) {
if (prop.name == "script") { if (prop.name == "script") {
// Skip the script property, it's always added by the non-virtual method. // Skip the script property, it's always added by the non-virtual method.
@ -66,31 +94,35 @@ void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list)
} }
} }
String EditorDebuggerRemoteObject::get_title() { void EditorDebuggerRemoteObjects::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) {
if (remote_object_id.is_valid()) { _set_impl(p_property, p_value, p_field);
return vformat(TTR("Remote %s:"), String(type_name)) + " " + itos(remote_object_id);
} else {
return "<null>";
}
} }
Variant EditorDebuggerRemoteObject::get_variant(const StringName &p_name) { String EditorDebuggerRemoteObjects::get_title() {
if (!remote_object_ids.is_empty() && ObjectID(remote_object_ids[0].operator uint64_t()).is_valid()) {
const int size = remote_object_ids.size();
return size == 1 ? vformat(TTR("Remote %s: %d"), type_name, remote_object_ids[0]) : vformat(TTR("Remote %s (%d Selected)"), type_name, size);
}
return "<null>";
}
Variant EditorDebuggerRemoteObjects::get_variant(const StringName &p_name) {
Variant var; Variant var;
_get(p_name, var); _get(p_name, var);
return var; return var;
} }
void EditorDebuggerRemoteObject::_bind_methods() { void EditorDebuggerRemoteObjects::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObject::get_title); ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObjects::get_title);
ClassDB::bind_method(D_METHOD("get_variant"), &EditorDebuggerRemoteObject::get_variant);
ClassDB::bind_method(D_METHOD("clear"), &EditorDebuggerRemoteObject::clear);
ClassDB::bind_method(D_METHOD("get_remote_object_id"), &EditorDebuggerRemoteObject::get_remote_object_id);
ADD_SIGNAL(MethodInfo("value_edited", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"))); ADD_SIGNAL(MethodInfo("values_edited", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::DICTIONARY, "values", PROPERTY_HINT_DICTIONARY_TYPE, "uint64_t:Variant"), PropertyInfo(Variant::STRING, "field")));
} }
/// EditorDebuggerInspector
EditorDebuggerInspector::EditorDebuggerInspector() { EditorDebuggerInspector::EditorDebuggerInspector() {
variables = memnew(EditorDebuggerRemoteObject); variables = memnew(EditorDebuggerRemoteObjects);
} }
EditorDebuggerInspector::~EditorDebuggerInspector() { EditorDebuggerInspector::~EditorDebuggerInspector() {
@ -100,7 +132,7 @@ EditorDebuggerInspector::~EditorDebuggerInspector() {
void EditorDebuggerInspector::_bind_methods() { void EditorDebuggerInspector::_bind_methods() {
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("object_edited", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"))); ADD_SIGNAL(MethodInfo("objects_edited", PropertyInfo(Variant::ARRAY, "ids"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"), PropertyInfo(Variant::STRING, "field")));
ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
} }
@ -111,50 +143,143 @@ void EditorDebuggerInspector::_notification(int p_what) {
} break; } break;
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
variables->remote_object_ids.append(0);
edit(variables); edit(variables);
} break; } break;
} }
} }
void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { void EditorDebuggerInspector::_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field) {
emit_signal(SNAME("object_edited"), p_id, p_prop, p_value); emit_signal(SNAME("objects_edited"), p_prop, p_values, p_field);
} }
void EditorDebuggerInspector::_object_selected(ObjectID p_object) { void EditorDebuggerInspector::_object_selected(ObjectID p_object) {
emit_signal(SNAME("object_selected"), p_object); emit_signal(SNAME("object_selected"), p_object);
} }
ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { EditorDebuggerRemoteObjects *EditorDebuggerInspector::set_objects(const Array &p_arr) {
EditorDebuggerRemoteObject *debug_obj = nullptr; ERR_FAIL_COND_V(p_arr.is_empty(), nullptr);
SceneDebuggerObject obj; TypedArray<uint64_t> ids;
obj.deserialize(p_arr); LocalVector<SceneDebuggerObject> objects;
ERR_FAIL_COND_V(obj.id.is_null(), ObjectID()); for (const Array arr : p_arr) {
SceneDebuggerObject obj;
obj.deserialize(arr);
if (obj.id.is_valid()) {
ids.push_back((uint64_t)obj.id);
objects.push_back(obj);
}
}
ERR_FAIL_COND_V(ids.is_empty(), nullptr);
if (remote_objects.has(obj.id)) { // Sorting is necessary, as selected nodes in the remote tree are ordered by index.
debug_obj = remote_objects[obj.id]; ids.sort();
} else {
debug_obj = memnew(EditorDebuggerRemoteObject); EditorDebuggerRemoteObjects *remote_objects = nullptr;
debug_obj->remote_object_id = obj.id; for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
debug_obj->type_name = obj.class_name; if (robjs->remote_object_ids == ids) {
remote_objects[obj.id] = debug_obj; remote_objects = robjs;
debug_obj->connect("value_edited", callable_mp(this, &EditorDebuggerInspector::_object_edited)); break;
}
} }
int old_prop_size = debug_obj->prop_list.size(); if (!remote_objects) {
remote_objects = memnew(EditorDebuggerRemoteObjects);
remote_objects->remote_object_ids = ids;
remote_objects->remote_object_ids.make_read_only();
remote_objects->connect("values_edited", callable_mp(this, &EditorDebuggerInspector::_objects_edited));
remote_objects_list.push_back(remote_objects);
}
debug_obj->prop_list.clear(); StringName class_name = objects[0].class_name;
if (class_name != SNAME("Object")) {
// Search for the common class between all selected objects.
bool check_type_again = true;
while (check_type_again) {
check_type_again = false;
if (class_name == SNAME("Object") || class_name == StringName()) {
// All objects inherit from Object, so no need to continue checking.
class_name = SNAME("Object");
break;
}
// Check that all objects inherit from type_name.
for (const SceneDebuggerObject &obj : objects) {
if (obj.class_name == class_name || ClassDB::is_parent_class(obj.class_name, class_name)) {
continue; // class_name is the same or a parent of the object's class.
}
// class_name is not a parent of the node's class, so check again with the parent class.
class_name = ClassDB::get_parent_class(class_name);
check_type_again = true;
break;
}
}
}
remote_objects->type_name = class_name;
// Search for properties that are present in all selected objects.
struct UsageData {
int qty = 0;
SceneDebuggerObject::SceneDebuggerProperty prop;
TypedDictionary<uint64_t, Variant> values;
};
HashMap<String, UsageData> usage;
int nc = 0;
for (const SceneDebuggerObject &obj : objects) {
for (const SceneDebuggerObject::SceneDebuggerProperty &prop : obj.properties) {
PropertyInfo pinfo = prop.first;
if (pinfo.name == "script") {
continue; // Added later manually, since this is intercepted before being set (check Variant Object::get()).
} else if (pinfo.name.begins_with("metadata/")) {
pinfo.name = pinfo.name.replace_first("metadata/", "Metadata/"); // Trick to not get actual metadata edited from EditorDebuggerRemoteObjects.
}
if (!usage.has(pinfo.name)) {
UsageData usage_dt;
usage_dt.prop = prop;
usage_dt.prop.first.name = pinfo.name;
usage_dt.values[obj.id] = prop.second;
usage[pinfo.name] = usage_dt;
}
// Make sure only properties with the same exact PropertyInfo data will appear.
if (usage[pinfo.name].prop.first == pinfo) {
usage[pinfo.name].qty++;
usage[pinfo.name].values[obj.id] = prop.second;
}
}
nc++;
}
for (HashMap<String, UsageData>::Iterator E = usage.begin(); E;) {
HashMap<String, UsageData>::Iterator next = E;
++next;
UsageData usage_dt = E->value;
if (nc != usage_dt.qty) {
// Doesn't appear on all of them, remove it.
usage.erase(E->key);
}
E = next;
}
int old_prop_size = remote_objects->prop_list.size();
remote_objects->prop_list.clear();
int new_props_added = 0; int new_props_added = 0;
HashSet<String> changed; HashSet<String> changed;
for (SceneDebuggerObject::SceneDebuggerProperty &property : obj.properties) { for (const KeyValue<String, UsageData> &KV : usage) {
PropertyInfo &pinfo = property.first; const PropertyInfo &pinfo = KV.value.prop.first;
Variant &var = property.second; Variant var = KV.value.values[remote_objects->remote_object_ids[0]];
if (pinfo.type == Variant::OBJECT) { if (pinfo.type == Variant::OBJECT) {
if (var.is_string()) { if (var.is_string()) {
String path = var; String path = var;
if (path.contains("::")) { if (path.contains("::")) {
// built-in resource // Built-in resource.
String base_path = path.get_slice("::", 0); String base_path = path.get_slice("::", 0);
Ref<Resource> dependency = ResourceLoader::load(base_path); Ref<Resource> dependency = ResourceLoader::load(base_path);
if (dependency.is_valid()) { if (dependency.is_valid()) {
@ -164,13 +289,13 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
var = ResourceLoader::load(path); var = ResourceLoader::load(path);
if (pinfo.hint_string == "Script") { if (pinfo.hint_string == "Script") {
if (debug_obj->get_script() != var) { if (remote_objects->get_script() != var) {
debug_obj->set_script(Ref<RefCounted>()); remote_objects->set_script(Ref<RefCounted>());
Ref<Script> scr(var); Ref<Script> scr(var);
if (scr.is_valid()) { if (scr.is_valid()) {
ScriptInstance *scr_instance = scr->placeholder_instance_create(debug_obj); ScriptInstance *scr_instance = scr->placeholder_instance_create(remote_objects);
if (scr_instance) { if (scr_instance) {
debug_obj->set_script_and_instance(var, scr_instance); remote_objects->set_script_and_instance(var, scr_instance);
} }
} }
} }
@ -178,49 +303,67 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
} }
} }
//always add the property, since props may have been added or removed // Always add the property, since props may have been added or removed.
debug_obj->prop_list.push_back(pinfo); remote_objects->prop_list.push_back(pinfo);
if (!debug_obj->prop_values.has(pinfo.name)) { if (!remote_objects->prop_values.has(pinfo.name)) {
new_props_added++; new_props_added++;
debug_obj->prop_values[pinfo.name] = var; } else if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, remote_objects->prop_values[pinfo.name], var))) {
} else { changed.insert(pinfo.name);
if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debug_obj->prop_values[pinfo.name], var))) {
debug_obj->prop_values[pinfo.name] = var;
changed.insert(pinfo.name);
}
} }
remote_objects->prop_values[pinfo.name] = KV.value.values;
} }
if (old_prop_size == debug_obj->prop_list.size() && new_props_added == 0) { if (old_prop_size == remote_objects->prop_list.size() && new_props_added == 0) {
//only some may have changed, if so, then update those, if exist // Only some may have changed, if so, then update those, if they exist.
for (const String &E : changed) { for (const String &E : changed) {
emit_signal(SNAME("object_property_updated"), debug_obj->remote_object_id, E); emit_signal(SNAME("object_property_updated"), remote_objects->get_instance_id(), E);
} }
} else { } else {
//full update, because props were added or removed // Full update, because props were added or removed.
debug_obj->update(); remote_objects->update();
}
return remote_objects;
}
void EditorDebuggerInspector::clear_remote_inspector() {
if (remote_objects_list.is_empty()) {
return;
}
const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();
// Check if the inspector holds remote items, and take it out if so.
if (Object::cast_to<EditorDebuggerRemoteObjects>(obj)) {
EditorNode::get_singleton()->push_item(nullptr);
} }
return obj.id;
} }
void EditorDebuggerInspector::clear_cache() { void EditorDebuggerInspector::clear_cache() {
for (const KeyValue<ObjectID, EditorDebuggerRemoteObject *> &E : remote_objects) { clear_remote_inspector();
EditorNode *editor = EditorNode::get_singleton();
if (editor->get_editor_selection_history()->get_current() == E.value->get_instance_id()) { for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
editor->push_item(nullptr); memdelete(robjs);
}
memdelete(E.value);
} }
remote_objects.clear(); remote_objects_list.clear();
remote_dependencies.clear(); remote_dependencies.clear();
} }
Object *EditorDebuggerInspector::get_object(ObjectID p_id) { void EditorDebuggerInspector::invalidate_selection_from_cache(const TypedArray<uint64_t> &p_ids) {
if (remote_objects.has(p_id)) { for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
return remote_objects[p_id]; if (robjs->remote_object_ids == p_ids) {
const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();
if (obj == robjs) {
EditorNode::get_singleton()->push_item(nullptr);
}
remote_objects_list.erase(robjs);
memdelete(robjs);
break;
}
} }
return nullptr;
} }
void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) { void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) {
@ -270,7 +413,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_off
} }
variables->prop_list.insert_before(current, pinfo); variables->prop_list.insert_before(current, pinfo);
} }
variables->prop_values[type + n] = v; variables->prop_values[type + n][0] = v;
variables->update(); variables->update();
edit(variables); edit(variables);
@ -288,7 +431,7 @@ void EditorDebuggerInspector::clear_stack_variables() {
} }
String EditorDebuggerInspector::get_stack_variable(const String &p_var) { String EditorDebuggerInspector::get_stack_variable(const String &p_var) {
for (KeyValue<StringName, Variant> &E : variables->prop_values) { for (KeyValue<StringName, TypedDictionary<uint64_t, Variant>> &E : variables->prop_values) {
String v = E.key.operator String(); String v = E.key.operator String();
if (v.get_slicec('/', 1) == p_var) { if (v.get_slicec('/', 1) == p_var) {
return variables->get_variant(v); return variables->get_variant(v);

View file

@ -30,10 +30,16 @@
#pragma once #pragma once
#include "core/variant/typed_dictionary.h"
#include "editor/editor_inspector.h" #include "editor/editor_inspector.h"
class EditorDebuggerRemoteObject : public Object { class SceneDebuggerObject;
GDCLASS(EditorDebuggerRemoteObject, Object);
class EditorDebuggerRemoteObjects : public Object {
GDCLASS(EditorDebuggerRemoteObjects, Object);
private:
bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field);
protected: protected:
bool _set(const StringName &p_name, const Variant &p_value); bool _set(const StringName &p_name, const Variant &p_value);
@ -42,14 +48,13 @@ protected:
static void _bind_methods(); static void _bind_methods();
public: public:
ObjectID remote_object_id; TypedArray<uint64_t> remote_object_ids;
String type_name; String type_name;
List<PropertyInfo> prop_list; List<PropertyInfo> prop_list;
HashMap<StringName, Variant> prop_values; HashMap<StringName, TypedDictionary<uint64_t, Variant>> prop_values;
ObjectID get_remote_object_id() { return remote_object_id; } void set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field);
String get_title(); String get_title();
Variant get_variant(const StringName &p_name); Variant get_variant(const StringName &p_name);
void clear() { void clear() {
@ -59,20 +64,19 @@ public:
void update() { notify_property_list_changed(); } void update() { notify_property_list_changed(); }
EditorDebuggerRemoteObject() {} EditorDebuggerRemoteObjects() {}
}; };
class EditorDebuggerInspector : public EditorInspector { class EditorDebuggerInspector : public EditorInspector {
GDCLASS(EditorDebuggerInspector, EditorInspector); GDCLASS(EditorDebuggerInspector, EditorInspector);
private: private:
ObjectID inspected_object_id; LocalVector<EditorDebuggerRemoteObjects *> remote_objects_list;
HashMap<ObjectID, EditorDebuggerRemoteObject *> remote_objects;
HashSet<Ref<Resource>> remote_dependencies; HashSet<Ref<Resource>> remote_dependencies;
EditorDebuggerRemoteObject *variables = nullptr; EditorDebuggerRemoteObjects *variables = nullptr;
void _object_selected(ObjectID p_object); void _object_selected(ObjectID p_object);
void _object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value); void _objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field);
protected: protected:
void _notification(int p_what); void _notification(int p_what);
@ -83,9 +87,10 @@ public:
~EditorDebuggerInspector(); ~EditorDebuggerInspector();
// Remote Object cache // Remote Object cache
ObjectID add_object(const Array &p_arr); EditorDebuggerRemoteObjects *set_objects(const Array &p_array);
Object *get_object(ObjectID p_id); void clear_remote_inspector();
void clear_cache(); void clear_cache();
void invalidate_selection_from_cache(const TypedArray<uint64_t> &p_ids);
// Stack Dump variables // Stack Dump variables
String get_stack_variable(const String &p_var); String get_stack_variable(const String &p_var);

View file

@ -82,7 +82,8 @@ EditorDebuggerNode::EditorDebuggerNode() {
// Remote scene tree // Remote scene tree
remote_scene_tree = memnew(EditorDebuggerTree); remote_scene_tree = memnew(EditorDebuggerTree);
remote_scene_tree->connect("object_selected", callable_mp(this, &EditorDebuggerNode::_remote_object_requested)); remote_scene_tree->connect("objects_selected", callable_mp(this, &EditorDebuggerNode::_remote_objects_requested));
remote_scene_tree->connect("selection_cleared", callable_mp(this, &EditorDebuggerNode::_remote_selection_cleared));
remote_scene_tree->connect("save_node", callable_mp(this, &EditorDebuggerNode::_save_node_requested)); remote_scene_tree->connect("save_node", callable_mp(this, &EditorDebuggerNode::_save_node_requested));
remote_scene_tree->connect("button_clicked", callable_mp(this, &EditorDebuggerNode::_remote_tree_button_pressed)); remote_scene_tree->connect("button_clicked", callable_mp(this, &EditorDebuggerNode::_remote_tree_button_pressed));
SceneTreeDock::get_singleton()->add_remote_tree_editor(remote_scene_tree); SceneTreeDock::get_singleton()->add_remote_tree_editor(remote_scene_tree);
@ -109,11 +110,12 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
node->connect("breakpoint_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id)); node->connect("breakpoint_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id));
node->connect("clear_execution", callable_mp(this, &EditorDebuggerNode::_clear_execution)); node->connect("clear_execution", callable_mp(this, &EditorDebuggerNode::_clear_execution));
node->connect("breaked", callable_mp(this, &EditorDebuggerNode::_breaked).bind(id)); node->connect("breaked", callable_mp(this, &EditorDebuggerNode::_breaked).bind(id));
node->connect("debug_data", callable_mp(this, &EditorDebuggerNode::_debug_data).bind(id));
node->connect("remote_tree_select_requested", callable_mp(this, &EditorDebuggerNode::_remote_tree_select_requested).bind(id)); node->connect("remote_tree_select_requested", callable_mp(this, &EditorDebuggerNode::_remote_tree_select_requested).bind(id));
node->connect("remote_tree_clear_selection_requested", callable_mp(this, &EditorDebuggerNode::_remote_tree_clear_selection_requested).bind(id));
node->connect("remote_tree_updated", callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id)); node->connect("remote_tree_updated", callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id));
node->connect("remote_object_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_updated).bind(id)); node->connect("remote_objects_updated", callable_mp(this, &EditorDebuggerNode::_remote_objects_updated).bind(id));
node->connect("remote_object_property_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id)); node->connect("remote_object_property_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id));
node->connect("remote_object_requested", callable_mp(this, &EditorDebuggerNode::_remote_object_requested).bind(id));
node->connect("set_breakpoint", callable_mp(this, &EditorDebuggerNode::_breakpoint_set_in_tree).bind(id)); node->connect("set_breakpoint", callable_mp(this, &EditorDebuggerNode::_breakpoint_set_in_tree).bind(id));
node->connect("clear_breakpoints", callable_mp(this, &EditorDebuggerNode::_breakpoints_cleared_in_tree).bind(id)); node->connect("clear_breakpoints", callable_mp(this, &EditorDebuggerNode::_breakpoints_cleared_in_tree).bind(id));
node->connect("errors_cleared", callable_mp(this, &EditorDebuggerNode::_update_errors)); node->connect("errors_cleared", callable_mp(this, &EditorDebuggerNode::_update_errors));
@ -222,10 +224,6 @@ void EditorDebuggerNode::register_undo_redo(UndoRedo *p_undo_redo) {
p_undo_redo->set_property_notify_callback(_properties_changed, this); p_undo_redo->set_property_notify_callback(_properties_changed, this);
} }
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
return Object::cast_to<EditorDebuggerRemoteObject>(ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_current()));
}
ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const { ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const {
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(p_id)); return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(p_id));
} }
@ -292,6 +290,10 @@ void EditorDebuggerNode::stop(bool p_force) {
if (keep_open && !p_force) { if (keep_open && !p_force) {
return; return;
} }
remote_scene_tree_wait = false;
inspect_edited_object_wait = false;
current_uri.clear(); current_uri.clear();
if (server.is_valid()) { if (server.is_valid()) {
server->stop(); server->stop();
@ -304,6 +306,7 @@ void EditorDebuggerNode::stop(bool p_force) {
server.unref(); server.unref();
} }
// Also close all debugging sessions. // Also close all debugging sessions.
_for_all(tabs, [&](ScriptEditorDebugger *dbg) { _for_all(tabs, [&](ScriptEditorDebugger *dbg) {
if (dbg->is_session_active()) { if (dbg->is_session_active()) {
@ -351,21 +354,29 @@ void EditorDebuggerNode::_notification(int p_what) {
_update_errors(); _update_errors();
// Remote scene tree update // Remote scene tree update.
remote_scene_tree_timeout -= get_process_delta_time(); if (!remote_scene_tree_wait) {
if (remote_scene_tree_timeout < 0) { remote_scene_tree_timeout -= get_process_delta_time();
remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval"); if (remote_scene_tree_timeout < 0) {
if (remote_scene_tree->is_visible_in_tree()) { remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval");
get_current_debugger()->request_remote_tree();
if (remote_scene_tree->is_visible_in_tree()) {
remote_scene_tree_wait = true;
get_current_debugger()->request_remote_tree();
}
} }
} }
// Remote inspector update // Remote inspector update.
inspect_edited_object_timeout -= get_process_delta_time(); if (!inspect_edited_object_wait) {
if (inspect_edited_object_timeout < 0) { inspect_edited_object_timeout -= get_process_delta_time();
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval"); if (inspect_edited_object_timeout < 0) {
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
get_current_debugger()->request_remote_object(obj->remote_object_id);
if (EditorDebuggerRemoteObjects *robjs = Object::cast_to<EditorDebuggerRemoteObjects>(InspectorDock::get_inspector_singleton()->get_edited_object())) {
inspect_edited_object_wait = true;
get_current_debugger()->request_remote_objects(robjs->remote_object_ids, false);
}
} }
} }
@ -467,21 +478,26 @@ void EditorDebuggerNode::_debugger_stopped(int p_id) {
void EditorDebuggerNode::_debugger_wants_stop(int p_id) { void EditorDebuggerNode::_debugger_wants_stop(int p_id) {
// Ask editor to kill PID. // Ask editor to kill PID.
int pid = get_debugger(p_id)->get_remote_pid(); if (int pid = get_debugger(p_id)->get_remote_pid()) {
if (pid) {
callable_mp(EditorNode::get_singleton(), &EditorNode::stop_child_process).call_deferred(pid); callable_mp(EditorNode::get_singleton(), &EditorNode::stop_child_process).call_deferred(pid);
} }
} }
void EditorDebuggerNode::_debugger_changed(int p_tab) { void EditorDebuggerNode::_debugger_changed(int p_tab) {
if (get_inspected_remote_object()) { remote_scene_tree_wait = false;
// Clear inspected object, you can only inspect objects in selected debugger. inspect_edited_object_wait = false;
// Hopefully, in the future, we will have one inspector per debugger.
EditorNode::get_singleton()->push_item(nullptr); if (Object *robjs = InspectorDock::get_inspector_singleton()->get_edited_object()) {
if (Object::cast_to<EditorDebuggerRemoteObjects>(robjs)) {
// Clear inspected object, you can only inspect objects in selected debugger.
// Hopefully, in the future, we will have one inspector per debugger.
EditorNode::get_singleton()->push_item(nullptr);
}
} }
if (get_previous_debugger()) { if (ScriptEditorDebugger *prev_debug = get_previous_debugger()) {
_text_editor_stack_clear(get_previous_debugger()); prev_debug->clear_inspector();
_text_editor_stack_clear(prev_debug);
} }
if (remote_scene_tree->is_visible_in_tree()) { if (remote_scene_tree->is_visible_in_tree()) {
get_current_debugger()->request_remote_tree(); get_current_debugger()->request_remote_tree();
@ -493,6 +509,18 @@ void EditorDebuggerNode::_debugger_changed(int p_tab) {
_break_state_changed(); _break_state_changed();
} }
void EditorDebuggerNode::_debug_data(const String &p_msg, const Array &p_data, int p_debugger) {
if (p_debugger != tabs->get_current_tab()) {
return;
}
if (p_msg == "scene:scene_tree") {
remote_scene_tree_wait = false;
} else if (p_msg == "scene:inspect_objects") {
inspect_edited_object_wait = false;
}
}
void EditorDebuggerNode::set_script_debug_button(MenuButton *p_button) { void EditorDebuggerNode::set_script_debug_button(MenuButton *p_button) {
script_menu = p_button; script_menu = p_button;
script_menu->set_text(TTR("Debug")); script_menu->set_text(TTR("Debug"));
@ -646,11 +674,41 @@ void EditorDebuggerNode::request_remote_tree() {
get_current_debugger()->request_remote_tree(); get_current_debugger()->request_remote_tree();
} }
void EditorDebuggerNode::_remote_tree_select_requested(ObjectID p_id, int p_debugger) { void EditorDebuggerNode::set_remote_selection(const TypedArray<int64_t> &p_ids) {
remote_scene_tree->select_nodes(p_ids);
stop_waiting_inspection();
get_current_debugger()->request_remote_objects(p_ids);
}
void EditorDebuggerNode::clear_remote_tree_selection() {
remote_scene_tree->clear_selection();
get_current_debugger()->clear_inspector(remote_scene_tree_clear_msg);
}
void EditorDebuggerNode::stop_waiting_inspection() {
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
inspect_edited_object_wait = false;
}
bool EditorDebuggerNode::match_remote_selection(const TypedArray<uint64_t> &p_ids) const {
return p_ids == remote_scene_tree->get_selection();
}
void EditorDebuggerNode::_remote_tree_select_requested(const TypedArray<int64_t> &p_ids, int p_debugger) {
if (p_debugger == tabs->get_current_tab()) {
remote_scene_tree->select_nodes(p_ids);
}
}
void EditorDebuggerNode::_remote_tree_clear_selection_requested(int p_debugger) {
if (p_debugger != tabs->get_current_tab()) { if (p_debugger != tabs->get_current_tab()) {
return; return;
} }
remote_scene_tree->select_node(p_id); remote_scene_tree->clear_selection();
remote_scene_tree_clear_msg = false;
get_current_debugger()->clear_inspector(false);
remote_scene_tree_clear_msg = true;
} }
void EditorDebuggerNode::_remote_tree_updated(int p_debugger) { void EditorDebuggerNode::_remote_tree_updated(int p_debugger) {
@ -679,37 +737,37 @@ void EditorDebuggerNode::_remote_tree_button_pressed(Object *p_item, int p_colum
} }
} }
void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) { void EditorDebuggerNode::_remote_objects_updated(EditorDebuggerRemoteObjects *p_objs, int p_debugger) {
if (p_debugger != tabs->get_current_tab()) { if (p_debugger == tabs->get_current_tab() && p_objs != InspectorDock::get_inspector_singleton()->get_edited_object()) {
return; EditorNode::get_singleton()->push_item(p_objs);
} }
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
if (obj->remote_object_id == p_id) {
return; // Already being edited
}
}
EditorNode::get_singleton()->push_item(get_current_debugger()->get_remote_object(p_id));
} }
void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) { void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) {
if (p_debugger != tabs->get_current_tab()) { if (p_debugger != tabs->get_current_tab()) {
return; return;
} }
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
if (obj->remote_object_id != p_id) { Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();
return; if (obj && obj->get_instance_id() == p_id) {
}
InspectorDock::get_inspector_singleton()->update_property(p_property); InspectorDock::get_inspector_singleton()->update_property(p_property);
} }
} }
void EditorDebuggerNode::_remote_object_requested(ObjectID p_id, int p_debugger) { void EditorDebuggerNode::_remote_objects_requested(const TypedArray<uint64_t> &p_ids, int p_debugger) {
if (p_debugger != tabs->get_current_tab()) { if (p_debugger != tabs->get_current_tab()) {
return; return;
} }
inspect_edited_object_timeout = 0.7; // Temporarily disable timeout to avoid multiple requests. stop_waiting_inspection();
get_current_debugger()->request_remote_object(p_id); get_current_debugger()->request_remote_objects(p_ids);
}
void EditorDebuggerNode::_remote_selection_cleared(int p_debugger) {
if (p_debugger != tabs->get_current_tab()) {
return;
}
stop_waiting_inspection();
get_current_debugger()->clear_inspector();
} }
void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) { void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) {

View file

@ -38,7 +38,7 @@ class Button;
class DebugAdapterParser; class DebugAdapterParser;
class EditorDebuggerPlugin; class EditorDebuggerPlugin;
class EditorDebuggerTree; class EditorDebuggerTree;
class EditorDebuggerRemoteObject; class EditorDebuggerRemoteObjects;
class MenuButton; class MenuButton;
class ScriptEditorDebugger; class ScriptEditorDebugger;
class TabContainer; class TabContainer;
@ -102,9 +102,12 @@ private:
int last_error_count = 0; int last_error_count = 0;
int last_warning_count = 0; int last_warning_count = 0;
bool inspect_edited_object_wait = false;
float inspect_edited_object_timeout = 0; float inspect_edited_object_timeout = 0;
EditorDebuggerTree *remote_scene_tree = nullptr; EditorDebuggerTree *remote_scene_tree = nullptr;
bool remote_scene_tree_wait = false;
float remote_scene_tree_timeout = 0.0; float remote_scene_tree_timeout = 0.0;
bool remote_scene_tree_clear_msg = true;
bool auto_switch_remote_scene_tree = false; bool auto_switch_remote_scene_tree = false;
bool debug_with_external_editor = false; bool debug_with_external_editor = false;
bool keep_open = false; bool keep_open = false;
@ -116,7 +119,6 @@ private:
HashSet<Ref<EditorDebuggerPlugin>> debugger_plugins; HashSet<Ref<EditorDebuggerPlugin>> debugger_plugins;
ScriptEditorDebugger *_add_debugger(); ScriptEditorDebugger *_add_debugger();
EditorDebuggerRemoteObject *get_inspected_remote_object();
void _update_errors(); void _update_errors();
friend class DebuggerEditorPlugin; friend class DebuggerEditorPlugin;
@ -128,12 +130,15 @@ protected:
void _debugger_stopped(int p_id); void _debugger_stopped(int p_id);
void _debugger_wants_stop(int p_id); void _debugger_wants_stop(int p_id);
void _debugger_changed(int p_tab); void _debugger_changed(int p_tab);
void _remote_tree_select_requested(ObjectID p_id, int p_debugger); void _debug_data(const String &p_msg, const Array &p_data, int p_debugger);
void _remote_tree_select_requested(const TypedArray<int64_t> &p_ids, int p_debugger);
void _remote_tree_clear_selection_requested(int p_debugger);
void _remote_tree_updated(int p_debugger); void _remote_tree_updated(int p_debugger);
void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _remote_object_updated(ObjectID p_id, int p_debugger); void _remote_objects_updated(EditorDebuggerRemoteObjects *p_objs, int p_debugger);
void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger); void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger);
void _remote_object_requested(ObjectID p_id, int p_debugger); void _remote_objects_requested(const TypedArray<uint64_t> &p_ids, int p_debugger);
void _remote_selection_cleared(int p_debugger);
void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger); void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger);
void _breakpoint_set_in_tree(Ref<RefCounted> p_script, int p_line, bool p_enabled, int p_debugger); void _breakpoint_set_in_tree(Ref<RefCounted> p_script, int p_line, bool p_enabled, int p_debugger);
@ -190,6 +195,10 @@ public:
// Remote inspector/edit. // Remote inspector/edit.
void request_remote_tree(); void request_remote_tree();
void set_remote_selection(const TypedArray<int64_t> &p_ids);
void clear_remote_tree_selection();
void stop_waiting_inspection();
bool match_remote_selection(const TypedArray<uint64_t> &p_ids) const;
static void _methods_changed(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount); static void _methods_changed(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
static void _properties_changed(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value); static void _properties_changed(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);

View file

@ -35,6 +35,7 @@
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_toaster.h"
#include "editor/scene_tree_dock.h" #include "editor/scene_tree_dock.h"
#include "scene/debugger/scene_debugger.h" #include "scene/debugger/scene_debugger.h"
#include "scene/gui/texture_rect.h" #include "scene/gui/texture_rect.h"
@ -44,6 +45,7 @@
EditorDebuggerTree::EditorDebuggerTree() { EditorDebuggerTree::EditorDebuggerTree() {
set_v_size_flags(SIZE_EXPAND_FILL); set_v_size_flags(SIZE_EXPAND_FILL);
set_allow_rmb_select(true); set_allow_rmb_select(true);
set_select_mode(SELECT_MULTI);
// Popup // Popup
item_menu = memnew(PopupMenu); item_menu = memnew(PopupMenu);
@ -54,6 +56,9 @@ EditorDebuggerTree::EditorDebuggerTree() {
file_dialog = memnew(EditorFileDialog); file_dialog = memnew(EditorFileDialog);
file_dialog->connect("file_selected", callable_mp(this, &EditorDebuggerTree::_file_selected)); file_dialog->connect("file_selected", callable_mp(this, &EditorDebuggerTree::_file_selected));
add_child(file_dialog); add_child(file_dialog);
accept = memnew(AcceptDialog);
add_child(accept);
} }
void EditorDebuggerTree::_notification(int p_what) { void EditorDebuggerTree::_notification(int p_what) {
@ -61,7 +66,8 @@ void EditorDebuggerTree::_notification(int p_what) {
case NOTIFICATION_POSTINITIALIZE: { case NOTIFICATION_POSTINITIALIZE: {
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
connect("cell_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selected)); connect("multi_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selection_changed));
connect("nothing_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_nothing_selected));
connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded)); connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded));
connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected)); connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected));
} break; } break;
@ -73,24 +79,57 @@ void EditorDebuggerTree::_notification(int p_what) {
} }
void EditorDebuggerTree::_bind_methods() { void EditorDebuggerTree::_bind_methods() {
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::INT, "debugger"))); ADD_SIGNAL(MethodInfo("objects_selected", PropertyInfo(Variant::ARRAY, "object_ids"), PropertyInfo(Variant::INT, "debugger")));
ADD_SIGNAL(MethodInfo("selection_cleared", PropertyInfo(Variant::INT, "debugger")));
ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger"))); ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));
ADD_SIGNAL(MethodInfo("open")); ADD_SIGNAL(MethodInfo("open"));
} }
void EditorDebuggerTree::_scene_tree_selected() { void EditorDebuggerTree::_scene_tree_selection_changed(TreeItem *p_item, int p_column, bool p_selected) {
if (updating_scene_tree) { if (updating_scene_tree || !p_item) {
return; return;
} }
TreeItem *item = get_selected(); uint64_t id = uint64_t(p_item->get_metadata(0));
if (!item) { if (p_selected) {
return; if (inspected_object_ids.size() == (int)EDITOR_GET("debugger/max_node_selection")) {
selection_surpassed_limit = true;
p_item->deselect(0);
return;
}
if (!inspected_object_ids.has(id)) {
inspected_object_ids.append(id);
}
} else if (inspected_object_ids.has(id)) {
inspected_object_ids.erase(id);
} }
inspected_object_id = uint64_t(item->get_metadata(0)); if (!notify_selection_queued) {
callable_mp(this, &EditorDebuggerTree::_notify_selection_changed).call_deferred();
notify_selection_queued = true;
}
}
emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id); void EditorDebuggerTree::_scene_tree_nothing_selected() {
deselect_all();
inspected_object_ids.clear();
emit_signal(SNAME("selection_cleared"), debugger_id);
}
void EditorDebuggerTree::_notify_selection_changed() {
notify_selection_queued = false;
if (inspected_object_ids.is_empty()) {
emit_signal(SNAME("selection_cleared"), debugger_id);
} else {
emit_signal(SNAME("objects_selected"), inspected_object_ids.duplicate(), debugger_id);
}
if (selection_surpassed_limit) {
selection_surpassed_limit = false;
EditorToaster::get_singleton()->popup_str(vformat(TTR("Some remote nodes were not selected, as the configured maximum selection is %d. This can be changed at \"debugger/max_node_selection\" in the Editor Settings."), EDITOR_GET("debugger/max_node_selection")), EditorToaster::SEVERITY_WARNING);
}
} }
void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) { void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {
@ -124,7 +163,7 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou
item->select(0); item->select(0);
item_menu->clear(); item_menu->clear();
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene..."), ITEM_MENU_SAVE_REMOTE_NODE);
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE); item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);
item_menu->set_position(get_screen_position() + get_local_mouse_position()); item_menu->set_position(get_screen_position() + get_local_mouse_position());
@ -152,12 +191,13 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = true; updating_scene_tree = true;
const String last_path = get_selected_path(); const String last_path = get_selected_path();
const String filter = SceneTreeDock::get_singleton()->get_filter(); const String filter = SceneTreeDock::get_singleton()->get_filter();
TreeItem *select_item = nullptr; LocalVector<TreeItem *> select_items;
bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"); bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents");
bool should_scroll = scrolling_to_item || filter != last_filter; bool should_scroll = scrolling_to_item || filter != last_filter;
scrolling_to_item = false; scrolling_to_item = false;
TreeItem *scroll_item = nullptr; TreeItem *scroll_item = nullptr;
TypedArray<uint64_t> ids_present;
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion. // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
List<ParentItem> parents; List<ParentItem> parents;
@ -216,9 +256,11 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
} }
item->set_meta("node_path", current_path + "/" + item->get_text(0)); item->set_meta("node_path", current_path + "/" + item->get_text(0));
// Select previously selected node. // Select previously selected nodes.
if (debugger_id == p_debugger) { // Can use remote id. if (debugger_id == p_debugger) { // Can use remote id.
if (node.id == inspected_object_id) { if (inspected_object_ids.has(uint64_t(node.id))) {
ids_present.append(node.id);
if (selection_uncollapse_all) { if (selection_uncollapse_all) {
selection_uncollapse_all = false; selection_uncollapse_all = false;
@ -228,14 +270,14 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = true; updating_scene_tree = true;
} }
select_item = item; select_items.push_back(item);
if (should_scroll) { if (should_scroll) {
scroll_item = item; scroll_item = item;
} }
} }
} else if (last_path == (String)item->get_meta("node_path")) { // Must use path. } else if (last_path == (String)item->get_meta("node_path")) { // Must use path.
updating_scene_tree = false; // Force emission of new selection. updating_scene_tree = false; // Force emission of new selections.
select_item = item; select_items.push_back(item);
if (should_scroll) { if (should_scroll) {
scroll_item = item; scroll_item = item;
} }
@ -280,12 +322,12 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
break; // Filter matches, must survive. break; // Filter matches, must survive.
} }
parent->remove_child(item); if (select_items.has(item) || scroll_item == item) {
memdelete(item); select_items.resize(select_items.size() - 1);
if (select_item == item || scroll_item == item) {
select_item = nullptr;
scroll_item = nullptr; scroll_item = nullptr;
} }
parent->remove_child(item);
memdelete(item);
if (had_siblings) { if (had_siblings) {
break; // Parent must survive. break; // Parent must survive.
@ -316,18 +358,20 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
from->get_parent()->remove_child(from); from->get_parent()->remove_child(from);
memdelete(from); memdelete(from);
if (select_item == from || scroll_item == from) { if (select_items.has(from) || scroll_item == from) {
select_item = nullptr; select_items.erase(from);
scroll_item = nullptr; scroll_item = nullptr;
} }
} }
} }
} }
inspected_object_ids = ids_present;
debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree. debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
if (select_item) { for (TreeItem *item : select_items) {
select_item->select(0); item->select(0);
} }
if (scroll_item) { if (scroll_item) {
scroll_to_item(scroll_item, false); scroll_to_item(scroll_item, false);
@ -337,12 +381,22 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = false; updating_scene_tree = false;
} }
void EditorDebuggerTree::select_node(ObjectID p_id) { void EditorDebuggerTree::select_nodes(const TypedArray<int64_t> &p_ids) {
// Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet). // Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).
selection_uncollapse_all = true; selection_uncollapse_all = true;
inspected_object_id = uint64_t(p_id); inspected_object_ids = p_ids;
scrolling_to_item = true; scrolling_to_item = true;
emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id);
if (!updating_scene_tree) {
// Request a tree refresh.
EditorDebuggerNode::get_singleton()->request_remote_tree();
}
// Set the value immediately, so no update flooding happens and causes a crash.
updating_scene_tree = true;
}
void EditorDebuggerTree::clear_selection() {
inspected_object_ids.clear();
if (!updating_scene_tree) { if (!updating_scene_tree) {
// Request a tree refresh. // Request a tree refresh.
@ -453,8 +507,11 @@ void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
} }
void EditorDebuggerTree::_file_selected(const String &p_file) { void EditorDebuggerTree::_file_selected(const String &p_file) {
if (inspected_object_id.is_null()) { if (inspected_object_ids.size() != 1) {
accept->set_text(vformat(TTR("Saving the branch as a scene requires selecting only one node, but you have selected %d nodes."), inspected_object_ids.size()));
accept->popup_centered();
return; return;
} }
emit_signal(SNAME("save_node"), inspected_object_id, p_file, debugger_id);
emit_signal(SNAME("save_node"), inspected_object_ids[0], p_file, debugger_id);
} }

View file

@ -32,6 +32,7 @@
#include "scene/gui/tree.h" #include "scene/gui/tree.h"
class AcceptDialog;
class SceneDebuggerTree; class SceneDebuggerTree;
class EditorFileDialog; class EditorFileDialog;
@ -57,18 +58,23 @@ private:
ITEM_MENU_EXPAND_COLLAPSE, ITEM_MENU_EXPAND_COLLAPSE,
}; };
ObjectID inspected_object_id; TypedArray<uint64_t> inspected_object_ids;
int debugger_id = 0; int debugger_id = 0;
bool updating_scene_tree = false; bool updating_scene_tree = false;
bool scrolling_to_item = false; bool scrolling_to_item = false;
bool notify_selection_queued = false;
bool selection_surpassed_limit = false;
bool selection_uncollapse_all = false; bool selection_uncollapse_all = false;
HashSet<ObjectID> unfold_cache; HashSet<ObjectID> unfold_cache;
PopupMenu *item_menu = nullptr; PopupMenu *item_menu = nullptr;
EditorFileDialog *file_dialog = nullptr; EditorFileDialog *file_dialog = nullptr;
AcceptDialog *accept = nullptr;
String last_filter; String last_filter;
void _scene_tree_folded(Object *p_obj); void _scene_tree_folded(Object *p_obj);
void _scene_tree_selected(); void _scene_tree_selection_changed(TreeItem *p_item, int p_column, bool p_selected);
void _scene_tree_nothing_selected();
void _notify_selection_changed();
void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button); void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button);
void _item_menu_id_pressed(int p_option); void _item_menu_id_pressed(int p_option);
void _file_selected(const String &p_file); void _file_selected(const String &p_file);
@ -89,7 +95,10 @@ public:
String get_selected_path(); String get_selected_path();
ObjectID get_selected_object(); ObjectID get_selected_object();
int get_current_debugger(); // Would love to have one tree for every debugger. int get_current_debugger(); // Would love to have one tree for every debugger.
inline TypedArray<uint64_t> get_selection() const { return inspected_object_ids.duplicate(); }
void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger); void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger);
void select_node(ObjectID p_id); void select_nodes(const TypedArray<int64_t> &p_ids);
void clear_selection();
EditorDebuggerTree(); EditorDebuggerTree();
}; };

View file

@ -33,10 +33,8 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/debugger/debugger_marshalls.h" #include "core/debugger/debugger_marshalls.h"
#include "core/debugger/remote_debugger.h" #include "core/debugger/remote_debugger.h"
#include "core/io/marshalls.h"
#include "core/string/ustring.h" #include "core/string/ustring.h"
#include "core/version.h" #include "core/version.h"
#include "editor/debugger/debug_adapter/debug_adapter_protocol.h"
#include "editor/debugger/editor_expression_evaluator.h" #include "editor/debugger/editor_expression_evaluator.h"
#include "editor/debugger/editor_performance_profiler.h" #include "editor/debugger/editor_performance_profiler.h"
#include "editor/debugger/editor_profiler.h" #include "editor/debugger/editor_profiler.h"
@ -48,6 +46,7 @@
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_toaster.h"
#include "editor/inspector_dock.h" #include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_debugger_plugin.h"
@ -77,7 +76,8 @@ void ScriptEditorDebugger::_put_msg(const String &p_message, const Array &p_data
msg.push_back(p_message); msg.push_back(p_message);
msg.push_back(p_thread_id); msg.push_back(p_thread_id);
msg.push_back(p_data); msg.push_back(p_data);
peer->put_message(msg); Error err = peer->put_message(msg);
ERR_FAIL_COND_MSG(err != OK, vformat("Failed to send message %d", err));
} }
} }
@ -257,32 +257,44 @@ void ScriptEditorDebugger::request_remote_evaluate(const String &p_expression, i
_put_msg("evaluate", msg); _put_msg("evaluate", msg);
} }
void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) { void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value, const String &p_field) {
Array msg; Array msg;
msg.push_back(p_obj_id); msg.push_back(p_obj_id);
msg.push_back(p_prop); msg.push_back(p_prop);
msg.push_back(p_value); msg.push_back(p_value);
_put_msg("scene:set_object_property", msg); if (p_field.is_empty()) {
_put_msg("scene:set_object_property", msg);
} else {
msg.push_back(p_field);
_put_msg("scene:set_object_property_field", msg);
}
} }
void ScriptEditorDebugger::request_remote_object(ObjectID p_obj_id) { void ScriptEditorDebugger::request_remote_objects(const TypedArray<uint64_t> &p_obj_ids, bool p_update_selection) {
ERR_FAIL_COND(p_obj_id.is_null()); ERR_FAIL_COND(p_obj_ids.is_empty());
Array msg; Array msg;
msg.push_back(p_obj_id); msg.push_back(p_obj_ids.duplicate());
_put_msg("scene:inspect_object", msg); msg.push_back(p_update_selection);
_put_msg("scene:inspect_objects", msg);
} }
Object *ScriptEditorDebugger::get_remote_object(ObjectID p_id) { void ScriptEditorDebugger::clear_inspector(bool p_send_msg) {
return inspector->get_object(p_id); inspector->clear_remote_inspector();
if (p_send_msg) {
_put_msg("scene:clear_selection", Array());
}
} }
void ScriptEditorDebugger::_remote_object_selected(ObjectID p_id) { void ScriptEditorDebugger::_remote_object_selected(ObjectID p_id) {
emit_signal(SNAME("remote_object_requested"), p_id); emit_signal(SNAME("remote_object_requested"), p_id);
} }
void ScriptEditorDebugger::_remote_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { void ScriptEditorDebugger::_remote_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field) {
update_remote_object(p_id, p_prop, p_value); const Array &ids = p_values.keys();
request_remote_object(p_id); for (uint64_t id : ids) {
update_remote_object(ObjectID(id), p_prop, p_values[id], p_field);
}
request_remote_objects(p_values.keys(), false);
} }
void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const String &p_property) { void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const String &p_property) {
@ -405,10 +417,19 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
scene_tree->deserialize(p_data); scene_tree->deserialize(p_data);
emit_signal(SNAME("remote_tree_updated")); emit_signal(SNAME("remote_tree_updated"));
_update_buttons_state(); _update_buttons_state();
} else if (p_msg == "scene:inspect_object") { } else if (p_msg == "scene:inspect_objects") {
ObjectID id = inspector->add_object(p_data); ERR_FAIL_COND(p_data.is_empty());
if (id.is_valid()) {
emit_signal(SNAME("remote_object_updated"), id); TypedArray<uint64_t> ids;
for (const Array arr : p_data) {
ERR_FAIL_COND(arr.is_empty());
ERR_FAIL_COND(arr[0].get_type() != Variant::INT);
ids.append(arr[0]);
}
if (EditorDebuggerNode::get_singleton()->match_remote_selection(ids)) {
EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data);
emit_signal(SNAME("remote_objects_updated"), objs);
} }
} else if (p_msg == "servers:memory_usage") { } else if (p_msg == "servers:memory_usage") {
vmem_tree->clear(); vmem_tree->clear();
@ -809,10 +830,24 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
} else if (p_msg == "request_quit") { } else if (p_msg == "request_quit") {
emit_signal(SNAME("stop_requested")); emit_signal(SNAME("stop_requested"));
_stop_and_notify(); _stop_and_notify();
} else if (p_msg == "remote_node_clicked") { } else if (p_msg == "remote_nodes_clicked") {
if (!p_data.is_empty()) { ERR_FAIL_COND(p_data.is_empty());
emit_signal(SNAME("remote_tree_select_requested"), p_data[0]); EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data);
if (objs) {
EditorDebuggerNode::get_singleton()->stop_waiting_inspection();
emit_signal(SNAME("remote_objects_updated"), objs);
emit_signal(SNAME("remote_tree_select_requested"), objs->remote_object_ids.duplicate());
} }
} else if (p_msg == "remote_nothing_clicked") {
EditorDebuggerNode::get_singleton()->stop_waiting_inspection();
emit_signal(SNAME("remote_tree_clear_selection_requested"));
} else if (p_msg == "remote_selection_invalidated") {
ERR_FAIL_COND(p_data.is_empty());
inspector->invalidate_selection_from_cache(p_data[0]);
} else if (p_msg == "show_selection_limit_warning") {
EditorToaster::get_singleton()->popup_str(vformat(TTR("Some remote nodes were not selected, as the configured maximum selection is %d. This can be changed at \"debugger/max_node_selection\" in the Editor Settings."), EDITOR_GET("debugger/max_node_selection")), EditorToaster::SEVERITY_WARNING);
} else if (p_msg == "performance:profile_names") { } else if (p_msg == "performance:profile_names") {
Vector<StringName> monitors; Vector<StringName> monitors;
monitors.resize(p_data.size()); monitors.resize(p_data.size());
@ -1772,8 +1807,7 @@ void ScriptEditorDebugger::_bind_methods() {
ClassDB::bind_method(D_METHOD("live_debug_restore_node"), &ScriptEditorDebugger::live_debug_restore_node); ClassDB::bind_method(D_METHOD("live_debug_restore_node"), &ScriptEditorDebugger::live_debug_restore_node);
ClassDB::bind_method(D_METHOD("live_debug_duplicate_node"), &ScriptEditorDebugger::live_debug_duplicate_node); ClassDB::bind_method(D_METHOD("live_debug_duplicate_node"), &ScriptEditorDebugger::live_debug_duplicate_node);
ClassDB::bind_method(D_METHOD("live_debug_reparent_node"), &ScriptEditorDebugger::live_debug_reparent_node); ClassDB::bind_method(D_METHOD("live_debug_reparent_node"), &ScriptEditorDebugger::live_debug_reparent_node);
ClassDB::bind_method(D_METHOD("request_remote_object", "id"), &ScriptEditorDebugger::request_remote_object); ClassDB::bind_method(D_METHOD("update_remote_object", "id", "property", "value", "field"), &ScriptEditorDebugger::update_remote_object);
ClassDB::bind_method(D_METHOD("update_remote_object", "id", "property", "value"), &ScriptEditorDebugger::update_remote_object);
ADD_SIGNAL(MethodInfo("started")); ADD_SIGNAL(MethodInfo("started"));
ADD_SIGNAL(MethodInfo("stopped")); ADD_SIGNAL(MethodInfo("stopped"));
@ -1784,12 +1818,12 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"), PropertyInfo(Variant::STRING, "reason"), PropertyInfo(Variant::BOOL, "has_stackdump"))); ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"), PropertyInfo(Variant::STRING, "reason"), PropertyInfo(Variant::BOOL, "has_stackdump")));
ADD_SIGNAL(MethodInfo("remote_object_requested", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("remote_objects_updated", PropertyInfo(Variant::OBJECT, "remote_objects")));
ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
ADD_SIGNAL(MethodInfo("remote_tree_updated"));
ADD_SIGNAL(MethodInfo("remote_tree_select_requested", PropertyInfo(Variant::NODE_PATH, "path")));
ADD_SIGNAL(MethodInfo("remote_window_title_changed", PropertyInfo(Variant::STRING, "title"))); ADD_SIGNAL(MethodInfo("remote_window_title_changed", PropertyInfo(Variant::STRING, "title")));
ADD_SIGNAL(MethodInfo("remote_tree_updated"));
ADD_SIGNAL(MethodInfo("remote_tree_select_requested", PropertyInfo(Variant::ARRAY, "ids")));
ADD_SIGNAL(MethodInfo("remote_tree_clear_selection_requested"));
ADD_SIGNAL(MethodInfo("output", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::INT, "level"))); ADD_SIGNAL(MethodInfo("output", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::INT, "level")));
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump"))); ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars"))); ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
@ -1955,7 +1989,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
inspector->set_property_name_style(EditorPropertyNameProcessor::STYLE_RAW); inspector->set_property_name_style(EditorPropertyNameProcessor::STYLE_RAW);
inspector->set_read_only(true); inspector->set_read_only(true);
inspector->connect("object_selected", callable_mp(this, &ScriptEditorDebugger::_remote_object_selected)); inspector->connect("object_selected", callable_mp(this, &ScriptEditorDebugger::_remote_object_selected));
inspector->connect("object_edited", callable_mp(this, &ScriptEditorDebugger::_remote_object_edited)); inspector->connect("objects_edited", callable_mp(this, &ScriptEditorDebugger::_remote_objects_edited));
inspector->connect("object_property_updated", callable_mp(this, &ScriptEditorDebugger::_remote_object_property_updated)); inspector->connect("object_property_updated", callable_mp(this, &ScriptEditorDebugger::_remote_object_property_updated));
inspector->register_text_enter(search); inspector->register_text_enter(search);
inspector->set_use_filter(true); inspector->set_use_filter(true);
@ -2169,9 +2203,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
msgdialog = memnew(AcceptDialog); msgdialog = memnew(AcceptDialog);
add_child(msgdialog); add_child(msgdialog);
live_debug = true;
camera_override = CameraOverride::OVERRIDE_NONE; camera_override = CameraOverride::OVERRIDE_NONE;
last_path_id = false;
error_count = 0; error_count = 0;
warning_count = 0; warning_count = 0;
_update_buttons_state(); _update_buttons_state();

View file

@ -34,7 +34,6 @@
#include "core/os/os.h" #include "core/os/os.h"
#include "editor/debugger/editor_debugger_inspector.h" #include "editor/debugger/editor_debugger_inspector.h"
#include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/editor_debugger_server.h"
#include "scene/gui/button.h" #include "scene/gui/button.h"
#include "scene/gui/margin_container.h" #include "scene/gui/margin_container.h"
@ -146,7 +145,7 @@ private:
Ref<RemoteDebuggerPeer> peer; Ref<RemoteDebuggerPeer> peer;
HashMap<NodePath, int> node_path_cache; HashMap<NodePath, int> node_path_cache;
int last_path_id; int last_path_id = 0;
HashMap<String, int> res_path_cache; HashMap<String, int> res_path_cache;
EditorProfiler *profiler = nullptr; EditorProfiler *profiler = nullptr;
@ -158,7 +157,7 @@ private:
bool move_to_foreground = true; bool move_to_foreground = true;
bool can_request_idle_draw = false; bool can_request_idle_draw = false;
bool live_debug; bool live_debug = true;
uint64_t debugging_thread_id = Thread::UNASSIGNED_ID; uint64_t debugging_thread_id = Thread::UNASSIGNED_ID;
@ -191,7 +190,7 @@ private:
void _set_reason_text(const String &p_reason, MessageType p_type); void _set_reason_text(const String &p_reason, MessageType p_type);
void _update_buttons_state(); void _update_buttons_state();
void _remote_object_selected(ObjectID p_object); void _remote_object_selected(ObjectID p_object);
void _remote_object_edited(ObjectID, const String &p_prop, const Variant &p_value); void _remote_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field);
void _remote_object_property_updated(ObjectID p_id, const String &p_property); void _remote_object_property_updated(ObjectID p_id, const String &p_property);
void _video_mem_request(); void _video_mem_request();
@ -245,9 +244,10 @@ protected:
static void _bind_methods(); static void _bind_methods();
public: public:
void request_remote_object(ObjectID p_obj_id); void request_remote_objects(const TypedArray<uint64_t> &p_obj_ids, bool p_update_selection = true);
void update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value); void update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value, const String &p_field = "");
Object *get_remote_object(ObjectID p_id);
void clear_inspector(bool p_send_msg = true);
// Needed by _live_edit_set, buttons state. // Needed by _live_edit_set, buttons state.
void set_editor_remote_tree(const Tree *p_tree) { editor_remote_tree = p_tree; } void set_editor_remote_tree(const Tree *p_tree) { editor_remote_tree = p_tree; }

View file

@ -33,6 +33,7 @@
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "editor/add_metadata_dialog.h" #include "editor/add_metadata_dialog.h"
#include "editor/debugger/editor_debugger_inspector.h"
#include "editor/doc_tools.h" #include "editor/doc_tools.h"
#include "editor/editor_feature_profile.h" #include "editor/editor_feature_profile.h"
#include "editor/editor_main_screen.h" #include "editor/editor_main_screen.h"
@ -4282,6 +4283,10 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo
Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field); Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field);
_edit_request_change(object, p_name); _edit_request_change(object, p_name);
emit_signal(_prop_edited, p_name); emit_signal(_prop_edited, p_name);
} else if (Object::cast_to<EditorDebuggerRemoteObjects>(object)) {
Object::cast_to<EditorDebuggerRemoteObjects>(object)->set_property_field(p_name, p_value, p_changed_field);
_edit_request_change(object, p_name);
emit_signal(_prop_edited, p_name);
} else { } else {
undo_redo->create_action(vformat(TTR("Set %s"), p_name), UndoRedo::MERGE_ENDS); undo_redo->create_action(vformat(TTR("Set %s"), p_name), UndoRedo::MERGE_ENDS);
undo_redo->add_do_property(object, p_name, p_value); undo_redo->add_do_property(object, p_name, p_value);

View file

@ -2546,12 +2546,13 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
InspectorDock::get_inspector_singleton()->edit(nullptr); InspectorDock::get_inspector_singleton()->edit(nullptr);
NodeDock::get_singleton()->set_node(nullptr); NodeDock::get_singleton()->set_node(nullptr);
InspectorDock::get_singleton()->update(nullptr); InspectorDock::get_singleton()->update(nullptr);
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
hide_unused_editors(); hide_unused_editors();
return; return;
} }
// Update the use folding setting and state. // Update the use folding setting and state.
bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")) || current_obj->is_class("EditorDebuggerRemoteObject"); bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")) || current_obj->is_class("EditorDebuggerRemoteObjects");
if (InspectorDock::get_inspector_singleton()->is_using_folding() == disable_folding) { if (InspectorDock::get_inspector_singleton()->is_using_folding() == disable_folding) {
InspectorDock::get_inspector_singleton()->set_use_folding(!disable_folding, false); InspectorDock::get_inspector_singleton()->set_use_folding(!disable_folding, false);
} }
@ -2579,6 +2580,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
SceneTreeDock::get_singleton()->set_selected(nullptr); SceneTreeDock::get_singleton()->set_selected(nullptr);
NodeDock::get_singleton()->set_node(nullptr); NodeDock::get_singleton()->set_node(nullptr);
InspectorDock::get_singleton()->update(nullptr); InspectorDock::get_singleton()->update(nullptr);
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
ImportDock::get_singleton()->set_edit_path(current_res->get_path()); ImportDock::get_singleton()->set_edit_path(current_res->get_path());
} }
@ -2618,6 +2620,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
SceneTreeDock::get_singleton()->set_selected(nullptr); SceneTreeDock::get_singleton()->set_selected(nullptr);
InspectorDock::get_singleton()->update(nullptr); InspectorDock::get_singleton()->update(nullptr);
} }
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
if (get_edited_scene() && !get_edited_scene()->get_scene_file_path().is_empty()) { if (get_edited_scene() && !get_edited_scene()->get_scene_file_path().is_empty()) {
String source_scene = get_edited_scene()->get_scene_file_path(); String source_scene = get_edited_scene()->get_scene_file_path();
@ -2626,7 +2629,6 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
info_is_warning = true; info_is_warning = true;
} }
} }
} else { } else {
Node *selected_node = nullptr; Node *selected_node = nullptr;
@ -2652,6 +2654,10 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
} }
} }
if (!current_obj->is_class("EditorDebuggerRemoteObjects")) {
EditorDebuggerNode::get_singleton()->clear_remote_tree_selection();
}
InspectorDock::get_inspector_singleton()->edit(current_obj); InspectorDock::get_inspector_singleton()->edit(current_obj);
NodeDock::get_singleton()->set_node(nullptr); NodeDock::get_singleton()->set_node(nullptr);
SceneTreeDock::get_singleton()->set_selected(selected_node); SceneTreeDock::get_singleton()->set_selected(selected_node);
@ -4945,7 +4951,8 @@ Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String
Ref<Script> scr = p_object->get_script(); Ref<Script> scr = p_object->get_script();
if (Object::cast_to<EditorDebuggerRemoteObject>(p_object)) { const EditorDebuggerRemoteObjects *robjs = Object::cast_to<EditorDebuggerRemoteObjects>(p_object);
if (robjs) {
String class_name; String class_name;
if (scr.is_valid()) { if (scr.is_valid()) {
class_name = scr->get_global_name(); class_name = scr->get_global_name();
@ -4955,7 +4962,12 @@ Ref<Texture2D> EditorNode::get_object_icon(const Object *p_object, const String
class_name = scr->get_path(); class_name = scr->get_path();
} }
} }
return get_class_icon(class_name.is_empty() ? Object::cast_to<EditorDebuggerRemoteObject>(p_object)->type_name : class_name, p_fallback);
if (class_name.is_empty()) {
return get_class_icon(robjs->type_name, p_fallback);
}
return get_class_icon(class_name, p_fallback);
} }
if (scr.is_null() && p_object->is_class("Script")) { if (scr.is_null() && p_object->is_class("Script")) {

View file

@ -1007,6 +1007,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "") EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "")
EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_stack_trace", true, "") EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_stack_trace", true, "")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/max_node_selection", 20, "1,100,1")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_target_fps", 60, "1,1000,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_target_fps", 60, "1,1000,1")

View file

@ -61,7 +61,7 @@ UndoRedo *EditorUndoRedoManager::get_history_undo_redo(int p_idx) const {
int EditorUndoRedoManager::get_history_id_for_object(Object *p_object) const { int EditorUndoRedoManager::get_history_id_for_object(Object *p_object) const {
int history_id = INVALID_HISTORY; int history_id = INVALID_HISTORY;
if (Object::cast_to<EditorDebuggerRemoteObject>(p_object)) { if (Object::cast_to<EditorDebuggerRemoteObjects>(p_object)) {
return REMOTE_HISTORY; return REMOTE_HISTORY;
} }

View file

@ -128,7 +128,6 @@ void EditorObjectSelector::update_path() {
} }
Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj); Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
if (obj_icon.is_valid()) { if (obj_icon.is_valid()) {
current_object_icon->set_texture(obj_icon); current_object_icon->set_texture(obj_icon);
} }
@ -148,7 +147,7 @@ void EditorObjectSelector::update_path() {
if (name.is_empty()) { if (name.is_empty()) {
name = r->get_class(); name = r->get_class();
} }
} else if (obj->is_class("EditorDebuggerRemoteObject")) { } else if (obj->is_class("EditorDebuggerRemoteObjects")) {
name = obj->call("get_title"); name = obj->call("get_title");
} else if (Object::cast_to<Node>(obj)) { } else if (Object::cast_to<Node>(obj)) {
name = Object::cast_to<Node>(obj)->get_name(); name = Object::cast_to<Node>(obj)->get_name();

View file

@ -30,6 +30,8 @@
#include "inspector_dock.h" #include "inspector_dock.h"
#include "editor/debugger/editor_debugger_inspector.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_main_screen.h" #include "editor/editor_main_screen.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
@ -351,7 +353,7 @@ void InspectorDock::_prepare_history() {
} }
} else if (Object::cast_to<Node>(obj)) { } else if (Object::cast_to<Node>(obj)) {
text = Object::cast_to<Node>(obj)->get_name(); text = Object::cast_to<Node>(obj)->get_name();
} else if (obj->is_class("EditorDebuggerRemoteObject")) { } else if (obj->is_class("EditorDebuggerRemoteObjects")) {
text = obj->call("get_title"); text = obj->call("get_title");
} else { } else {
text = obj->get_class(); text = obj->get_class();
@ -372,6 +374,10 @@ void InspectorDock::_select_history(int p_idx) {
return; return;
} }
EditorNode::get_singleton()->push_item(obj); EditorNode::get_singleton()->push_item(obj);
if (const EditorDebuggerRemoteObjects *robjs = Object::cast_to<EditorDebuggerRemoteObjects>(obj)) {
EditorDebuggerNode::get_singleton()->set_remote_selection(robjs->remote_object_ids.duplicate());
}
} }
void InspectorDock::_resource_created() { void InspectorDock::_resource_created() {
@ -396,6 +402,10 @@ void InspectorDock::_resource_selected(const Ref<Resource> &p_res, const String
void InspectorDock::_edit_forward() { void InspectorDock::_edit_forward() {
if (EditorNode::get_singleton()->get_editor_selection_history()->next()) { if (EditorNode::get_singleton()->get_editor_selection_history()->next()) {
EditorNode::get_singleton()->edit_current(); EditorNode::get_singleton()->edit_current();
if (const EditorDebuggerRemoteObjects *robjs = Object::cast_to<EditorDebuggerRemoteObjects>(current)) {
EditorDebuggerNode::get_singleton()->set_remote_selection(robjs->remote_object_ids.duplicate());
}
} }
} }
@ -403,6 +413,10 @@ void InspectorDock::_edit_back() {
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history(); EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
if ((current && editor_history->previous()) || editor_history->get_path_size() == 1) { if ((current && editor_history->previous()) || editor_history->get_path_size() == 1) {
EditorNode::get_singleton()->edit_current(); EditorNode::get_singleton()->edit_current();
if (const EditorDebuggerRemoteObjects *robjs = Object::cast_to<EditorDebuggerRemoteObjects>(current)) {
EditorDebuggerNode::get_singleton()->set_remote_selection(robjs->remote_object_ids.duplicate());
}
} }
} }

View file

@ -35,7 +35,6 @@
#include "core/string/translation_server.h" #include "core/string/translation_server.h"
#include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h" #include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h" #include "editor/editor_feature_profile.h"
#include "editor/editor_interface.h" #include "editor/editor_interface.h"
#include "editor/editor_main_screen.h" #include "editor/editor_main_screen.h"
@ -58,13 +57,16 @@ void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
return; return;
} }
Array setup_data;
Dictionary settings; Dictionary settings;
settings["debugger/max_node_selection"] = EDITOR_GET("debugger/max_node_selection");
settings["editors/panning/2d_editor_panning_scheme"] = EDITOR_GET("editors/panning/2d_editor_panning_scheme"); settings["editors/panning/2d_editor_panning_scheme"] = EDITOR_GET("editors/panning/2d_editor_panning_scheme");
settings["editors/panning/simple_panning"] = EDITOR_GET("editors/panning/simple_panning"); settings["editors/panning/simple_panning"] = EDITOR_GET("editors/panning/simple_panning");
settings["editors/panning/warped_mouse_panning"] = EDITOR_GET("editors/panning/warped_mouse_panning"); settings["editors/panning/warped_mouse_panning"] = EDITOR_GET("editors/panning/warped_mouse_panning");
settings["editors/panning/2d_editor_pan_speed"] = EDITOR_GET("editors/panning/2d_editor_pan_speed"); settings["editors/panning/2d_editor_pan_speed"] = EDITOR_GET("editors/panning/2d_editor_pan_speed");
settings["editors/polygon_editor/point_grab_radius"] = EDITOR_GET("editors/polygon_editor/point_grab_radius");
settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view")); settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view"));
settings["box_selection_fill_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_fill_color"), EditorStringName(Editor));
settings["box_selection_stroke_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor));
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
settings["editors/3d/default_fov"] = EDITOR_GET("editors/3d/default_fov"); settings["editors/3d/default_fov"] = EDITOR_GET("editors/3d/default_fov");
settings["editors/3d/default_z_near"] = EDITOR_GET("editors/3d/default_z_near"); settings["editors/3d/default_z_near"] = EDITOR_GET("editors/3d/default_z_near");
@ -76,7 +78,11 @@ void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
settings["editors/3d/freelook/freelook_sensitivity"] = EDITOR_GET("editors/3d/freelook/freelook_sensitivity"); settings["editors/3d/freelook/freelook_sensitivity"] = EDITOR_GET("editors/3d/freelook/freelook_sensitivity");
settings["editors/3d/navigation_feel/orbit_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity"); settings["editors/3d/navigation_feel/orbit_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");
settings["editors/3d/navigation_feel/translation_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity"); settings["editors/3d/navigation_feel/translation_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");
settings["editors/3d/selection_box_color"] = EDITOR_GET("editors/3d/selection_box_color");
settings["editors/3d/freelook/freelook_base_speed"] = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
#endif // _3D_DISABLED #endif // _3D_DISABLED
Array setup_data;
setup_data.append(settings); setup_data.append(settings);
p_session->send_message("scene:runtime_node_select_setup", setup_data); p_session->send_message("scene:runtime_node_select_setup", setup_data);
@ -422,6 +428,8 @@ void GameView::_select_mode_pressed(int p_option) {
} }
debugger->set_select_mode(mode); debugger->set_select_mode(mode);
EditorSettings::get_singleton()->set_project_metadata("game_view", "select_mode", mode);
} }
void GameView::_embed_options_menu_menu_id_pressed(int p_id) { void GameView::_embed_options_menu_menu_id_pressed(int p_id) {
@ -581,6 +589,8 @@ void GameView::_hide_selection_toggled(bool p_pressed) {
hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
debugger->set_selection_visible(!p_pressed); debugger->set_selection_visible(!p_pressed);
EditorSettings::get_singleton()->set_project_metadata("game_view", "hide_selection", p_pressed);
} }
void GameView::_camera_override_button_toggled(bool p_pressed) { void GameView::_camera_override_button_toggled(bool p_pressed) {
@ -609,12 +619,16 @@ void GameView::_camera_override_menu_id_pressed(int p_id) {
menu->set_item_checked(menu->get_item_index(p_id), true); menu->set_item_checked(menu->get_item_index(p_id), true);
_update_debugger_buttons(); _update_debugger_buttons();
EditorSettings::get_singleton()->set_project_metadata("game_view", "camera_override_mode", p_id);
} break; } break;
case CAMERA_MODE_EDITORS: { case CAMERA_MODE_EDITORS: {
debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_EDITORS); debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_EDITORS);
menu->set_item_checked(menu->get_item_index(p_id), true); menu->set_item_checked(menu->get_item_index(p_id), true);
_update_debugger_buttons(); _update_debugger_buttons();
EditorSettings::get_singleton()->set_project_metadata("game_view", "camera_override_mode", p_id);
} break; } break;
} }
} }
@ -700,41 +714,6 @@ void GameView::set_is_feature_enabled(bool p_enabled) {
is_feature_enabled = p_enabled; is_feature_enabled = p_enabled;
} }
void GameView::set_state(const Dictionary &p_state) {
if (p_state.has("hide_selection")) {
hide_selection->set_pressed(p_state["hide_selection"]);
_hide_selection_toggled(hide_selection->is_pressed());
}
if (p_state.has("select_mode")) {
_select_mode_pressed(p_state["select_mode"]);
}
if (p_state.has("camera_override_mode")) {
_camera_override_menu_id_pressed(p_state["camera_override_mode"]);
}
}
Dictionary GameView::get_state() const {
Dictionary d;
d["hide_selection"] = hide_selection->is_pressed();
for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
if (select_mode_button[i]->is_pressed()) {
d["select_mode"] = i;
break;
}
}
PopupMenu *menu = camera_override_menu->get_popup();
for (int i = CAMERA_MODE_INGAME; i < CAMERA_MODE_EDITORS + 1; i++) {
if (menu->is_item_checked(menu->get_item_index(i))) {
d["camera_override_mode"] = i;
break;
}
}
return d;
}
void GameView::set_window_layout(Ref<ConfigFile> p_layout) { void GameView::set_window_layout(Ref<ConfigFile> p_layout) {
floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i()); floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i());
floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1); floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1);
@ -959,6 +938,7 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
hide_selection->set_theme_type_variation(SceneStringName(FlatButton)); hide_selection->set_theme_type_variation(SceneStringName(FlatButton));
hide_selection->connect(SceneStringName(toggled), callable_mp(this, &GameView::_hide_selection_toggled)); hide_selection->connect(SceneStringName(toggled), callable_mp(this, &GameView::_hide_selection_toggled));
hide_selection->set_tooltip_text(TTR("Toggle Selection Visibility")); hide_selection->set_tooltip_text(TTR("Toggle Selection Visibility"));
hide_selection->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "hide_selection", false));
main_menu_hbox->add_child(memnew(VSeparator)); main_menu_hbox->add_child(memnew(VSeparator));
@ -979,14 +959,16 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST)); select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST));
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.")); select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked."));
_select_mode_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "select_mode", 0));
main_menu_hbox->add_child(memnew(VSeparator)); main_menu_hbox->add_child(memnew(VSeparator));
camera_override_button = memnew(Button); camera_override_button = memnew(Button);
main_menu_hbox->add_child(camera_override_button); main_menu_hbox->add_child(camera_override_button);
camera_override_button->set_toggle_mode(true); camera_override_button->set_toggle_mode(true);
camera_override_button->set_theme_type_variation(SceneStringName(FlatButton)); camera_override_button->set_theme_type_variation(SceneStringName(FlatButton));
camera_override_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_camera_override_button_toggled));
camera_override_button->set_tooltip_text(TTR("Override the in-game camera.")); camera_override_button->set_tooltip_text(TTR("Override the in-game camera."));
camera_override_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_camera_override_button_toggled));
camera_override_menu = memnew(MenuButton); camera_override_menu = memnew(MenuButton);
main_menu_hbox->add_child(camera_override_menu); main_menu_hbox->add_child(camera_override_menu);
@ -994,6 +976,7 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
camera_override_menu->set_theme_type_variation("FlatMenuButton"); camera_override_menu->set_theme_type_variation("FlatMenuButton");
camera_override_menu->set_h_size_flags(SIZE_SHRINK_END); camera_override_menu->set_h_size_flags(SIZE_SHRINK_END);
camera_override_menu->set_tooltip_text(TTR("Camera Override Options")); camera_override_menu->set_tooltip_text(TTR("Camera Override Options"));
_camera_override_menu_id_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "camera_override_mode", 0));
PopupMenu *menu = camera_override_menu->get_popup(); PopupMenu *menu = camera_override_menu->get_popup();
menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed)); menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed));
@ -1098,7 +1081,7 @@ void GameViewPlugin::selected_notify() {
notify_main_screen_changed(get_plugin_name()); notify_main_screen_changed(get_plugin_name());
#else #else
window_wrapper->grab_window_focus(); window_wrapper->grab_window_focus();
#endif #endif // ANDROID_ENABLED
_focus_another_editor(); _focus_another_editor();
} }
} }
@ -1119,19 +1102,7 @@ void GameViewPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
void GameViewPlugin::get_window_layout(Ref<ConfigFile> p_layout) { void GameViewPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
game_view->get_window_layout(p_layout); game_view->get_window_layout(p_layout);
} }
#endif // ANDROID_ENABLED
void GameViewPlugin::set_state(const Dictionary &p_state) {
game_view->set_state(p_state);
}
Dictionary GameViewPlugin::get_state() const {
return game_view->get_state();
}
void GameViewPlugin::_window_visibility_changed(bool p_visible) {
_focus_another_editor();
}
#endif
void GameViewPlugin::_notification(int p_what) { void GameViewPlugin::_notification(int p_what) {
switch (p_what) { switch (p_what) {
@ -1161,7 +1132,7 @@ void GameViewPlugin::_feature_profile_changed() {
if (game_view) { if (game_view) {
game_view->set_is_feature_enabled(is_feature_enabled); game_view->set_is_feature_enabled(is_feature_enabled);
} }
#endif #endif // ANDROID_ENABLED
} }
void GameViewPlugin::_save_last_editor(const String &p_editor) { void GameViewPlugin::_save_last_editor(const String &p_editor) {
@ -1185,7 +1156,7 @@ bool GameViewPlugin::_is_window_wrapper_enabled() const {
return true; return true;
#else #else
return window_wrapper->get_window_enabled(); return window_wrapper->get_window_enabled();
#endif #endif // ANDROID_ENABLED
} }
GameViewPlugin::GameViewPlugin() { GameViewPlugin::GameViewPlugin() {
@ -1204,8 +1175,8 @@ GameViewPlugin::GameViewPlugin() {
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper); EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL); window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide(); window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_window_visibility_changed)); window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_focus_another_editor).unbind(1));
#endif #endif // ANDROID_ENABLED
EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewPlugin::_feature_profile_changed)); EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewPlugin::_feature_profile_changed));
} }

View file

@ -212,7 +212,7 @@ class GameViewPlugin : public EditorPlugin {
#ifndef ANDROID_ENABLED #ifndef ANDROID_ENABLED
GameView *game_view = nullptr; GameView *game_view = nullptr;
WindowWrapper *window_wrapper = nullptr; WindowWrapper *window_wrapper = nullptr;
#endif #endif // ANDROID_ENABLED
Ref<GameViewDebugger> debugger; Ref<GameViewDebugger> debugger;
@ -221,7 +221,7 @@ class GameViewPlugin : public EditorPlugin {
void _feature_profile_changed(); void _feature_profile_changed();
#ifndef ANDROID_ENABLED #ifndef ANDROID_ENABLED
void _window_visibility_changed(bool p_visible); void _window_visibility_changed(bool p_visible);
#endif #endif // ANDROID_ENABLED
void _save_last_editor(const String &p_editor); void _save_last_editor(const String &p_editor);
void _focus_another_editor(); void _focus_another_editor();
bool _is_window_wrapper_enabled() const; bool _is_window_wrapper_enabled() const;
@ -243,10 +243,7 @@ public:
virtual void set_window_layout(Ref<ConfigFile> p_layout) override; virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override; virtual void get_window_layout(Ref<ConfigFile> p_layout) override;
#endif // ANDROID_ENABLED
virtual void set_state(const Dictionary &p_state) override;
virtual Dictionary get_state() const override;
#endif
GameViewPlugin(); GameViewPlugin();
~GameViewPlugin(); ~GameViewPlugin();

File diff suppressed because it is too large Load diff

View file

@ -36,11 +36,16 @@
#include "core/templates/pair.h" #include "core/templates/pair.h"
#include "core/variant/array.h" #include "core/variant/array.h"
#include "scene/gui/view_panner.h" #include "scene/gui/view_panner.h"
#ifndef _3D_DISABLED
#include "scene/resources/mesh.h" #include "scene/resources/mesh.h"
#endif // _3D_DISABLED
class CanvasItem;
class PopupMenu; class PopupMenu;
class Script; class Script;
class Node; #ifndef _3D_DISABLED
class Node3D;
#endif // _3D_DISABLED
class SceneDebugger { class SceneDebugger {
private: private:
@ -60,8 +65,8 @@ private:
static void _save_node(ObjectID id, const String &p_path); static void _save_node(ObjectID id, const String &p_path);
static void _set_node_owner_recursive(Node *p_node, Node *p_owner); static void _set_node_owner_recursive(Node *p_node, Node *p_owner);
static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value, const String &p_field = "");
static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20); static void _send_object_ids(const Vector<ObjectID> &p_ids, bool p_update_selection);
static void _next_frame(); static void _next_frame();
public: public:
@ -179,30 +184,49 @@ public:
NODE_TYPE_NONE, NODE_TYPE_NONE,
NODE_TYPE_2D, NODE_TYPE_2D,
NODE_TYPE_3D, NODE_TYPE_3D,
NODE_TYPE_MAX NODE_TYPE_MAX,
}; };
enum SelectMode { enum SelectMode {
SELECT_MODE_SINGLE, SELECT_MODE_SINGLE,
SELECT_MODE_LIST, SELECT_MODE_LIST,
SELECT_MODE_MAX SELECT_MODE_MAX,
}; };
private: private:
friend class SceneDebugger; friend class SceneDebugger;
NodeType node_select_type = NODE_TYPE_2D;
SelectMode node_select_mode = SELECT_MODE_SINGLE;
struct SelectResult { struct SelectResult {
Node *item = nullptr; Node *item = nullptr;
real_t order = 0; real_t order = 0;
_FORCE_INLINE_ bool operator<(const SelectResult &p_rr) const { return p_rr.order < order; } _FORCE_INLINE_ bool operator<(const SelectResult &p_rr) const { return p_rr.order < order; }
}; };
const int SELECTION_MIN_AREA = 8 * 8;
enum SelectionDragState {
SELECTION_DRAG_NONE,
SELECTION_DRAG_MOVE,
SELECTION_DRAG_END,
};
SelectionDragState selection_drag_state = SELECTION_DRAG_NONE;
bool has_selection = false; bool has_selection = false;
Node *selected_node = nullptr; int max_selection = 1;
Point2 selection_position = Point2(INFINITY, INFINITY);
Rect2 selection_drag_area;
PopupMenu *selection_list = nullptr; PopupMenu *selection_list = nullptr;
Color selection_area_fill;
Color selection_area_outline;
bool selection_visible = true; bool selection_visible = true;
bool selection_update_queued = false; bool selection_update_queued = false;
bool warped_panning = false;
bool multi_shortcut_pressed = false;
bool list_shortcut_pressed = false;
RID draw_canvas;
RID sel_drag_ci;
bool camera_override = false; bool camera_override = false;
@ -213,11 +237,12 @@ private:
Ref<ViewPanner> panner; Ref<ViewPanner> panner;
Vector2 view_2d_offset; Vector2 view_2d_offset;
real_t view_2d_zoom = 1.0; real_t view_2d_zoom = 1.0;
bool warped_panning = false;
LocalVector<ObjectID> selected_ci_nodes;
real_t sel_2d_grab_dist = 0;
RID sbox_2d_canvas;
RID sbox_2d_ci; RID sbox_2d_ci;
Transform2D sbox_2d_xform;
Rect2 sbox_2d_rect;
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
struct Cursor { struct Cursor {
@ -264,22 +289,35 @@ private:
Vector2 previous_mouse_position; Vector2 previous_mouse_position;
struct SelectionBox3D : public RefCounted {
RID instance;
RID instance_ofs;
RID instance_xray;
RID instance_xray_ofs;
Transform3D transform;
AABB bounds;
~SelectionBox3D() {
if (instance.is_valid()) {
RS::get_singleton()->free(instance);
RS::get_singleton()->free(instance_ofs);
RS::get_singleton()->free(instance_xray);
RS::get_singleton()->free(instance_xray_ofs);
}
}
};
HashMap<ObjectID, Ref<SelectionBox3D>> selected_3d_nodes;
Color sbox_3d_color;
Ref<ArrayMesh> sbox_3d_mesh; Ref<ArrayMesh> sbox_3d_mesh;
Ref<ArrayMesh> sbox_3d_mesh_xray; Ref<ArrayMesh> sbox_3d_mesh_xray;
RID sbox_3d_instance; RID sbox_3d;
RID sbox_3d_instance_ofs; RID sbox_3d_ofs;
RID sbox_3d_instance_xray; RID sbox_3d_xray;
RID sbox_3d_instance_xray_ofs; RID sbox_3d_xray_ofs;
Transform3D sbox_3d_xform;
AABB sbox_3d_bounds;
#endif // _3D_DISABLED #endif // _3D_DISABLED
Point2 selection_position = Point2(INFINITY, INFINITY);
bool list_shortcut_pressed = false;
NodeType node_select_type = NODE_TYPE_2D;
SelectMode node_select_mode = SELECT_MODE_SINGLE;
void _setup(const Dictionary &p_settings); void _setup(const Dictionary &p_settings);
void _node_set_type(NodeType p_type); void _node_set_type(NodeType p_type);
@ -294,17 +332,19 @@ private:
void _process_frame(); void _process_frame();
void _physics_frame(); void _physics_frame();
void _click_point(); void _send_ids(const Vector<Node *> &p_picked_nodes, bool p_invert_new_selections = true);
void _select_node(Node *p_node); void _set_selected_nodes(const Vector<Node *> &p_nodes);
void _queue_selection_update(); void _queue_selection_update();
void _update_selection(); void _update_selection();
void _clear_selection(); void _clear_selection();
void _update_selection_drag(const Point2 &p_end_pos = Point2());
void _set_selection_visible(bool p_visible); void _set_selection_visible(bool p_visible);
void _open_selection_list(const Vector<SelectResult> &p_items, const Point2 &p_pos); void _open_selection_list(const Vector<SelectResult> &p_items, const Point2 &p_pos);
void _close_selection_list(); void _close_selection_list();
void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
void _find_canvas_items_at_rect(const Rect2 &p_rect, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _reset_camera_2d(); void _reset_camera_2d();
@ -312,6 +352,9 @@ private:
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
void _find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items); void _find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items);
void _find_3d_items_at_rect(const Rect2 &p_rect, Vector<SelectResult> &r_items);
Vector3 _get_screen_to_space(const Vector3 &p_vector3);
bool _handle_3d_input(const Ref<InputEvent> &p_event); bool _handle_3d_input(const Ref<InputEvent> &p_event);
void _set_camera_freelook_enabled(bool p_enabled); void _set_camera_freelook_enabled(bool p_enabled);
void _cursor_scale_distance(real_t p_scale); void _cursor_scale_distance(real_t p_scale);
@ -322,7 +365,7 @@ private:
Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_border) const; Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_border) const;
Transform3D _get_cursor_transform(); Transform3D _get_cursor_transform();
void _reset_camera_3d(); void _reset_camera_3d();
#endif #endif // _3D_DISABLED
RuntimeNodeSelect() { singleton = this; } RuntimeNodeSelect() { singleton = this; }
@ -333,4 +376,4 @@ public:
~RuntimeNodeSelect(); ~RuntimeNodeSelect();
}; };
#endif #endif // DEBUG_ENABLED