diff options
Diffstat (limited to 'editor')
54 files changed, 2736 insertions, 408 deletions
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index 945291b163..fbd3aaa409 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -33,12 +33,15 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" +#include "editor/editor_run_native.h" void DebugAdapterParser::_bind_methods() { // Requests ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize); - ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::prepare_success_response); + ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect); ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch); + ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach); + ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart); ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate); ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response); ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause); @@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() { ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads); ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace); ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints); + ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations); ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes); ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables); ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next); ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn); + ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate); + ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg); } Dictionary DebugAdapterParser::prepare_base_event() const { @@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params DAP::Message message; String error, error_desc; switch (err_type) { + case DAP::ErrorType::WRONG_PATH: + error = "wrong_path"; + error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; + break; + case DAP::ErrorType::NOT_RUNNING: + error = "not_running"; + error_desc = "Can't attach to a running session since there isn't one."; + break; + case DAP::ErrorType::TIMEOUT: + error = "timeout"; + error_desc = "Timeout reached while processing a request."; + break; + case DAP::ErrorType::UNKNOWN_PLATFORM: + error = "unknown_platform"; + error_desc = "The specified platform is unknown."; + break; + case DAP::ErrorType::MISSING_DEVICE: + error = "missing_device"; + error_desc = "There's no connected device with specified id."; + break; case DAP::ErrorType::UNKNOWN: + default: error = "unknown"; error_desc = "An unknown error has ocurred when processing the request."; break; - case DAP::ErrorType::WRONG_PATH: - error = "wrong_path"; - error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; } message.id = err_type; @@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const DebugAdapterProtocol::get_singleton()->notify_initialized(); + if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) { + // Send all current breakpoints + List<String> breakpoints; + ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); + for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { + String breakpoint = E->get(); + + String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://" + int line = breakpoint.substr(path.size()).to_int(); + + DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true); + } + } else { + // Remove all current breakpoints + EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints(); + } + return response; } -Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) { + EditorNode::get_singleton()->run_stop(); + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const { Dictionary args = p_params["arguments"]; if (args.has("project") && !is_valid_path(args["project"])) { Dictionary variables; @@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) { return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); } + if (args.has("godot/custom_data")) { + DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"]; + } + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) { dbg->debug_skip_breakpoints(); } - EditorNode::get_singleton()->run_play(); + String platform_string = args.get("platform", "host"); + if (platform_string == "host") { + EditorNode::get_singleton()->run_play(); + } else { + int device = args.get("device", -1); + int idx = -1; + if (platform_string == "android") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") { + idx = i; + break; + } + } + } else if (platform_string == "web") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") { + idx = i; + break; + } + } + } + + if (idx == -1) { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM); + } + + EditorNode *editor = EditorNode::get_singleton(); + Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx); + if (err) { + if (err == ERR_INVALID_PARAMETER && platform_string == "android") { + return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE); + } else { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN); + } + } + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false; DebugAdapterProtocol::get_singleton()->notify_process(); return prepare_success_response(p_params); } +Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const { + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if (!dbg->is_session_active()) { + return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING); + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true; + DebugAdapterProtocol::get_singleton()->notify_process(); + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const { + // Extract embedded "arguments" so it can be given to req_launch/req_attach + Dictionary params = p_params, args; + args = params["arguments"]; + args = args["arguments"]; + params["arguments"] = args; + + Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params); + if (!response["success"]) { + response["command"] = p_params["command"]; + return response; + } + + return prepare_success_response(p_params); +} + Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const { EditorNode::get_singleton()->run_stop(); @@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const return response; } -Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { Dictionary response = prepare_success_response(p_params), body; response["body"] = body; @@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { lines.push_back(breakpoint.line + !lines_at_one); } - EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines); Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); body["breakpoints"] = updated_breakpoints; return response; } -Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + Dictionary args = p_params["arguments"]; + + Array locations; + DAP::BreakpointLocation location; + location.line = args["line"]; + if (args.has("endLine")) { + location.endLine = args["endLine"]; + } + locations.push_back(location.to_json()); + + body["breakpoints"] = locations; + return response; +} + +Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const { Dictionary response = prepare_success_response(p_params), body; response["body"] = body; @@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { int variable_id = args["variablesReference"]; Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); + if (E) { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) { + for (int i = 0; i < E->value().size(); i++) { + Dictionary variable = E->value()[i]; + variable.erase("type"); + } + } body["variables"] = E ? E->value() : Array(); return response; } else { @@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { return prepare_success_response(p_params); } +Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + + String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]); + body["result"] = value; + + return response; +} + +Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + + String msg = args["message"]; + Array data = args["data"]; + + EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data); + + return prepare_success_response(p_params); +} + Dictionary DebugAdapterParser::ev_initialized() const { Dictionary event = prepare_base_event(); event["event"] = "initialized"; @@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const { return event; } + +Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "breakpoint"; + event["body"] = body; + + body["reason"] = p_enabled ? "new" : "removed"; + body["breakpoint"] = p_breakpoint.to_json(); + + return event; +} + +Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "godot/custom_data"; + event["body"] = body; + + body["message"] = p_msg; + body["data"] = p_data; + + return event; +} diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h index b86b37d067..4c93464e39 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.h +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -44,7 +44,7 @@ class DebugAdapterParser : public Object { private: friend DebugAdapterProtocol; - _FORCE_INLINE_ bool is_valid_path(const String &p_path) { + _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); } @@ -60,17 +60,23 @@ protected: public: // Requests Dictionary req_initialize(const Dictionary &p_params) const; - Dictionary req_launch(const Dictionary &p_params); + Dictionary req_launch(const Dictionary &p_params) const; + Dictionary req_disconnect(const Dictionary &p_params) const; + Dictionary req_attach(const Dictionary &p_params) const; + Dictionary req_restart(const Dictionary &p_params) const; Dictionary req_terminate(const Dictionary &p_params) const; Dictionary req_pause(const Dictionary &p_params) const; Dictionary req_continue(const Dictionary &p_params) const; Dictionary req_threads(const Dictionary &p_params) const; Dictionary req_stackTrace(const Dictionary &p_params) const; - Dictionary req_setBreakpoints(const Dictionary &p_params); - Dictionary req_scopes(const Dictionary &p_params); + Dictionary req_setBreakpoints(const Dictionary &p_params) const; + Dictionary req_breakpointLocations(const Dictionary &p_params) const; + Dictionary req_scopes(const Dictionary &p_params) const; Dictionary req_variables(const Dictionary &p_params) const; Dictionary req_next(const Dictionary &p_params) const; Dictionary req_stepIn(const Dictionary &p_params) const; + Dictionary req_evaluate(const Dictionary &p_params) const; + Dictionary req_godot_put_msg(const Dictionary &p_params) const; // Events Dictionary ev_initialized() const; @@ -83,6 +89,8 @@ public: Dictionary ev_stopped_step() const; Dictionary ev_continued() const; Dictionary ev_output(const String &p_message) const; + Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const; + Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const; }; #endif diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp index 0482271432..2e0e6cb7c8 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp @@ -94,11 +94,17 @@ Error DAPeer::handle_data() { String msg; msg.parse_utf8((const char *)req_buf, req_pos); + // Apply a timestamp if it there's none yet + if (!timestamp) { + timestamp = OS::get_singleton()->get_ticks_msec(); + } + // Response if (DebugAdapterProtocol::get_singleton()->process_message(msg)) { // Reset to read again req_pos = 0; has_header = false; + timestamp = 0; } } return OK; @@ -180,12 +186,460 @@ void DebugAdapterProtocol::reset_stack_info() { variable_list.clear(); } +int DebugAdapterProtocol::parse_variant(const Variant &p_var) { + switch (p_var.get_type()) { + case Variant::VECTOR2: + case Variant::VECTOR2I: { + int id = variable_id++; + Vector2 vec = p_var; + DAP::Variable x, y; + x.name = "x"; + y.name = "y"; + x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::RECT2: + case Variant::RECT2I: { + int id = variable_id++; + Rect2 rect = p_var; + DAP::Variable x, y, w, h; + x.name = "x"; + y.name = "y"; + w.name = "w"; + h.name = "h"; + x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(rect.position.x); + y.value = rtos(rect.position.y); + w.value = rtos(rect.size.x); + h.value = rtos(rect.size.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(w.to_json()); + arr.push_back(h.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::VECTOR3: + case Variant::VECTOR3I: { + int id = variable_id++; + Vector3 vec = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + z.value = rtos(vec.z); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM2D: { + int id = variable_id++; + Transform2D transform = p_var; + DAP::Variable x, y, origin; + x.name = "x"; + y.name = "y"; + origin.name = "origin"; + x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2); + x.value = transform.elements[0]; + y.value = transform.elements[1]; + origin.value = transform.elements[2]; + x.variablesReference = parse_variant(transform.elements[0]); + y.variablesReference = parse_variant(transform.elements[1]); + origin.variablesReference = parse_variant(transform.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::PLANE: { + int id = variable_id++; + Plane plane = p_var; + DAP::Variable d, normal; + d.name = "d"; + normal.name = "normal"; + d.type = Variant::get_type_name(Variant::FLOAT); + normal.type = Variant::get_type_name(Variant::VECTOR3); + d.value = rtos(plane.d); + normal.value = plane.normal; + normal.variablesReference = parse_variant(plane.normal); + + Array arr; + arr.push_back(d.to_json()); + arr.push_back(normal.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::QUATERNION: { + int id = variable_id++; + Quaternion quat = p_var; + DAP::Variable x, y, z, w; + x.name = "x"; + y.name = "y"; + z.name = "z"; + w.name = "w"; + x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT); + x.value = rtos(quat.x); + y.value = rtos(quat.y); + z.value = rtos(quat.z); + w.value = rtos(quat.w); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + arr.push_back(w.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::AABB: { + int id = variable_id++; + AABB aabb = p_var; + DAP::Variable position, size; + position.name = "position"; + size.name = "size"; + position.type = size.type = Variant::get_type_name(Variant::VECTOR3); + position.value = aabb.position; + size.value = aabb.size; + position.variablesReference = parse_variant(aabb.position); + size.variablesReference = parse_variant(aabb.size); + + Array arr; + arr.push_back(position.to_json()); + arr.push_back(size.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::BASIS: { + int id = variable_id++; + Basis basis = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2); + x.value = basis.elements[0]; + y.value = basis.elements[1]; + z.value = basis.elements[2]; + x.variablesReference = parse_variant(basis.elements[0]); + y.variablesReference = parse_variant(basis.elements[1]); + z.variablesReference = parse_variant(basis.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM3D: { + int id = variable_id++; + Transform3D transform = p_var; + DAP::Variable basis, origin; + basis.name = "basis"; + origin.name = "origin"; + basis.type = Variant::get_type_name(Variant::BASIS); + origin.type = Variant::get_type_name(Variant::VECTOR3); + basis.value = transform.basis; + origin.value = transform.origin; + basis.variablesReference = parse_variant(transform.basis); + origin.variablesReference = parse_variant(transform.origin); + + Array arr; + arr.push_back(basis.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::COLOR: { + int id = variable_id++; + Color color = p_var; + DAP::Variable r, g, b, a; + r.name = "r"; + g.name = "g"; + b.name = "b"; + a.name = "a"; + r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT); + r.value = rtos(color.r); + g.value = rtos(color.g); + b.value = rtos(color.b); + a.value = rtos(color.a); + + Array arr; + arr.push_back(r.to_json()); + arr.push_back(g.to_json()); + arr.push_back(b.to_json()); + arr.push_back(a.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::ARRAY: { + int id = variable_id++; + Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(array[i].get_type()); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::DICTIONARY: { + int id = variable_id++; + Dictionary dictionary = p_var; + Array arr; + + for (int i = 0; i < dictionary.size(); i++) { + DAP::Variable var; + var.name = dictionary.get_key_at_index(i); + Variant value = dictionary.get_value_at_index(i); + var.type = Variant::get_type_name(value.get_type()); + var.value = value; + var.variablesReference = parse_variant(value); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_BYTE_ARRAY: { + int id = variable_id++; + PackedByteArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "byte"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT32_ARRAY: { + int id = variable_id++; + PackedInt32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "int"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT64_ARRAY: { + int id = variable_id++; + PackedInt64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "long"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT32_ARRAY: { + int id = variable_id++; + PackedFloat32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "float"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT64_ARRAY: { + int id = variable_id++; + PackedFloat64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "double"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_STRING_ARRAY: { + int id = variable_id++; + PackedStringArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::STRING); + var.value = array[i]; + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR2_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR2); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR3_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR3); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_COLOR_ARRAY: { + int id = variable_id++; + PackedColorArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::COLOR); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + default: + // Simple atomic stuff, or too complex to be manipulated + return 0; + } +} + bool DebugAdapterProtocol::process_message(const String &p_text) { JSON json; ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!"); Dictionary params = json.get_data(); bool completed = true; + if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) { + Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT); + _current_peer->res_queue.push_front(response); + return true; + } + // Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting String command = "req_" + (String)params["command"]; if (parser->has_method(command)) { @@ -211,7 +665,7 @@ void DebugAdapterProtocol::notify_initialized() { } void DebugAdapterProtocol::notify_process() { - String launch_mode = _current_request.is_empty() ? "launch" : _current_request; + String launch_mode = _current_peer->attached ? "attach" : "launch"; Dictionary event = parser->ev_process(launch_mode); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { @@ -222,7 +676,7 @@ void DebugAdapterProtocol::notify_process() { void DebugAdapterProtocol::notify_terminated() { Dictionary event = parser->ev_terminated(); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { - if (_current_request == "launch" && _current_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -232,7 +686,7 @@ void DebugAdapterProtocol::notify_terminated() { void DebugAdapterProtocol::notify_exited(const int &p_exitcode) { Dictionary event = parser->ev_exited(p_exitcode); for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { - if (_current_request == "launch" && _current_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -286,25 +740,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) { } } +void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) { + Dictionary event = parser->ev_custom_data(p_msg, p_data); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + if (peer->supportsCustomData) { + peer->res_queue.push_back(event); + } + } +} + +void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) { + Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "setBreakpoints" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) { Array updated_breakpoints; + // Add breakpoints for (int i = 0; i < p_lines.size(); i++) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true); DAP::Breakpoint breakpoint; - breakpoint.verified = true; - breakpoint.source.path = p_path; - breakpoint.source.compute_checksums(); breakpoint.line = p_lines[i]; + breakpoint.source.path = p_path; - List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); - if (E) { - breakpoint.id = E->get().id; - } else { - breakpoint.id = breakpoint_id++; - breakpoint_list.push_back(breakpoint); - } + ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array()); + updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json()); + } - updated_breakpoints.push_back(breakpoint.to_json()); + // Remove breakpoints + for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) { + DAP::Breakpoint b = E->get(); + if (b.source.path == p_path && !p_lines.has(b.line)) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false); + } } return updated_breakpoints; @@ -347,6 +822,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool _processing_stackdump = p_has_stackdump; } +void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) { + DAP::Breakpoint breakpoint; + breakpoint.verified = true; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path); + breakpoint.source.compute_checksums(); + breakpoint.line = p_line; + + if (p_enabled) { + // Add the breakpoint + breakpoint.id = breakpoint_id++; + breakpoint_list.push_back(breakpoint); + } else { + // Remove the breakpoint + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + breakpoint.id = E->get().id; + breakpoint_list.erase(E); + } + } + + notify_breakpoint(breakpoint, p_enabled); +} + void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) { if (_processing_breakpoint && !p_stack_dump.is_empty()) { // Find existing breakpoint @@ -424,11 +922,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) { variable.name = stack_var.name; variable.value = stack_var.value; variable.type = Variant::get_type_name(stack_var.value.get_type()); + variable.variablesReference = parse_variant(stack_var.value); variable_list.find(variable_id)->value().push_back(variable.to_json()); _remaining_vars--; } +void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) { + // Ignore data that is already handled by DAP + if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") { + return; + } + + notify_custom_data(p_msg, p_data); +} + void DebugAdapterProtocol::poll() { if (server->is_connection_available()) { on_client_connected(); @@ -459,6 +967,8 @@ void DebugAdapterProtocol::poll() { } Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) { + _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout"); + _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints"); _initialized = true; return server->listen(p_port, p_bind_ip); } @@ -484,12 +994,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() { node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused)); EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton(); + debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled)); + debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped)); debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output)); debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked)); debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump)); debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars)); debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var)); + debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data)); } DebugAdapterProtocol::~DebugAdapterProtocol() { diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h index 6b542033ed..d4291992bf 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.h +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h @@ -52,12 +52,17 @@ struct DAPeer : RefCounted { int content_length = 0; List<Dictionary> res_queue; int seq = 0; + uint64_t timestamp = 0; // Client specific info bool linesStartAt1 = false; bool columnsStartAt1 = false; bool supportsVariableType = false; bool supportsInvalidatedEvent = false; + bool supportsCustomData = false; + + // Internal client info + bool attached = false; Error handle_data(); Error send_data(); @@ -82,20 +87,26 @@ private: void on_debug_stopped(); void on_debug_output(const String &p_message); void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump); + void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled); void on_debug_stack_dump(const Array &p_stack_dump); void on_debug_stack_frame_vars(const int &p_size); void on_debug_stack_frame_var(const Array &p_data); + void on_debug_data(const String &p_msg, const Array &p_data); void reset_current_info(); void reset_ids(); void reset_stack_info(); + int parse_variant(const Variant &p_var); + bool _initialized = false; bool _processing_breakpoint = false; bool _stepping = false; bool _processing_stackdump = false; int _remaining_vars = 0; int _current_frame = 0; + uint64_t _request_timeout = 1000; + bool _sync_breakpoints = false; String _current_request; Ref<DAPeer> _current_peer; @@ -108,6 +119,8 @@ private: Map<int, Array> variable_list; public: + friend class DebugAdapterServer; + _FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; } _FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; } @@ -126,8 +139,10 @@ public: void notify_stopped_step(); void notify_continued(); void notify_output(const String &p_message); + void notify_custom_data(const String &p_msg, const Array &p_data); + void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled); - Array update_breakpoints(const String &p_path, const Array &p_breakpoints); + Array update_breakpoints(const String &p_path, const Array &p_lines); void poll(); Error start(int p_port, const IPAddress &p_bind_ip); diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp index f9092a1791..4775e2c8b0 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp @@ -36,7 +36,8 @@ DebugAdapterServer::DebugAdapterServer() { _EDITOR_DEF("network/debug_adapter/remote_port", remote_port); - _EDITOR_DEF("network/debug_adapter/use_thread", use_thread); + _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout); + _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints); } void DebugAdapterServer::_notification(int p_what) { @@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { // The main loop can be run again during request processing, which modifies internal state of the protocol. // Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished. - if (started && !use_thread && !polling) { + if (started && !polling) { polling = true; protocol.poll(); polling = false; } } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout"); + protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints"); int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); - bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); - if (remote_port != this->remote_port || use_thread != this->use_thread) { + if (remote_port != this->remote_port) { this->stop(); this->start(); } @@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) { } } -void DebugAdapterServer::thread_func(void *p_userdata) { - DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata); - while (self->thread_running) { - // Poll 20 times per second - self->protocol.poll(); - OS::get_singleton()->delay_usec(50000); - } -} - void DebugAdapterServer::start() { remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); - use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR); - if (use_thread) { - thread_running = true; - thread.start(DebugAdapterServer::thread_func, this); - } - set_process_internal(!use_thread); + set_process_internal(true); started = true; } } void DebugAdapterServer::stop() { - if (use_thread) { - ERR_FAIL_COND(!thread.is_started()); - thread_running = false; - thread.wait_to_finish(); - } protocol.stop(); started = false; EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR); diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h index f8a38965cc..c449403cc2 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.h +++ b/editor/debugger/debug_adapter/debug_adapter_server.h @@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin { DebugAdapterProtocol protocol; - Thread thread; int remote_port = 6006; bool thread_running = false; bool started = false; - bool use_thread = false; bool polling = false; static void thread_func(void *p_userdata); diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h index aa9ab1adcd..5156c91d14 100644 --- a/editor/debugger/debug_adapter/debug_adapter_types.h +++ b/editor/debugger/debug_adapter/debug_adapter_types.h @@ -38,7 +38,11 @@ namespace DAP { enum ErrorType { UNKNOWN, - WRONG_PATH + WRONG_PATH, + NOT_RUNNING, + TIMEOUT, + UNKNOWN_PLATFORM, + MISSING_DEVICE }; struct Checksum { @@ -118,10 +122,14 @@ struct Breakpoint { struct BreakpointLocation { int line; + int endLine = -1; _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["line"] = line; + if (endLine >= 0) { + dict["endLine"] = endLine; + } return dict; } diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index a9cb1a0131..07c02eb022 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() { ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); + ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); } EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() { @@ -487,6 +488,8 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p _for_all(tabs, [&](ScriptEditorDebugger *dbg) { dbg->set_breakpoint(p_path, p_line, p_enabled); }); + + emit_signal("breakpoint_toggled", p_path, p_line, p_enabled); } void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 2a5013893f..2f5bde64a9 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -296,6 +296,7 @@ Size2 ScriptEditorDebugger::get_minimum_size() const { } void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { + emit_signal(SNAME("debug_data"), p_msg, p_data); if (p_msg == "debug_enter") { _put_msg("get_stack_dump", Array()); @@ -872,6 +873,16 @@ void ScriptEditorDebugger::_clear_execution() { inspector->clear_stack_variables(); } +void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) { + Ref<Script> script = ResourceLoader::load(p_file); + emit_signal("set_breakpoint", script, p_line - 1, p_enabled); + script.unref(); +} + +void ScriptEditorDebugger::_clear_breakpoints() { + emit_signal("clear_breakpoints"); +} + void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { error_count = 0; warning_count = 0; @@ -1503,6 +1514,9 @@ void ScriptEditorDebugger::_bind_methods() { 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_var", PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); + ADD_SIGNAL(MethodInfo("clear_breakpoints")); } void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) { diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 499dda86da..1c1c0fd3e5 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -205,6 +205,9 @@ private: void _clear_execution(); void _stop_and_notify(); + void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled); + void _clear_breakpoints(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index fee2deddda..d04875f188 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -277,7 +277,7 @@ void DocTools::generate(bool p_basic_types) { EO = EO->next(); } - if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL) { + if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index c62e5b75b2..e40bbefef8 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -438,6 +438,21 @@ const Vector<Callable> EditorData::get_undo_redo_inspector_hook_callback() { return undo_redo_callbacks; } +void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) { + move_element_functions.insert(p_class, p_callable); +} + +void EditorData::remove_move_array_element_function(const StringName &p_class) { + move_element_functions.erase(p_class); +} + +Callable EditorData::get_move_array_element_function(const StringName &p_class) const { + if (move_element_functions.has(p_class)) { + return move_element_functions[p_class]; + } + return Callable(); +} + void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) { p_plugin->undo_redo = nullptr; editor_plugins.erase(p_plugin); diff --git a/editor/editor_data.h b/editor/editor_data.h index df6ba9d0c9..9184ddcf39 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -133,6 +133,7 @@ private: List<PropertyData> clipboard; UndoRedo undo_redo; Vector<Callable> undo_redo_callbacks; + Map<StringName, Callable> move_element_functions; void _cleanup_history(); @@ -167,10 +168,14 @@ public: EditorPlugin *get_editor_plugin(int p_idx); UndoRedo &get_undo_redo(); - void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have 4 args: (Object* undo_redo, Object *modified_object, String property, Variant new_value) + void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value) void remove_undo_redo_inspector_hook_callback(Callable p_callable); const Vector<Callable> get_undo_redo_inspector_hook_callback(); + void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position) + void remove_move_array_element_function(const StringName &p_class); + Callable get_move_array_element_function(const StringName &p_class) const; + void save_editor_global_states(); void restore_editor_global_states(); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 1240496028..10ed76673e 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -1948,7 +1948,7 @@ void EditorExportPlatformPC::set_debug_32(const String &p_file) { void EditorExportPlatformPC::get_platform_features(List<String> *r_features) { r_features->push_back("pc"); //all pcs support "pc" r_features->push_back("s3tc"); //all pcs support "s3tc" compression - r_features->push_back(get_os_name()); //OS name is a feature + r_features->push_back(get_os_name().to_lower()); //OS name is a feature } void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 1e9d579708..bf95e6cf62 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -958,7 +958,7 @@ String EditorFileDialog::get_current_path() const { } void EditorFileDialog::set_current_dir(const String &p_dir) { - if (p_dir.is_rel_path()) { + if (p_dir.is_relative_path()) { dir_access->change_dir(OS::get_singleton()->get_resource_dir()); } dir_access->change_dir(p_dir); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 24b6ba1a14..490c8f287f 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1328,6 +1328,8 @@ void EditorHelp::_help_callback(const String &p_topic) { } else if (what == "class_global") { if (constant_line.has(name)) { line = constant_line[name]; + } else if (method_line.has(name)) { + line = method_line[name]; } else { Map<String, Map<String, int>>::Element *iter = enum_values_line.front(); while (true) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index fee27dae58..d3841ad6c0 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -246,9 +246,9 @@ void EditorProperty::_notification(int p_what) { Color color; if (draw_red) { - color = get_theme_color(SNAME("error_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_error_color") : SNAME("error_color")); } else { - color = get_theme_color(SNAME("property_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color")); } if (label.find(".") != -1) { color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides @@ -283,7 +283,7 @@ void EditorProperty::_notification(int p_what) { check_rect = Rect2(); } - if (can_revert) { + if (can_revert && !is_read_only()) { Ref<Texture2D> reload_icon = get_theme_icon(SNAME("ReloadSmall"), SNAME("EditorIcons")); text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2; revert_rect = Rect2(text_limit + get_theme_constant(SNAME("hseparator"), SNAME("Tree")), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); @@ -384,8 +384,12 @@ void EditorProperty::update_property() { GDVIRTUAL_CALL(_update_property); } +void EditorProperty::_set_read_only(bool p_read_only) { +} + void EditorProperty::set_read_only(bool p_read_only) { read_only = p_read_only; + _set_read_only(p_read_only); } bool EditorProperty::is_read_only() const { @@ -1180,147 +1184,144 @@ EditorInspectorCategory::EditorInspectorCategory() { void EditorInspectorSection::_test_unfold() { if (!vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } } void EditorInspectorSection::_notification(int p_what) { - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - Ref<Texture2D> arrow; - - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); + } break; + case NOTIFICATION_SORT_CHILDREN: { + if (!vbox_added) { + return; + } + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Size2 size = get_size(); - Point2 offset; - Rect2 rect; - offset.y = font->get_height(font_size); - if (arrow.is_valid()) { - offset.y = MAX(offset.y, arrow->get_height()); - } - offset.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (is_layout_rtl()) { - rect = Rect2(offset, size - offset - Vector2(get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")), 0)); - } else { - offset.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - rect = Rect2(offset, size - offset); - } - - //set children - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible_in_tree()) { - continue; - } - - fit_child_in_rect(c, rect); - } - - update(); //need to redraw text - } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> arrow; - bool rtl = is_layout_rtl(); + int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + Size2 size = get_size() - Vector2(inspector_margin, 0); + Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + fit_child_in_rect(c, Rect2(offset, size)); + } + } break; + case NOTIFICATION_DRAW: { + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - int h = font->get_height(font_size); - if (arrow.is_valid()) { - h = MAX(h, arrow->get_height()); - } - h += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - Color c = bg_color; - c.a *= 0.4; - draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), c); - const int arrow_margin = 2; - const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; - Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); - draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (h - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); + bool rtl = is_layout_rtl(); - if (arrow.is_valid()) { - if (rtl) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); - } else { - draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } - } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (dropping && !vbox->is_visible_in_tree()) { - Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - draw_rect(Rect2(Point2(), get_size()), accent_color, false); - } - } + Color c = bg_color; + c.a *= 0.4; + draw_rect(Rect2(Vector2(), Vector2(get_size().width, header_height)), c); - if (p_what == NOTIFICATION_DRAG_BEGIN) { - Dictionary dd = get_viewport()->gui_get_drag_data(); + const int arrow_margin = 2; + const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; + Color color = get_theme_color(SNAME("font_color")); + float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); + draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); - // Only allow dropping if the section contains properties which can take the dragged data. - bool children_can_drop = false; - for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { - Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); + if (arrow.is_valid()) { + if (rtl) { + draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } else { + draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } + } - // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. - if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { - children_can_drop = true; - break; + if (dropping && !vbox->is_visible_in_tree()) { + Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + draw_rect(Rect2(Point2(), get_size()), accent_color, false); } - } + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dd = get_viewport()->gui_get_drag_data(); - dropping = children_can_drop; - update(); - } + // Only allow dropping if the section contains properties which can take the dragged data. + bool children_can_drop = false; + for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { + Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); - if (p_what == NOTIFICATION_DRAG_END) { - dropping = false; - update(); - } + // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. + if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { + children_can_drop = true; + break; + } + } - if (p_what == NOTIFICATION_MOUSE_ENTER) { - if (dropping) { - dropping_unfold_timer->start(); - } - } + dropping = children_can_drop; + update(); + } break; + case NOTIFICATION_DRAG_END: { + dropping = false; + update(); + } break; + case NOTIFICATION_MOUSE_ENTER: { + if (dropping) { + dropping_unfold_timer->start(); + } + } break; - if (p_what == NOTIFICATION_MOUSE_EXIT) { - if (dropping) { - dropping_unfold_timer->stop(); - } + case NOTIFICATION_MOUSE_EXIT: { + if (dropping) { + dropping_unfold_timer->stop(); + } + } break; } } @@ -1359,6 +1360,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe if (!foldable && !vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } @@ -1418,7 +1420,7 @@ void EditorInspectorSection::fold() { } if (!vbox_added) { - return; //kinda pointless + return; } object->editor_set_section_unfold(section, false); @@ -1455,6 +1457,732 @@ EditorInspectorSection::~EditorInspectorSection() { //////////////////////////////////////////////// //////////////////////////////////////////////// +int EditorInspectorArray::_get_array_count() { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + return _extract_properties_as_array(object_property_list).size(); + } else if (mode == MODE_USE_COUNT_PROPERTY) { + bool valid; + int count = object->get(count_property, &valid); + ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property)); + return count; + } + return 0; +} + +void EditorInspectorArray::_add_button_pressed() { + _move_element(-1, -1); +} + +void EditorInspectorArray::_first_page_button_pressed() { + emit_signal("page_change_request", 0); +} + +void EditorInspectorArray::_prev_page_button_pressed() { + emit_signal("page_change_request", MAX(0, page - 1)); +} + +void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) { + if (p_text.is_valid_int()) { + int new_page = p_text.to_int() - 1; + new_page = MIN(MAX(0, new_page), max_page); + page_line_edit->set_text(Variant(new_page)); + emit_signal("page_change_request", new_page); + } else { + page_line_edit->set_text(Variant(page)); + } +} + +void EditorInspectorArray::_next_page_button_pressed() { + emit_signal("page_change_request", MIN(max_page, page + 1)); +} + +void EditorInspectorArray::_last_page_button_pressed() { + emit_signal("page_change_request", max_page); +} + +void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) { + switch (p_id) { + case OPTION_MOVE_UP: + if (popup_array_index_pressed > 0) { + _move_element(popup_array_index_pressed, popup_array_index_pressed - 1); + } + break; + case OPTION_MOVE_DOWN: + if (popup_array_index_pressed < count - 1) { + _move_element(popup_array_index_pressed, popup_array_index_pressed + 2); + } + break; + case OPTION_NEW_BEFORE: + _move_element(-1, popup_array_index_pressed); + break; + case OPTION_NEW_AFTER: + _move_element(-1, popup_array_index_pressed + 1); + break; + case OPTION_REMOVE: + _move_element(popup_array_index_pressed, -1); + break; + case OPTION_CLEAR_ARRAY: + _clear_array(); + break; + case OPTION_RESIZE_ARRAY: + new_size = count; + new_size_line_edit->set_text(Variant(new_size)); + resize_dialog->get_ok_button()->set_disabled(true); + resize_dialog->popup_centered(); + new_size_line_edit->grab_focus(); + new_size_line_edit->select_all(); + break; + default: + break; + } +} + +void EditorInspectorArray::_control_dropping_draw() { + int drop_position = _drop_position(); + + if (dropping && drop_position >= 0) { + Vector2 from; + Vector2 to; + if (drop_position < elements_vbox->get_child_count()) { + Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform(); + from = xform.xform(Vector2()); + to = xform.xform(Vector2(elements_vbox->get_size().x, 0)); + } else { + Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1)); + Transform2D xform = child->get_transform(); + from = xform.xform(Vector2(0, child->get_size().y)); + to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y)); + } + Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + control_dropping->draw_line(from, to, color, 2); + } +} + +void EditorInspectorArray::_vbox_visibility_changed() { + control_dropping->set_visible(vbox->is_visible_in_tree()); +} + +void EditorInspectorArray::_panel_draw(int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<StyleBox> style = get_theme_stylebox("Focus", "EditorStyles"); + if (!style.is_valid()) { + return; + } + if (array_elements[p_index].panel->has_focus()) { + array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size())); + } +} + +void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<InputEventKey> key_ref = p_event; + if (key_ref.is_valid()) { + const InputEventKey &key = **key_ref; + + if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == KEY_DELETE) { + _move_element(begin_array_index + p_index, -1); + array_elements[p_index].panel->accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + popup_array_index_pressed = begin_array_index + p_index; + rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0); + rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1); + rmb_popup->set_position(mb->get_global_position()); + rmb_popup->set_size(Vector2()); + rmb_popup->popup(); + } + } +} + +void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { + String action_name; + if (p_element_index < 0) { + action_name = vformat("Add element to property array with prefix %s.", array_element_prefix); + } else if (p_to_pos < 0) { + action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix); + } else { + action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix); + } + undo_redo->create_action(action_name); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count); + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + if (p_element_index < 0) { + // Add an element. + properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary()); + } else if (p_to_pos < 0) { + // Delete the element. + properties_as_array.remove(p_element_index); + } else { + // Move the element. + properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate()); + properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index); + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = properties_as_array[i]; + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_do_property(object, vformat(key, i), d[key]); + } + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + if (p_element_index < 0) { + int added_index = p_to_pos < 0 ? count : p_to_pos; + emit_signal("page_change_request", added_index / page_lenght); + count += 1; + } else if (p_to_pos < 0) { + count -= 1; + if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) { + emit_signal("page_change_request", max_page - 1); + } + } + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; +} + +void EditorInspectorArray::_clear_array() { + undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix)); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i >= 0; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, 0); + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; +} + +void EditorInspectorArray::_resize_array(int p_size) { + ERR_FAIL_COND(p_size < 0); + if (p_size == count) { + return; + } + + undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix)); + if (p_size > count) { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count; i < p_size; i++) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + undo_redo->add_undo_property(object, count_property, count); + undo_redo->add_do_property(object, count_property, p_size); + } + } else { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i > p_size - 1; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = count - 1; i > p_size - 1; i--) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, p_size); + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + /* + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; + */ +} + +Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) { + Array output; + + for (const PropertyInfo &pi : p_list) { + if (pi.name.begins_with(array_element_prefix)) { + String str = pi.name.trim_prefix(array_element_prefix); + + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + Error error = OK; + if (array_index >= output.size()) { + error = output.resize(array_index + 1); + } + if (error == OK) { + String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index); + Dictionary dict = output[array_index]; + dict[format_string] = object->get(pi.name); + output[array_index] = dict; + } else { + WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name)); + } + } + } + } + return output; +} + +int EditorInspectorArray::_drop_position() const { + for (int i = 0; i < (int)array_elements.size(); i++) { + const ArrayElement &ae = array_elements[i]; + + Size2 size = ae.panel->get_size(); + Vector2 mp = ae.panel->get_local_mouse_position(); + + if (Rect2(Vector2(), size).has_point(mp)) { + if (mp.y < size.y / 2) { + return i; + } else { + return i + 1; + } + } + } + return -1; +} + +void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + valid = true; + } + } + resize_dialog->get_ok_button()->set_disabled(!valid); +} + +void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + new_size = val; + valid = true; + } + } + if (valid) { + resize_dialog->hide(); + _resize_array(new_size); + } else { + new_size_line_edit->set_text(Variant(new_size)); + } +} + +void EditorInspectorArray::_resize_dialog_confirmed() { + _new_size_line_edit_text_submitted(new_size_line_edit->get_text()); +} + +void EditorInspectorArray::_setup() { + // Setup counts. + count = _get_array_count(); + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; + array_elements.resize(MAX(0, end_array_index - begin_array_index)); + if (page < 0 || page > max_page) { + WARN_PRINT(vformat("Invalid page number %d", page)); + page = CLAMP(page, 0, max_page); + } + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + + // Panel and its hbox. + ae.panel = memnew(PanelContainer); + ae.panel->set_focus_mode(FOCUS_ALL); + ae.panel->set_mouse_filter(MOUSE_FILTER_PASS); + ae.panel->set_drag_forwarding(this); + ae.panel->set_meta("index", begin_array_index + i); + ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i)); + ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("draw", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_draw), i)); + ae.panel->connect("gui_input", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_gui_input), i)); + ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style); + elements_vbox->add_child(ae.panel); + + ae.margin = memnew(MarginContainer); + ae.margin->set_mouse_filter(MOUSE_FILTER_PASS); + if (is_inside_tree()) { + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + ae.panel->add_child(ae.margin); + + ae.hbox = memnew(HBoxContainer); + ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.margin->add_child(ae.hbox); + + // Move button. + ae.move_texture_rect = memnew(TextureRect); + ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + if (is_inside_tree()) { + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + } + ae.hbox->add_child(ae.move_texture_rect); + + // Right vbox. + ae.vbox = memnew(VBoxContainer); + ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.hbox->add_child(ae.vbox); + } + + // Hide/show the add button. + add_button->set_visible(page == max_page); + + if (max_page == 0) { + hbox_pagination->hide(); + } else { + // Update buttons. + first_page_button->set_disabled(page == 0); + prev_page_button->set_disabled(page == 0); + next_page_button->set_disabled(page == max_page); + last_page_button->set_disabled(page == max_page); + + // Update page number and page count. + page_line_edit->set_text(vformat("%d", page + 1)); + page_count_label->set_text(vformat("/ %d", max_page + 1)); + } +} + +Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + int index = p_from->get_meta("index"); + Dictionary dict; + dict["type"] = "property_array_element"; + dict["property_array_prefix"] = array_element_prefix; + dict["index"] = index; + + return dict; +} + +void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + Dictionary dict = p_data; + + int to_drop = dict["index"]; + int drop_position = _drop_position(); + if (drop_position < 0) { + return; + } + _move_element(to_drop, begin_array_index + drop_position); +} + +bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + // First, update drawing. + control_dropping->update(); + + if (p_data.get_type() != Variant::DICTIONARY) { + return false; + } + Dictionary dict = p_data; + int drop_position = _drop_position(); + if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) { + return false; + } + + // Check in dropping at the given index does indeed move the item. + int moved_array_index = (int)dict["index"]; + int drop_array_index = begin_array_index + drop_position; + + return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index; +} + +void EditorInspectorArray::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + Color color = get_theme_color(SNAME("dark_color_1"), SNAME("Editor")); + odd_style->set_bg_color(color.lightened(0.15)); + even_style->set_bg_color(color.darkened(0.15)); + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + + add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); + prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); + next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); + last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); + minimum_size_changed(); + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dict = get_viewport()->gui_get_drag_data(); + if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) { + dropping = true; + control_dropping->update(); + } + } break; + case NOTIFICATION_DRAG_END: { + if (dropping) { + dropping = false; + control_dropping->update(); + } + } break; + } +} + +void EditorInspectorArray::_bind_methods() { + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw); + + ADD_SIGNAL(MethodInfo("page_change_request")); +} + +void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = ""; + mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = p_count_property; + mode = MODE_USE_COUNT_PROPERTY; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +VBoxContainer *EditorInspectorArray::get_vbox(int p_index) { + if (p_index >= begin_array_index && p_index < end_array_index) { + return array_elements[p_index - begin_array_index].vbox; + } else if (p_index < 0) { + return vbox; + } else { + return nullptr; + } +} + +EditorInspectorArray::EditorInspectorArray() { + set_mouse_filter(Control::MOUSE_FILTER_STOP); + + odd_style.instantiate(); + even_style.instantiate(); + + rmb_popup = memnew(PopupMenu); + rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP); + rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE); + rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY); + rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY); + rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed)); + add_child(rmb_popup); + + elements_vbox = memnew(VBoxContainer); + elements_vbox->add_theme_constant_override("separation", 0); + vbox->add_child(elements_vbox); + + add_button = memnew(Button); + add_button->set_text(TTR("Add Element")); + add_button->set_text_align(Button::ALIGN_CENTER); + add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed)); + vbox->add_child(add_button); + + hbox_pagination = memnew(HBoxContainer); + hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL); + hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER); + vbox->add_child(hbox_pagination); + + first_page_button = memnew(Button); + first_page_button->set_flat(true); + first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed)); + hbox_pagination->add_child(first_page_button); + + prev_page_button = memnew(Button); + prev_page_button->set_flat(true); + prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed)); + hbox_pagination->add_child(prev_page_button); + + page_line_edit = memnew(LineEdit); + page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted)); + page_line_edit->add_theme_constant_override("minimum_character_width", 2); + hbox_pagination->add_child(page_line_edit); + + page_count_label = memnew(Label); + hbox_pagination->add_child(page_count_label); + next_page_button = memnew(Button); + next_page_button->set_flat(true); + next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed)); + hbox_pagination->add_child(next_page_button); + + last_page_button = memnew(Button); + last_page_button->set_flat(true); + last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed)); + hbox_pagination->add_child(last_page_button); + + control_dropping = memnew(Control); + control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); + control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + add_child(control_dropping); + + resize_dialog = memnew(AcceptDialog); + resize_dialog->set_title(TTRC("Resize Array")); + resize_dialog->add_cancel_button(); + resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed)); + add_child(resize_dialog); + + VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer); + resize_dialog->add_child(resize_dialog_vbox); + + new_size_line_edit = memnew(LineEdit); + new_size_line_edit->connect("text_changed", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_changed)); + new_size_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_submitted)); + resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit); + + vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed)); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; @@ -1640,18 +2368,17 @@ void EditorInspector::update_tree() { valid_plugins.push_back(inspector_plugins[i]); } + // Decide if properties should be drawn in red. bool draw_red = false; - if (is_inside_tree()) { Node *nod = Object::cast_to<Node>(object); Node *es = EditorNode::get_singleton()->get_edited_scene(); if (nod && es != nod && nod->get_owner() != es) { + // Draw in red edited nodes that are not in the currently edited scene. draw_red = true; } } - // TreeItem *current_category = nullptr; - String filter = search_box ? search_box->get_text() : ""; String group; String group_base; @@ -1663,30 +2390,30 @@ void EditorInspector::update_tree() { object->get_property_list(&plist, true); _update_script_class_properties(*object, plist); - HashMap<String, VBoxContainer *> item_path; - Map<VBoxContainer *, EditorInspectorSection *> section_map; - - item_path[""] = main_vbox; + Map<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path; + Map<String, EditorInspectorArray *> editor_inspector_array_per_prefix; Color sscolor = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + // Get the lists of editors to add the beginning. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_begin(object); _parse_added_editors(main_vbox, ped); } - for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { - PropertyInfo &p = I->get(); - - //make sure the property can be edited + // Get the lists of editors for properties. + for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) { + PropertyInfo &p = E_property->get(); if (p.usage & PROPERTY_USAGE_SUBGROUP) { + // Setup a property sub-group. subgroup = p.name; subgroup_base = p.hint_string; continue; } else if (p.usage & PROPERTY_USAGE_GROUP) { + // Setup a property group. group = p.name; group_base = p.hint_string; subgroup = ""; @@ -1695,6 +2422,7 @@ void EditorInspector::update_tree() { continue; } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + // Setup a property category. group = ""; group_base = ""; subgroup = ""; @@ -1704,9 +2432,9 @@ void EditorInspector::update_tree() { continue; } - List<PropertyInfo>::Element *N = I->next(); + // Iterate over remaining properties. If no properties in category, skip the category. + List<PropertyInfo>::Element *N = E_property->next(); bool valid = true; - //if no properties in category, skip while (N) { if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { break; @@ -1718,28 +2446,32 @@ void EditorInspector::update_tree() { N = N->next(); } if (!valid) { - continue; //empty, ignore + continue; // Empty, ignore it. } + // Create an EditorInspectorCategory and add it to the inspector. EditorInspectorCategory *category = memnew(EditorInspectorCategory); main_vbox->add_child(category); category_vbox = nullptr; //reset String type = p.name; + + // Set the category icon. if (!ClassDB::class_exists(type) && !ScriptServer::is_global_class(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) { - Ref<Script> s = ResourceLoader::load(p.hint_string, "Script"); + // If we have a category inside a script, search for the first script with a valid icon. + Ref<Script> script = ResourceLoader::load(p.hint_string, "Script"); String base_type; - if (s.is_valid()) { - base_type = s->get_instance_base_type(); + if (script.is_valid()) { + base_type = script->get_instance_base_type(); } - while (s.is_valid()) { - StringName name = EditorNode::get_editor_data().script_class_get_name(s->get_path()); + while (script.is_valid()) { + StringName name = EditorNode::get_editor_data().script_class_get_name(script->get_path()); String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); if (name != StringName() && icon_path.length()) { category->icon = ResourceLoader::load(icon_path, "Texture"); break; } - s = s->get_base_script(); + script = script->get_base_script(); } if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) { category->icon = get_theme_icon(base_type, SNAME("EditorIcons")); @@ -1750,9 +2482,12 @@ void EditorInspector::update_tree() { category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object"); } } + + // Set the category label. category->label = type; if (use_doc_hints) { + // Sets the category tooltip to show documentation. StringName type2 = p.name; if (!class_descr_cache.has(type2)) { String descr; @@ -1767,6 +2502,7 @@ void EditorInspector::update_tree() { category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2])); } + // Add editors at the start of a category. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_category(object, p.name); _parse_added_editors(main_vbox, ped); @@ -1775,134 +2511,215 @@ void EditorInspector::update_tree() { continue; } else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + // Ignore properties that are not supposed to be in the inspector. continue; } if (p.name == "script") { - category_vbox = nullptr; // script should go into its own category + // Script should go into its own category. + category_vbox = nullptr; } if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) { - continue; //do not show this property in low end gfx + // Do not show this property in low end gfx. + continue; } if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) { + // Hide script variables from inspector if required. continue; } - String basename = p.name; + // Get the path for property. + String path = p.name; - if (subgroup != "") { - if (subgroup_base != "") { - if (basename.begins_with(subgroup_base)) { - basename = basename.replace_first(subgroup_base, ""); - } else if (subgroup_base.begins_with(basename)) { - //keep it, this is used pretty often - } else { - subgroup = ""; //no longer using subgroup base, clear + // First check if we have an array that fits the prefix. + String array_prefix = ""; + int array_index = -1; + for (Map<String, EditorInspectorArray *>::Element *E = editor_inspector_array_per_prefix.front(); E; E = E->next()) { + if (p.name.begins_with(E->key()) && E->key().length() > array_prefix.length()) { + array_prefix = E->key(); + } + } + + if (!array_prefix.is_empty()) { + // If we have an array element, find the according index in array. + String str = p.name.trim_prefix(array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; } + to_char_index++; + } + if (to_char_index > 0) { + array_index = str.left(to_char_index).to_int(); + } else { + array_prefix = ""; } } - if (group != "") { - if (group_base != "" && subgroup == "") { - if (basename.begins_with(group_base)) { - basename = basename.replace_first(group_base, ""); - } else if (group_base.begins_with(basename)) { - //keep it, this is used pretty often + + if (!array_prefix.is_empty()) { + path = path.trim_prefix(array_prefix); + int char_index = path.find("/"); + if (char_index >= 0) { + path = path.right(-char_index - 1); + } else { + path = vformat(TTR("Element %s"), array_index); + } + } else { + // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string. + if (subgroup != "" && subgroup_base != "") { + if (path.begins_with(subgroup_base)) { + path = path.trim_prefix(subgroup_base); + } else if (subgroup_base.begins_with(path)) { + // Keep it, this is used pretty often. } else { - group = ""; //no longer using group base, clear + subgroup = ""; // The prefix changed, we are no longer in the subgroup. + } + } + + // Check if we exit or not a group. If there is a prefix, remove it from the property label string. + if (group != "" && group_base != "" && subgroup == "") { + if (path.begins_with(group_base)) { + path = path.trim_prefix(group_base); + } else if (group_base.begins_with(path)) { + // Keep it, this is used pretty often. + } else { + group = ""; // The prefix changed, we are no longer in the group. subgroup = ""; } } - } - if (subgroup != "") { - basename = subgroup + "/" + basename; - } - if (group != "") { - basename = group + "/" + basename; - } - String name = (basename.find("/") != -1) ? basename.substr(basename.rfind("/") + 1) : basename; + // Add the group and subgroup to the path. + if (subgroup != "") { + path = subgroup + "/" + path; + } + if (group != "") { + path = group + "/" + path; + } + } + // Get the property label's string. + String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; if (capitalize_paths) { - int dot = name.find("."); + // Capitalize paths. + int dot = property_label_string.find("."); if (dot != -1) { - String ov = name.substr(dot); - name = name.substr(0, dot); - name = name.capitalize(); - name += ov; - + String ov = property_label_string.substr(dot); + property_label_string = property_label_string.substr(0, dot); + property_label_string = property_label_string.capitalize(); + property_label_string += ov; } else { - name = name.capitalize(); + property_label_string = property_label_string.capitalize(); } } - String path; - { - int idx = basename.rfind("/"); - if (idx > -1) { - path = basename.left(idx); - } + // Remove the property from the path. + int idx = path.rfind("/"); + if (idx > -1) { + path = path.left(idx); + } else { + path = ""; } + // Ignore properties that do not fit the filter. if (use_filter && filter != "") { - String cat = path; - - if (capitalize_paths) { - cat = cat.capitalize(); - } - - if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) { + if (!filter.is_subsequence_ofi(path) && !filter.is_subsequence_ofi(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) { continue; } } + // Recreate the category vbox if it was reset. if (category_vbox == nullptr) { category_vbox = memnew(VBoxContainer); main_vbox->add_child(category_vbox); } - VBoxContainer *current_vbox = main_vbox; + // Find the correct section/vbox to add the property editor to. + VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index); + if (!root_vbox) { + continue; + } - { - String acc_path = ""; - int level = 1; - for (int i = 0; i < path.get_slice_count("/"); i++) { - String path_name = path.get_slice("/", i); - if (i > 0) { - acc_path += "/"; - } - acc_path += path_name; - if (!item_path.has(acc_path)) { - EditorInspectorSection *section = memnew(EditorInspectorSection); - current_vbox->add_child(section); - sections.push_back(section); - - if (capitalize_paths) { - path_name = path_name.capitalize(); - } + if (!vbox_per_path.has(root_vbox)) { + vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>(); + vbox_per_path[root_vbox][""] = root_vbox; + } + + VBoxContainer *current_vbox = root_vbox; + String acc_path = ""; + int level = 1; + + Vector<String> components = path.split("/"); + for (int i = 0; i < components.size(); i++) { + String component = components[i]; + acc_path += (i > 0) ? "/" + component : component; - Color c = sscolor; - c.a /= level; - section->setup(acc_path, path_name, object, c, use_folding); + if (!vbox_per_path[root_vbox].has(acc_path)) { + // If the section does not exists, create it. + EditorInspectorSection *section = memnew(EditorInspectorSection); + current_vbox->add_child(section); + sections.push_back(section); - VBoxContainer *vb = section->get_vbox(); - item_path[acc_path] = vb; - section_map[vb] = section; + if (capitalize_paths) { + component = component.capitalize(); } - current_vbox = item_path[acc_path]; - level = (MIN(level + 1, 4)); - } - if (current_vbox == main_vbox) { - //do not add directly to the main vbox, given it has no spacing - if (category_vbox == nullptr) { - category_vbox = memnew(VBoxContainer); + Color c = sscolor; + c.a /= level; + section->setup(acc_path, component, object, c, use_folding); + + vbox_per_path[root_vbox][acc_path] = section->get_vbox(); + } + + current_vbox = vbox_per_path[root_vbox][acc_path]; + level = (MIN(level + 1, 4)); + } + + // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly). + if (current_vbox == main_vbox) { + current_vbox = category_vbox; + } + + // Check if the property is an array counter, if so create a dedicated array editor for the array. + if (p.usage & PROPERTY_USAGE_ARRAY) { + EditorInspectorArray *editor_inspector_array = nullptr; + StringName array_element_prefix; + Color c = sscolor; + c.a /= level; + if (p.type == Variant::NIL) { + // Setup the array to use a method to create/move/delete elements. + array_element_prefix = p.class_name; + editor_inspector_array = memnew(EditorInspectorArray); + + String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; + array_label = property_label_string.capitalize(); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); + } else if (p.type == Variant::INT) { + // Setup the array to use the count property and built-in functions to create/move/delete elements. + Vector<String> class_name_components = String(p.class_name).split(","); + if (class_name_components.size() == 2) { + array_element_prefix = class_name_components[1]; + editor_inspector_array = memnew(EditorInspectorArray); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); } - current_vbox = category_vbox; } + + if (editor_inspector_array) { + current_vbox->add_child(editor_inspector_array); + editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array; + } + continue; } + // Checkable and checked properties. bool checkable = false; bool checked = false; if (p.usage & PROPERTY_USAGE_CHECKABLE) { @@ -1910,6 +2727,9 @@ void EditorInspector::update_tree() { checked = p.usage & PROPERTY_USAGE_CHECKED; } + bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only; + + // Mark properties that would require an editor restart (mostly when editing editor settings). if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) { restart_request_props.insert(p.name); } @@ -1917,14 +2737,19 @@ void EditorInspector::update_tree() { String doc_hint; if (use_doc_hints) { + // Build the doc hint, to use as tooltip. + + // Get the class name. StringName classname = object->get_class_name(); if (object_class != String()) { classname = object_class; } + StringName propname = property_prefix + p.name; String descr; bool found = false; + // Search for the property description in the cache. Map<StringName, Map<StringName, String>>::Element *E = descr_cache.find(classname); if (E) { Map<StringName, String>::Element *F = E->get().find(propname); @@ -1935,6 +2760,7 @@ void EditorInspector::update_tree() { } if (!found) { + // Build the property description String and add it to the cache. DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname); while (F && descr == String()) { @@ -1967,17 +2793,18 @@ void EditorInspector::update_tree() { doc_hint = descr; } + // Seach for the inspector plugin that will handle the properties. Then add the correct property editor to it. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors); - List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector + List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; // Make a copy, since plugins may be used again in a sub-inspector. ped->added_editors.clear(); for (const EditorInspectorPlugin::AddedEditor &F : editors) { EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor); if (ep) { - //set all this before the control gets the ENTER_TREE notification + // Set all this before the control gets the ENTER_TREE notification. ep->object = object; if (F.properties.size()) { @@ -1992,7 +2819,7 @@ void EditorInspector::update_tree() { ep->set_label(F.label); } else { // Use the existing one. - ep->set_label(name); + ep->set_label(property_label_string); } for (int i = 0; i < F.properties.size(); i++) { String prop = F.properties[i]; @@ -2008,18 +2835,16 @@ void EditorInspector::update_tree() { ep->set_checkable(checkable); ep->set_checked(checked); ep->set_keying(keying); - - ep->set_read_only(read_only); + ep->set_read_only(property_read_only); ep->set_deletable(deletable_properties); } current_vbox->add_child(F.property_editor); if (ep) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed)); - if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED); - } + // Eventually, set other properties/signals after the property editor got added to the tree. + bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed), varray(update_all)); ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed)); ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), varray(), CONNECT_DEFERRED); ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value)); @@ -2044,17 +2869,17 @@ void EditorInspector::update_tree() { } if (exclusive) { + // If we know the plugin is exclusive, we don't need to go through other plugins. break; } } } + // Get the lists of to add at the end. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_end(); _parse_added_editors(main_vbox, ped); } - - //see if this property exists and should be kept } void EditorInspector::update_property(const String &p_prop) { @@ -2093,6 +2918,7 @@ void EditorInspector::edit(Object *p_object) { _clear(); object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback)); } + per_array_page.clear(); object = p_object; @@ -2238,6 +3064,15 @@ void EditorInspector::set_use_deletable_properties(bool p_enabled) { deletable_properties = p_enabled; } +void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) { + int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0; + int new_page = MAX(0, p_new_page); + if (new_page != prev_page) { + per_array_page[p_array_prefix] = new_page; + update_tree_pending = true; + } +} + void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore return; @@ -2338,14 +3173,14 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } } -void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) { // The "changing" variable must be true for properties that trigger events as typing occurs, // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc. if (p_changing) { this->changing++; } - _edit_set(p_path, p_value, false, p_name); + _edit_set(p_path, p_value, p_update_all, p_name); if (p_changing) { this->changing--; @@ -2356,11 +3191,7 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v } } -void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { - update_tree(); -} - -void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { +void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) { ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); ERR_FAIL_COND(p_paths.size() != p_values.size()); String names; @@ -2377,9 +3208,13 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array emit_signal(SNAME("restart_requested")); } } - changing++; + if (p_changing) { + changing++; + } undo_redo->commit_action(); - changing--; + if (p_changing) { + changing--; + } } void EditorInspector::_property_keyed(const String &p_path, bool p_advance) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 8c522f00ef..5992c23f8c 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -32,8 +32,12 @@ #define EDITOR_INSPECTOR_H #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/texture_rect.h" class UndoRedo; @@ -115,6 +119,7 @@ private: protected: void _notification(int p_what); static void _bind_methods(); + virtual void _set_read_only(bool p_read_only); virtual void gui_input(const Ref<InputEvent> &p_event) override; virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; @@ -250,9 +255,7 @@ class EditorInspectorSection : public Container { String label; String section; - Object *object; - VBoxContainer *vbox; - bool vbox_added; //optimization + bool vbox_added; // Optimization. Color bg_color; bool foldable; @@ -262,6 +265,9 @@ class EditorInspectorSection : public Container { void _test_unfold(); protected: + Object *object; + VBoxContainer *vbox; + void _notification(int p_what); static void _bind_methods(); virtual void gui_input(const Ref<InputEvent> &p_event) override; @@ -280,6 +286,118 @@ public: ~EditorInspectorSection(); }; +class EditorInspectorArray : public EditorInspectorSection { + GDCLASS(EditorInspectorArray, EditorInspectorSection); + + UndoRedo *undo_redo; + + enum Mode { + MODE_NONE, + MODE_USE_COUNT_PROPERTY, + MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION, + } mode; + StringName count_property; + StringName array_element_prefix; + + int count = 0; + + VBoxContainer *elements_vbox; + + Control *control_dropping; + bool dropping = false; + + Button *add_button; + + AcceptDialog *resize_dialog; + int new_size = 0; + LineEdit *new_size_line_edit; + + // Pagination + int page_lenght = 5; + int page = 0; + int max_page = 0; + int begin_array_index = 0; + int end_array_index = 0; + HBoxContainer *hbox_pagination; + Button *first_page_button; + Button *prev_page_button; + LineEdit *page_line_edit; + Label *page_count_label; + Button *next_page_button; + Button *last_page_button; + + enum MenuOptions { + OPTION_MOVE_UP = 0, + OPTION_MOVE_DOWN, + OPTION_NEW_BEFORE, + OPTION_NEW_AFTER, + OPTION_REMOVE, + OPTION_CLEAR_ARRAY, + OPTION_RESIZE_ARRAY, + }; + int popup_array_index_pressed = -1; + PopupMenu *rmb_popup; + + struct ArrayElement { + PanelContainer *panel; + MarginContainer *margin; + HBoxContainer *hbox; + TextureRect *move_texture_rect; + VBoxContainer *vbox; + }; + LocalVector<ArrayElement> array_elements; + + Ref<StyleBoxFlat> odd_style; + Ref<StyleBoxFlat> even_style; + + int _get_array_count(); + void _add_button_pressed(); + + void _first_page_button_pressed(); + void _prev_page_button_pressed(); + void _page_line_edit_text_submitted(String p_text); + void _next_page_button_pressed(); + void _last_page_button_pressed(); + + void _rmb_popup_id_pressed(int p_id); + + void _control_dropping_draw(); + + void _vbox_visibility_changed(); + + void _panel_draw(int p_index); + void _panel_gui_input(Ref<InputEvent> p_event, int p_index); + void _move_element(int p_element_index, int p_to_pos); + void _clear_array(); + void _resize_array(int p_size); + Array _extract_properties_as_array(const List<PropertyInfo> &p_list); + int _drop_position() const; + + void _new_size_line_edit_text_changed(String p_text); + void _new_size_line_edit_text_submitted(String p_text); + void _resize_dialog_confirmed(); + + void _update_elements_visibility(); + void _setup(); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_undo_redo(UndoRedo *p_undo_redo); + + void setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + void setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + VBoxContainer *get_vbox(int p_index); + + EditorInspectorArray(); +}; + class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); @@ -339,9 +457,8 @@ class EditorInspector : public ScrollContainer { void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); - void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _multiple_properties_changed(Vector<String> p_paths, Array p_values); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false); + void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false); void _property_keyed(const String &p_path, bool p_advance); void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance); void _property_deleted(const String &p_path); @@ -354,6 +471,9 @@ class EditorInspector : public ScrollContainer { void _node_removed(Node *p_node); + Map<StringName, int> per_array_page; + void _page_change_request(int p_new_page, const StringName &p_array_prefix); + void _changed_callback(); void _edit_request_change(Object *p_object, const String &p_prop); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b1c546a8c0..f86a36df2f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2092,7 +2092,6 @@ void EditorNode::_edit_current() { Object *prev_inspected_object = get_inspector()->get_edited_object(); - bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties")); bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")); bool is_resource = current_obj->is_class("Resource"); bool is_node = current_obj->is_class("Node"); @@ -2150,7 +2149,6 @@ void EditorNode::_edit_current() { if (current_obj->is_class("EditorDebuggerRemoteObject")) { editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow."); - capitalize = false; disable_folding = true; } else if (current_obj->is_class("MultiNodeEdit")) { Node *scene = get_edited_scene(); @@ -2187,10 +2185,6 @@ void EditorNode::_edit_current() { inspector_dock->set_warning(editable_warning); - if (get_inspector()->is_capitalize_paths_enabled() != capitalize) { - get_inspector()->set_enable_capitalize_paths(capitalize); - } - if (get_inspector()->is_using_folding() == disable_folding) { get_inspector()->set_use_folding(!disable_folding); } @@ -4770,6 +4764,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) { return true; } +Error EditorNode::run_play_native(int p_idx, int p_platform) { + return run_native->run_native(p_idx, p_platform); +} + void EditorNode::run_play() { _menu_option_confirm(RUN_STOP, true); _run(false); diff --git a/editor/editor_node.h b/editor/editor_node.h index 51d01d07ef..03c18a8972 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -898,6 +898,7 @@ public: bool ensure_main_scene(bool p_from_native); + Error run_play_native(int p_idx, int p_platform); void run_play(); void run_play_current(); void run_play_custom(const String &p_custom); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index cb7fffe592..1729705be5 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -51,6 +51,10 @@ EditorPropertyNil::EditorPropertyNil() { ///////////////////// TEXT ///////////////////////// +void EditorPropertyText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); +}; + void EditorPropertyText::_text_submitted(const String &p_string) { if (updating) { return; @@ -68,9 +72,9 @@ void EditorPropertyText::_text_changed(const String &p_string) { } if (string_name) { - emit_changed(get_edited_property(), StringName(p_string), "", true); + emit_changed(get_edited_property(), StringName(p_string)); } else { - emit_changed(get_edited_property(), p_string, "", true); + emit_changed(get_edited_property(), p_string); } } @@ -108,6 +112,11 @@ EditorPropertyText::EditorPropertyText() { ///////////////////// MULTILINE TEXT ///////////////////////// +void EditorPropertyMultilineText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); + open_big_text->set_disabled(p_read_only); +}; + void EditorPropertyMultilineText::_big_text_changed() { text->set_text(big_text->get_text()); emit_changed(get_edited_property(), big_text->get_text(), "", true); @@ -180,6 +189,11 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() { ///////////////////// TEXT ENUM ///////////////////////// +void EditorPropertyTextEnum::_set_read_only(bool p_read_only) { + option_button->set_disabled(p_read_only); + edit_button->set_disabled(p_read_only); +}; + void EditorPropertyTextEnum::_emit_changed_value(String p_string) { if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); @@ -328,6 +342,11 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() { ///////////////////// PATH ///////////////////////// +void EditorPropertyPath::_set_read_only(bool p_read_only) { + path->set_editable(!p_read_only); + path_edit->set_disabled(p_read_only); +}; + void EditorPropertyPath::_path_selected(const String &p_path) { emit_changed(get_edited_property(), p_path); update_property(); @@ -420,6 +439,10 @@ EditorPropertyPath::EditorPropertyPath() { ///////////////////// CLASS NAME ///////////////////////// +void EditorPropertyClassName::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) { base_type = p_base_type; dialog->set_base_type(base_type); @@ -461,6 +484,10 @@ EditorPropertyClassName::EditorPropertyClassName() { ///////////////////// MEMBER ///////////////////////// +void EditorPropertyMember::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyMember::_property_selected(const String &p_selected) { emit_changed(get_edited_property(), p_selected); update_property(); @@ -557,6 +584,11 @@ EditorPropertyMember::EditorPropertyMember() { } ///////////////////// CHECK ///////////////////////// + +void EditorPropertyCheck::_set_read_only(bool p_read_only) { + checkbox->set_disabled(p_read_only); +}; + void EditorPropertyCheck::_checkbox_pressed() { emit_changed(get_edited_property(), checkbox->is_pressed()); } @@ -580,6 +612,10 @@ EditorPropertyCheck::EditorPropertyCheck() { ///////////////////// ENUM ///////////////////////// +void EditorPropertyEnum::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + void EditorPropertyEnum::_option_selected(int p_which) { int64_t val = options->get_item_metadata(p_which); emit_changed(get_edited_property(), val); @@ -628,6 +664,12 @@ EditorPropertyEnum::EditorPropertyEnum() { ///////////////////// FLAGS ///////////////////////// +void EditorPropertyFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flags) { + check->set_disabled(p_read_only); + } +}; + void EditorPropertyFlags::_flag_toggled() { uint32_t value = 0; for (int i = 0; i < flags.size(); i++) { @@ -698,6 +740,7 @@ private: bool expanded = false; int expansion_rows = 0; int hovered_index = -1; + bool read_only = false; Size2 get_grid_size() const { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); @@ -712,6 +755,10 @@ public: Vector<String> names; Vector<String> tooltips; + void set_read_only(bool p_read_only) { + read_only = p_read_only; + } + virtual Size2 get_minimum_size() const override { Size2 min_size = get_grid_size(); @@ -736,6 +783,9 @@ public: } void gui_input(const Ref<InputEvent> &p_ev) override { + if (read_only) { + return; + } const Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid()) { bool expand_was_hovered = expand_hovered; @@ -799,12 +849,12 @@ public: const int bsize = (grid_size.height * 80 / 100) / 2; const int h = bsize * 2 + 1; - Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); + Color color = get_theme_color(read_only ? SNAME("disabled_highlight_color") : SNAME("highlight_color"), SNAME("Editor")); - Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + Color text_color = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_color"), SNAME("Editor")); text_color.a *= 0.5; - Color text_color_on = get_theme_color(SNAME("font_hover_color"), SNAME("Editor")); + Color text_color_on = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_hover_color"), SNAME("Editor")); text_color_on.a *= 0.7; const int vofs = (grid_size.height - h) / 2; @@ -935,6 +985,11 @@ public: } }; +void EditorPropertyLayers::_set_read_only(bool p_read_only) { + button->set_disabled(p_read_only); + grid->set_read_only(p_read_only); +}; + void EditorPropertyLayers::_grid_changed(uint32_t p_grid) { emit_changed(get_edited_property(), p_grid); } @@ -1071,6 +1126,10 @@ EditorPropertyLayers::EditorPropertyLayers() { ///////////////////// INT ///////////////////////// +void EditorPropertyInteger::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyInteger::_value_changed(int64_t val) { if (setting) { return; @@ -1113,6 +1172,10 @@ EditorPropertyInteger::EditorPropertyInteger() { ///////////////////// OBJECT ID ///////////////////////// +void EditorPropertyObjectID::_set_read_only(bool p_read_only) { + edit->set_disabled(p_read_only); +}; + void EditorPropertyObjectID::_edit_pressed() { emit_signal(SNAME("object_id_selected"), get_edited_property(), get_edited_object()->get(get_edited_property())); } @@ -1151,6 +1214,10 @@ EditorPropertyObjectID::EditorPropertyObjectID() { ///////////////////// FLOAT ///////////////////////// +void EditorPropertyFloat::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyFloat::_value_changed(double val) { if (setting) { return; @@ -1197,7 +1264,14 @@ EditorPropertyFloat::EditorPropertyFloat() { ///////////////////// EASING ///////////////////////// +void EditorPropertyEasing::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + if (is_read_only()) { + return; + } const Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { if (mb->is_double_click() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { @@ -1271,12 +1345,12 @@ void EditorPropertyEasing::_draw_easing() { const Ref<Font> f = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - const Color font_color = get_theme_color(SNAME("font_color"), SNAME("Label")); + const Color font_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color line_color; if (dragging) { line_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); } else { - line_color = get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.9); + line_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")) * Color(1, 1, 1, 0.9); } Vector<Point2> points; @@ -1409,6 +1483,12 @@ EditorPropertyEasing::EditorPropertyEasing() { ///////////////////// VECTOR2 ///////////////////////// +void EditorPropertyVector2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1492,6 +1572,12 @@ EditorPropertyVector2::EditorPropertyVector2(bool p_force_wide) { ///////////////////// RECT2 ///////////////////////// +void EditorPropertyRect2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1589,6 +1675,12 @@ EditorPropertyRect2::EditorPropertyRect2(bool p_force_wide) { ///////////////////// VECTOR3 ///////////////////////// +void EditorPropertyVector3::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1701,6 +1793,12 @@ EditorPropertyVector3::EditorPropertyVector3(bool p_force_wide) { ///////////////////// VECTOR2i ///////////////////////// +void EditorPropertyVector2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1784,6 +1882,12 @@ EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) { ///////////////////// RECT2i ///////////////////////// +void EditorPropertyRect2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1881,6 +1985,12 @@ EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) { ///////////////////// VECTOR3i ///////////////////////// +void EditorPropertyVector3i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1965,6 +2075,12 @@ EditorPropertyVector3i::EditorPropertyVector3i(bool p_force_wide) { ///////////////////// PLANE ///////////////////////// +void EditorPropertyPlane::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyPlane::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2052,6 +2168,12 @@ EditorPropertyPlane::EditorPropertyPlane(bool p_force_wide) { ///////////////////// QUATERNION ///////////////////////// +void EditorPropertyQuaternion::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2136,6 +2258,12 @@ EditorPropertyQuaternion::EditorPropertyQuaternion() { ///////////////////// AABB ///////////////////////// +void EditorPropertyAABB::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyAABB::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2213,6 +2341,12 @@ EditorPropertyAABB::EditorPropertyAABB() { ///////////////////// TRANSFORM2D ///////////////////////// +void EditorPropertyTransform2D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2289,6 +2423,12 @@ EditorPropertyTransform2D::EditorPropertyTransform2D() { ///////////////////// BASIS ///////////////////////// +void EditorPropertyBasis::_set_read_only(bool p_read_only) { + for (int i = 0; i < 9; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyBasis::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2371,6 +2511,12 @@ EditorPropertyBasis::EditorPropertyBasis() { ///////////////////// TRANSFORM ///////////////////////// +void EditorPropertyTransform3D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 12; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2461,6 +2607,10 @@ EditorPropertyTransform3D::EditorPropertyTransform3D() { ////////////// COLOR PICKER ////////////////////// +void EditorPropertyColor::_set_read_only(bool p_read_only) { + picker->set_disabled(p_read_only); +}; + void EditorPropertyColor::_color_changed(const Color &p_color) { // Cancel the color change if the current color is identical to the new one. if (get_edited_object()->get(get_edited_property()) == p_color) { @@ -2533,6 +2683,11 @@ EditorPropertyColor::EditorPropertyColor() { ////////////// NODE PATH ////////////////////// +void EditorPropertyNodePath::_set_read_only(bool p_read_only) { + assign->set_disabled(p_read_only); + clear->set_disabled(p_read_only); +}; + void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { NodePath path = p_path; Node *base_node = nullptr; @@ -2678,6 +2833,10 @@ EditorPropertyRID::EditorPropertyRID() { ////////////// RESOURCE ////////////////////// +void EditorPropertyResource::_set_read_only(bool p_read_only) { + resource_picker->set_editable(!p_read_only); +}; + void EditorPropertyResource::_resource_selected(const RES &p_resource) { if (use_sub_inspector) { bool unfold = !get_edited_object()->editor_is_section_unfolded(get_edited_property()); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 0cb21bb391..cee5ab96a7 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -59,6 +59,7 @@ class EditorPropertyText : public EditorProperty { void _text_submitted(const String &p_string); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -81,6 +82,7 @@ class EditorPropertyMultilineText : public EditorProperty { void _open_big_text(); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -115,6 +117,7 @@ class EditorPropertyTextEnum : public EditorProperty { void _custom_value_cancelled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -139,6 +142,7 @@ class EditorPropertyPath : public EditorProperty { void _path_focus_exited(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -161,6 +165,7 @@ private: void _dialog_created(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -182,7 +187,6 @@ public: MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base - }; private: @@ -195,6 +199,7 @@ private: void _property_select(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -210,6 +215,7 @@ class EditorPropertyCheck : public EditorProperty { void _checkbox_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -224,6 +230,7 @@ class EditorPropertyEnum : public EditorProperty { void _option_selected(int p_which); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -242,6 +249,7 @@ class EditorPropertyFlags : public EditorProperty { void _flag_toggled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -276,6 +284,7 @@ private: void _menu_pressed(int p_menu); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -291,6 +300,7 @@ class EditorPropertyInteger : public EditorProperty { void _value_changed(int64_t p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -306,6 +316,7 @@ class EditorPropertyObjectID : public EditorProperty { void _edit_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -322,6 +333,7 @@ class EditorPropertyFloat : public EditorProperty { void _value_changed(double p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -363,6 +375,7 @@ class EditorPropertyEasing : public EditorProperty { void _notification(int p_what); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -378,6 +391,7 @@ class EditorPropertyVector2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -394,6 +408,7 @@ class EditorPropertyRect2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -411,6 +426,7 @@ class EditorPropertyVector3 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -429,6 +445,7 @@ class EditorPropertyVector2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -445,6 +462,7 @@ class EditorPropertyRect2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -461,6 +479,7 @@ class EditorPropertyVector3i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -477,6 +496,7 @@ class EditorPropertyPlane : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -493,6 +513,7 @@ class EditorPropertyQuaternion : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -509,6 +530,7 @@ class EditorPropertyAABB : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -525,6 +547,7 @@ class EditorPropertyTransform2D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -541,6 +564,7 @@ class EditorPropertyBasis : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -557,6 +581,7 @@ class EditorPropertyTransform3D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -578,6 +603,7 @@ class EditorPropertyColor : public EditorProperty { Color last_color; protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -600,6 +626,7 @@ class EditorPropertyNodePath : public EditorProperty { void _node_clear(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -644,6 +671,7 @@ class EditorPropertyResource : public EditorProperty { void _update_property_bg(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index e115cd77e1..5828549bdc 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) { small_icon.instantiate(); small_icon->create_from_image(im); MenuButton *mb = memnew(MenuButton); - mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i)); - mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i)); + mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i)); + mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i)); mb->set_icon(small_icon); add_child(mb); menus[i] = mb; @@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) { } } -void EditorRunNative::_run_native(int p_idx, int p_platform) { +Error EditorRunNative::run_native(int p_idx, int p_platform) { if (!EditorNode::get_singleton()->ensure_main_scene(true)) { resume_idx = p_idx; resume_platform = p_platform; - return; + return OK; } Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform); - ERR_FAIL_COND(eep.is_null()); + ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE); if (p_idx == -1) { if (eep->get_options_count() == 1) { menus[p_platform]->get_popup()->hide(); p_idx = 0; } else { - return; + return ERR_INVALID_PARAMETER; } } @@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) { if (preset.is_null()) { EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable.")); - return; + return ERR_UNAVAILABLE; } emit_signal(SNAME("native_run"), preset); @@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) { flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION; } - eep->run(preset, p_idx, flags); + return eep->run(preset, p_idx, flags); } void EditorRunNative::resume_run_native() { - _run_native(resume_idx, resume_platform); + run_native(resume_idx, resume_platform); } void EditorRunNative::_bind_methods() { diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h index 3516f668c6..97f6fc005a 100644 --- a/editor/editor_run_native.h +++ b/editor/editor_run_native.h @@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer { int resume_idx; int resume_platform; - void _run_native(int p_idx, int p_platform); - protected: static void _bind_methods(); void _notification(int p_what); public: + Error run_native(int p_idx, int p_platform); bool is_deploy_debug_remote_enabled() const; void resume_run_native(); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 3f65b101f7..8cd636ddf3 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -221,7 +221,7 @@ void EditorSpinSlider::_draw_spin_slider() { bool rtl = is_layout_rtl(); Vector2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : SNAME("normal"), SNAME("LineEdit")); if (!flat) { draw_style_box(sb, Rect2(Vector2(), size)); } @@ -233,7 +233,7 @@ void EditorSpinSlider::_draw_spin_slider() { int label_width = font->get_string_size(label, font_size).width; int number_width = size.width - sb->get_minimum_size().width - label_width - sep; - Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); if (get_step() == 1) { number_width -= updown->get_width(); @@ -243,7 +243,7 @@ void EditorSpinSlider::_draw_spin_slider() { int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); - Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); + Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color lc; if (use_custom_label_color) { lc = custom_label_color; @@ -299,7 +299,7 @@ void EditorSpinSlider::_draw_spin_slider() { TS->free(num_rid); if (get_step() == 1) { - Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); int updown_vofs = (size.height - updown2->get_height()) / 2; if (rtl) { updown_offset = sb->get_margin(SIDE_LEFT); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 8c348731d6..8a08f4e450 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -395,12 +395,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color separator_color = Color(mono_color.r, mono_color.g, mono_color.b, 0.1); const Color highlight_color = Color(accent_color.r, accent_color.g, accent_color.b, 0.275); + const Color disabled_highlight_color = highlight_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); float prev_icon_saturation = theme->has_color("icon_saturation", "Editor") ? theme->get_color("icon_saturation", "Editor").r : 1.0; theme->set_color("icon_saturation", "Editor", Color(icon_saturation, icon_saturation, icon_saturation)); //can't save single float in theme, so using color theme->set_color("accent_color", "Editor", accent_color); theme->set_color("highlight_color", "Editor", highlight_color); + theme->set_color("disabled_highlight_color", "Editor", disabled_highlight_color); theme->set_color("base_color", "Editor", base_color); theme->set_color("dark_color_1", "Editor", dark_color_1); theme->set_color("dark_color_2", "Editor", dark_color_2); @@ -424,6 +426,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color warning_color = Color(1, 0.87, 0.4); Color error_color = Color(1, 0.47, 0.42); Color property_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.5); + Color readonly_color = property_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); + Color readonly_error_color = error_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); if (!dark_theme) { // Darken some colors to be readable on a light background @@ -436,6 +440,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("warning_color", "Editor", warning_color); theme->set_color("error_color", "Editor", error_color); theme->set_color("property_color", "Editor", property_color); + theme->set_color("readonly_color", "Editor", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); if (!dark_theme) { theme->set_color("vulkan_color", "Editor", Color::hex(0xad1128ff)); @@ -696,6 +702,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "CheckBox", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "CheckBox", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "CheckBox", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "CheckBox", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "CheckBox", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "CheckBox", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "CheckBox", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_color("font_color", "CheckBox", font_color); theme->set_color("font_hover_color", "CheckBox", font_hover_color); @@ -742,6 +752,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "PopupMenu", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "PopupMenu", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "PopupMenu", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "PopupMenu", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons")); theme->set_icon("submenu_mirrored", "PopupMenu", theme->get_icon("ArrowLeft", "EditorIcons")); theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons")); @@ -803,6 +817,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("vseparation", "EditorProperty", (extra_spacing + default_margin_size) * EDSCALE); theme->set_color("error_color", "EditorProperty", error_color); theme->set_color("property_color", "EditorProperty", property_color); + theme->set_color("readonly_color", "EditorProperty", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35); theme->set_color("font_color", "EditorInspectorSection", inspector_section_color); @@ -1052,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("tab", "TextEdit", theme->get_icon("GuiTab", "EditorIcons")); theme->set_icon("space", "TextEdit", theme->get_icon("GuiSpace", "EditorIcons")); theme->set_color("font_color", "TextEdit", font_color); - theme->set_color("font_readonly_color", "LineEdit", font_readonly_color); + theme->set_color("font_readonly_color", "TextEdit", font_readonly_color); theme->set_color("caret_color", "TextEdit", font_color); theme->set_color("selection_color", "TextEdit", selection_color); theme->set_constant("line_spacing", "TextEdit", 4 * EDSCALE); @@ -1201,11 +1217,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // TooltipPanel Ref<StyleBoxFlat> style_tooltip = style_popup->duplicate(); style_tooltip->set_shadow_size(0); - style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE); + style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE * 0.5); style_tooltip->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE * 0.5); - style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE); + style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE * 0.5); style_tooltip->set_default_margin(SIDE_BOTTOM, default_margin_size * EDSCALE * 0.5); - style_tooltip->set_bg_color(mono_color.inverted() * Color(1, 1, 1, 0.9)); + style_tooltip->set_bg_color(dark_color_3 * Color(0.8, 0.8, 0.8, 0.9)); style_tooltip->set_border_width_all(0); theme->set_color("font_color", "TooltipLabel", font_hover_color); theme->set_color("font_color_shadow", "TooltipLabel", Color(0, 0, 0, 0)); @@ -1216,6 +1232,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // SpinBox theme->set_icon("updown", "SpinBox", theme->get_icon("GuiSpinboxUpdown", "EditorIcons")); + theme->set_icon("updown_disabled", "SpinBox", theme->get_icon("GuiSpinboxUpdownDisabled", "EditorIcons")); // ProgressBar theme->set_stylebox("bg", "ProgressBar", make_stylebox(theme->get_icon("GuiProgressBar", "EditorIcons"), 4, 4, 4, 4, 0, 0, 0, 0)); diff --git a/editor/icons/AnimatableBody2D.svg b/editor/icons/AnimatableBody2D.svg new file mode 100644 index 0000000000..f4fed813c9 --- /dev/null +++ b/editor/icons/AnimatableBody2D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#8da5f3" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#8da5f3"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/AnimatableBody3D.svg b/editor/icons/AnimatableBody3D.svg new file mode 100644 index 0000000000..2e472f0625 --- /dev/null +++ b/editor/icons/AnimatableBody3D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#fc7f7f"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/GuiCheckedDisabled.svg b/editor/icons/GuiCheckedDisabled.svg new file mode 100644 index 0000000000..6252176241 --- /dev/null +++ b/editor/icons/GuiCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080"/><path d="m11.501 3.734-5.612 5.612-1.704-1.681-1.5 1.5 3.204 3.181 7.111-7.113z" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioCheckedDisabled.svg b/editor/icons/GuiRadioCheckedDisabled.svg new file mode 100644 index 0000000000..94a1fffa0b --- /dev/null +++ b/editor/icons/GuiRadioCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080"/><path d="m12 8c0 2.209-1.791 4-4 4s-4-1.791-4-4 1.791-4 4-4 4 1.791 4 4" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioUncheckedDisabled.svg b/editor/icons/GuiRadioUncheckedDisabled.svg new file mode 100644 index 0000000000..3a75797c4d --- /dev/null +++ b/editor/icons/GuiRadioUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/GuiSpinboxUpdownDisabled.svg b/editor/icons/GuiSpinboxUpdownDisabled.svg new file mode 100644 index 0000000000..332c5e7bf8 --- /dev/null +++ b/editor/icons/GuiSpinboxUpdownDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.984 1.002c-.259.004-.507.108-.691.291l-4 4c-.398.383-.41 1.016-.027 1.414s1.016.41 1.414.027l.027-.027 3.293-3.293 3.293 3.293c.383.398 1.016.41 1.414.027s.41-1.016.027-1.414c-.01-.009-.018-.018-.027-.027l-4-4c-.191-.19-.452-.296-.723-.291zm4.006 7.984c-.264.006-.514.117-.697.307l-3.293 3.293-3.293-3.293c-.188-.193-.447-.303-.717-.303-.552 0-1 .448-1 1 0 .271.109.529.303.717l4 4c.391.391 1.023.391 1.414 0l4-4c.398-.383.41-1.016.027-1.414-.193-.202-.463-.313-.744-.307z" fill="#b3b3b3" fill-opacity=".7843"/></svg> diff --git a/editor/icons/GuiUncheckedDisabled.svg b/editor/icons/GuiUncheckedDisabled.svg new file mode 100644 index 0000000000..2b6515f9d7 --- /dev/null +++ b/editor/icons/GuiUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/PageFirst.svg b/editor/icons/PageFirst.svg new file mode 100644 index 0000000000..76078691ef --- /dev/null +++ b/editor/icons/PageFirst.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageFirst.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="M 6,9 3,6 6,3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 9,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageLast.svg b/editor/icons/PageLast.svg new file mode 100644 index 0000000000..17c874e8c9 --- /dev/null +++ b/editor/icons/PageLast.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageLast.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 6.0000414,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 3.0000414,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageNext.svg b/editor/icons/PageNext.svg new file mode 100644 index 0000000000..89ff6219bb --- /dev/null +++ b/editor/icons/PageNext.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageNext.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 4.5000207,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/icons/PagePrevious.svg b/editor/icons/PagePrevious.svg new file mode 100644 index 0000000000..a2fa84da0c --- /dev/null +++ b/editor/icons/PagePrevious.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PagePrevious.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 7.4999793,9 -3,-3 3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp index 71930e1e59..b6d0927ce6 100644 --- a/editor/import/collada.cpp +++ b/editor/import/collada.cpp @@ -287,7 +287,7 @@ void Collada::_parse_image(XMLParser &parser) { if (state.version < State::Version(1, 4, 0)) { /* <1.4 */ String path = parser.get_attribute_value("source").strip_edges(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode())); } @@ -300,7 +300,7 @@ void Collada::_parse_image(XMLParser &parser) { parser.read(); String path = parser.get_node_data().strip_edges().uri_decode(); - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path)); diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp index 4d92490675..c01d8068da 100644 --- a/editor/import/resource_importer_shader_file.cpp +++ b/editor/import/resource_importer_shader_file.cpp @@ -78,7 +78,7 @@ static String _include_function(const String &p_path, void *userpointer) { String *base_path = (String *)userpointer; String include = p_path; - if (include.is_rel_path()) { + if (include.is_relative_path()) { include = base_path->plus_file(include); } diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index daf7b15794..61745cb6ee 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -393,7 +393,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String float lossy = p_options["compress/lossy_quality"]; int pack_channels = p_options["compress/channel_pack"]; bool mipmaps = p_options["mipmaps/generate"]; - uint32_t mipmap_limit = int(mipmaps ? int(p_options["mipmaps/limit"]) : int(-1)); + uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit"]) : uint32_t(-1); bool fix_alpha_border = p_options["process/fix_alpha_border"]; bool premult_alpha = p_options["process/premult_alpha"]; bool normal_map_invert_y = p_options["process/normal_map_invert_y"]; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a24249b95b..7ef5993ec5 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" @@ -479,6 +480,29 @@ void ScriptEditor::_clear_execution(REF p_script) { } } +void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { + Ref<Script> script = Object::cast_to<Script>(*p_script); + if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { + if (edit(p_script, p_line, 0, false)) { + editor->push_item(p_script.ptr()); + + ScriptEditorBase *se = _get_current_editor(); + if (se) { + se->set_breakpoint(p_line, p_enabled); + } + } + } +} + +void ScriptEditor::_clear_breakpoints() { + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se) { + se->clear_breakpoints(); + } + } +} + ScriptEditorBase *ScriptEditor::_get_current_editor() const { int selected = tab_container->get_current_tab(); if (selected < 0 || selected >= tab_container->get_child_count()) { @@ -3484,6 +3508,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution)); debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution)); debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked)); + debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint)); + debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints)); menu_hb->add_spacer(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index a57aeea5c0..e2420b4623 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -155,6 +155,8 @@ public: virtual void tag_saved_version() = 0; virtual void reload(bool p_soft) {} virtual Array get_breakpoints() = 0; + virtual void set_breakpoint(int p_line, bool p_enabled) = 0; + virtual void clear_breakpoints() = 0; virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0; virtual void update_settings() = 0; virtual void set_debugger_active(bool p_active) = 0; @@ -374,6 +376,8 @@ class ScriptEditor : public PanelContainer { void _breaked(bool p_breaked, bool p_can_debug); void _update_window_menu(); void _script_created(Ref<Script> p_script); + void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); + void _clear_breakpoints(); ScriptEditorBase *_get_current_editor() const; Array _get_open_script_editors() const; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5f48106afc..48239a5d99 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } @@ -831,7 +830,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c if (info.is_singleton) { EditorNode::get_singleton()->load_scene(info.path); } - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { // Every symbol other than absolute path is relative path so keep this condition at last. String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { @@ -858,7 +857,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { ScriptLanguage::LookupResult result; if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { text_edit->set_symbol_lookup_word_as_valid(true); - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { text_edit->set_symbol_lookup_word_as_valid(true); @@ -1370,6 +1369,14 @@ Array ScriptTextEditor::get_breakpoints() { return code_editor->get_text_editor()->get_breakpointed_lines(); } +void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) { + code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled); +} + +void ScriptTextEditor::clear_breakpoints() { + code_editor->get_text_editor()->clear_breakpointed_lines(); +} + void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 1ca6f56ea1..4208d67f17 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -225,6 +225,8 @@ public: virtual void reload(bool p_soft) override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enabled) override; + virtual void clear_breakpoints() override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; virtual void update_settings() override; diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 839e1c5f7a..6bf0042393 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -121,6 +121,8 @@ public: virtual void set_edit_state(const Variant &p_state) override; virtual Vector<String> get_functions() override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enabled) override{}; + virtual void clear_breakpoints() override{}; virtual void goto_line(int p_line, bool p_with_error = false) override; void goto_line_selection(int p_line, int p_begin, int p_end); virtual void set_executing_line(int p_line) override; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index d406c2514c..fd5c59af34 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1465,12 +1465,13 @@ void TileDataTerrainsEditor::_tile_set_changed() { ERR_FAIL_COND(!tile_set.is_valid()); // Fix if wrong values are selected. - if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set >= tile_set->get_terrain_sets_count()) { + terrain_set = -1; dummy_object->set("terrain_set", -1); } - int terrain_set = int(dummy_object->get("terrain")); if (terrain_set >= 0) { - if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) { dummy_object->set("terrain", -1); } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 77084f551a..b5e070b4d6 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -3549,30 +3549,76 @@ void TileMapEditor::_update_layers_selection() { tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); } -void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { +void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); - if (tile_map) { - if (p_property == "layers_count") { - int new_layers_count = (int)p_new_value; - if (new_layers_count < tile_map->get_layers_count()) { - List<PropertyInfo> property_list; - tile_map->get_property_list(&property_list); - - for (PropertyInfo property_info : property_list) { - Vector<String> components = String(property_info.name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index >= new_layers_count) { - undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name)); - } - } + if (!tile_map) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "layer_") { + end = tile_map->get_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(tile_map, "add_layer", p_from_index); + } + + List<PropertyInfo> properties; + tile_map->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_map, pi.name); } } } } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(tile_map, "add_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_map, "remove_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); + } } bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -3851,7 +3897,7 @@ TileMapEditor::TileMapEditor() { _tab_changed(0); // Registers UndoRedo inspector callback. - EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback)); + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element)); } TileMapEditor::~TileMapEditor() { diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6e2f2ce2ba..6126db59e9 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -341,7 +341,7 @@ private: void _update_layers_selection(); // Inspector undo/redo callback. - void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); protected: void _notification(int p_what); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 432f48fa85..c3a3f40e00 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1866,7 +1866,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); if (tile_data) { diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ba98a7d6b3..48d0d9b333 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -330,11 +330,192 @@ void TileSetEditor::_tile_set_changed() { tile_set_changed_needs_update = true; } +void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + + TileSet *tile_set = Object::cast_to<TileSet>(p_edited); + if (!tile_set) { + return; + } + + Vector<String> components = String(p_array_prefix).split("/", true, 2); + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "occlusion_layer_") { + end = tile_set->get_occlusion_layers_count(); + } else if (p_array_prefix == "physics_layer_") { + end = tile_set->get_physics_layers_count(); + } else if (p_array_prefix == "terrain_set_") { + end = tile_set->get_terrain_sets_count(); + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + end = tile_set->get_terrains_count(terrain_set); + } else if (p_array_prefix == "navigation_layer_") { + end = tile_set->get_navigation_layers_count(); + } else if (p_array_prefix == "custom_data_layer_") { + end = tile_set->get_custom_data_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + List<PropertyInfo> properties; + tile_set->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_set, pi.name); + } + } + } + } + + // Save properties for TileSetAtlasSources tile data + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + if (tas.is_valid()) { + for (int j = 0; j < tas->get_tiles_count(); j++) { + Vector2i tile_id = tas->get_tile_id(j); + for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { + int alternative_id = tas->get_alternative_tile_id(tile_id, k); + TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + ERR_FAIL_COND(!tile_data); + + // Actually saving stuff. + if (p_array_prefix == "occlusion_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "physics_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index)); + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index)); + } + } + } else if (p_array_prefix == "terrain_set_") { + ADD_UNDO(tile_data, "terrain_set"); + for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) { + for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); + } + } + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index])); + } + } + } else if (p_array_prefix == "navigation_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "custom_data_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index)); + } + } + } + } + } + } +#undef ADD_UNDO + + // Add do method. + if (p_array_prefix == "occlusion_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "physics_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "terrain_set_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos); + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); + } + } else if (p_array_prefix == "navigation_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "custom_data_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos); + } + } +} + void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); if (tile_set) { Vector<String> components = p_property.split("/", true, 3); @@ -350,30 +531,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); ERR_FAIL_COND(!tile_data); - if (p_property == "occlusion_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_occlusion_layers_count(); - if (new_layer_count < old_layer_count) { - for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) { - ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index)); - } - } - } else if (p_property == "physics_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_physics_layers_count(); - if (new_layer_count < old_layer_count) { - for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index)); - } - } - } - } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) { + if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") { ADD_UNDO(tile_data, "terrain_set"); for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); @@ -381,22 +539,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); } } - } else if (p_property == "navigation_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_navigation_layers_count(); - if (new_layer_count < old_layer_count) { - for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) { - ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index)); - } - } - } else if (p_property == "custom_data_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_custom_data_layers_count(); - if (new_layer_count < old_layer_count) { - for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) { - ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index)); - } - } } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") { int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int(); ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer)); @@ -531,6 +673,7 @@ TileSetEditor::TileSetEditor() { tile_set_scenes_collection_source_editor->hide(); // Registers UndoRedo inspector callback. + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 970e3fabb6..fe854b2281 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -71,6 +71,7 @@ private: void _tile_set_changed(); + void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); protected: diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 5b1da11f12..50808a25af 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -2547,6 +2547,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } } } + _member_cancel(); VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()); if (uniform) { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index b8ccab78dd..db12e90540 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -222,7 +222,6 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("standalone"); presets.insert("32"); presets.insert("64"); - presets.insert("Server"); // Not available as an export platform yet, so it needs to be added manually EditorExport *ee = EditorExport::get_singleton(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e7ba80677d..d54bf73028 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -291,10 +291,12 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } } + // Display the node name in all tooltips so that long node names can be previewed + // without having to rename them. if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -303,7 +305,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -315,7 +317,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll type = p_node->get_class(); } - String tooltip = TTR("Type:") + " " + type; + String tooltip = String(p_node->get_name()) + "\n" + TTR("Type:") + " " + type; if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } |