diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 9fb12072ab2b..bcd3b63a0352 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -254,8 +254,8 @@ bool InputMap::event_is_action(const Ref &p_event, const StringName int InputMap::event_get_index(const Ref &p_event, const StringName &p_action, bool p_exact_match) const { int index = -1; - event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); - return index; + bool valid = event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); + return valid ? index : -1; } bool InputMap::event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const { diff --git a/core/io/json.cpp b/core/io/json.cpp index bb93e2fd5d26..131a7e80f753 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -198,7 +198,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to String str; while (true) { if (p_str[index] == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } else if (p_str[index] == '"') { index++; @@ -208,7 +208,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to index++; char32_t next = p_str[index]; if (next == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } char32_t res = 0; @@ -234,7 +234,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to for (int j = 0; j < 4; j++) { char32_t c = p_str[index + j + 1]; if (c == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } if (!is_hex_digit(c)) { @@ -270,7 +270,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to for (int j = 0; j < 4; j++) { char32_t c = p_str[index + j + 1]; if (c == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; return ERR_PARSE_ERROR; } if (!is_hex_digit(c)) { @@ -313,7 +313,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to res = next; } break; default: { - r_err_str = "Invalid escape sequence."; + r_err_str = "Invalid escape sequence"; return ERR_PARSE_ERROR; } } @@ -361,19 +361,20 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to r_token.value = id; return OK; } else { - r_err_str = "Unexpected character."; + r_err_str = "Unexpected character"; return ERR_PARSE_ERROR; } } } } + r_err_str = "Unknown error getting token"; return ERR_PARSE_ERROR; } Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str) { if (p_depth > Variant::MAX_RECURSION_DEPTH) { - r_err_str = "JSON structure is too deep. Bailing."; + r_err_str = "JSON structure is too deep"; return ERR_OUT_OF_MEMORY; } @@ -400,7 +401,7 @@ Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, in } else if (id == "null") { value = Variant(); } else { - r_err_str = "Expected 'true','false' or 'null', got '" + id + "'."; + r_err_str = vformat("Expected 'true', 'false', or 'null', got '%s'", id); return ERR_PARSE_ERROR; } } else if (token.type == TK_NUMBER) { @@ -408,7 +409,7 @@ Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, in } else if (token.type == TK_STRING) { value = token.value; } else { - r_err_str = "Expected value, got " + String(tk_name[token.type]) + "."; + r_err_str = vformat("Expected value, got '%s'", String(tk_name[token.type])); return ERR_PARSE_ERROR; } diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index b9fe121ea6d5..c7cfca190d71 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -198,11 +198,12 @@ Error PCKPacker::flush(bool p_verbose) { } for (int i = 0; i < files.size(); i++) { - int string_len = files[i].path.utf8().length(); + CharString utf8_string = files[i].path.utf8(); + int string_len = utf8_string.length(); int pad = _get_pad(4, string_len); fhead->store_32(uint32_t(string_len + pad)); - fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len); + fhead->store_buffer((const uint8_t *)utf8_string.get_data(), string_len); for (int j = 0; j < pad; j++) { fhead->store_8(0); } diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 3a0e2aa090d3..545659e460b0 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -47,7 +47,7 @@ String ResourceUID::get_cache_file() { static constexpr uint8_t uuid_characters[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', '0', '1', '2', '3', '4', '5', '6', '7', '8' }; static constexpr uint32_t uuid_characters_element_count = (sizeof(uuid_characters) / sizeof(*uuid_characters)); -static constexpr uint8_t max_uuid_number_length = 19; // Max 0x7FFFFFFFFFFFFFFF size is 19 digits. +static constexpr uint8_t max_uuid_number_length = 13; // Max 0x7FFFFFFFFFFFFFFF (uid://d4n4ub6itg400) size is 13 characters. String ResourceUID::id_to_text(ID p_id) const { if (p_id < 0) { @@ -56,12 +56,12 @@ String ResourceUID::id_to_text(ID p_id) const { char32_t tmp[max_uuid_number_length]; uint32_t tmp_size = 0; - while (p_id) { + do { uint32_t c = p_id % uuid_characters_element_count; tmp[tmp_size] = uuid_characters[c]; p_id /= uuid_characters_element_count; ++tmp_size; - } + } while (p_id); // tmp_size + uid:// (6) + 1 for null. String txt; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index f092905a3ab0..147ae88f2671 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1486,147 +1486,35 @@ void Variant::_clear_internal() { } Variant::operator int64_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return int64_t(_data._int); - case FLOAT: - return int64_t(_data._float); - case STRING: - return int64_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator int32_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return int32_t(_data._int); - case FLOAT: - return int32_t(_data._float); - case STRING: - return int32_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator int16_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return int16_t(_data._int); - case FLOAT: - return int16_t(_data._float); - case STRING: - return int16_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator int8_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return int8_t(_data._int); - case FLOAT: - return int8_t(_data._float); - case STRING: - return int8_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator uint64_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return uint64_t(_data._int); - case FLOAT: - return uint64_t(_data._float); - case STRING: - return uint64_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator uint32_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return uint32_t(_data._int); - case FLOAT: - return uint32_t(_data._float); - case STRING: - return uint32_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator uint16_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return uint16_t(_data._int); - case FLOAT: - return uint16_t(_data._float); - case STRING: - return uint16_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator uint8_t() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1 : 0; - case INT: - return uint8_t(_data._int); - case FLOAT: - return uint8_t(_data._float); - case STRING: - return uint8_t(operator String().to_int()); - default: { - return 0; - } - } + return _to_int(); } Variant::operator ObjectID() const { @@ -1644,39 +1532,11 @@ Variant::operator char32_t() const { } Variant::operator float() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1.0 : 0.0; - case INT: - return (float)_data._int; - case FLOAT: - return _data._float; - case STRING: - return operator String().to_float(); - default: { - return 0; - } - } + return _to_float(); } Variant::operator double() const { - switch (type) { - case NIL: - return 0; - case BOOL: - return _data._bool ? 1.0 : 0.0; - case INT: - return (double)_data._int; - case FLOAT: - return _data._float; - case STRING: - return operator String().to_float(); - default: { - return 0; - } - } + return _to_float(); } Variant::operator StringName() const { diff --git a/core/variant/variant.h b/core/variant/variant.h index ede1535e0c1c..633b9c9e831f 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -343,6 +343,44 @@ class Variant { void _variant_call_error(const String &p_method, Callable::CallError &error); + template + _ALWAYS_INLINE_ T _to_int() const { + switch (get_type()) { + case NIL: + return 0; + case BOOL: + return _data._bool ? 1 : 0; + case INT: + return T(_data._int); + case FLOAT: + return T(_data._float); + case STRING: + return reinterpret_cast(_data._mem)->to_int(); + default: { + return 0; + } + } + } + + template + _ALWAYS_INLINE_ T _to_float() const { + switch (type) { + case NIL: + return 0; + case BOOL: + return _data._bool ? 1 : 0; + case INT: + return T(_data._int); + case FLOAT: + return T(_data._float); + case STRING: + return reinterpret_cast(_data._mem)->to_float(); + default: { + return 0; + } + } + } + // Avoid accidental conversion. If you reached this point, it's because you most likely forgot to dereference // a Variant pointer (so add * like this: *variant_pointer). diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 2cae430bfbca..3ce242d49c9d 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -278,7 +278,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri char32_t ch = p_stream->get_char(); if (ch == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } else if (ch == '"') { @@ -287,7 +287,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri //escaped characters... char32_t next = p_stream->get_char(); if (next == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } @@ -317,7 +317,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri char32_t c = p_stream->get_char(); if (c == 0) { - r_err_str = "Unterminated String"; + r_err_str = "Unterminated string"; r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } @@ -504,7 +504,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri r_token.value = id.as_string(); return OK; } else { - r_err_str = "Unexpected character."; + r_err_str = "Unexpected character"; r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } @@ -512,6 +512,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri } } + r_err_str = "Unknown error getting token"; r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } @@ -1007,7 +1008,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, Object *obj = ClassDB::instantiate(type); if (!obj) { - r_err_str = "Can't instantiate Object() of type: " + type; + r_err_str = vformat("Can't instantiate Object() of type '%s'", type); return ERR_PARSE_ERROR; } @@ -1025,7 +1026,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, while (true) { if (p_stream->is_eof()) { - r_err_str = "Unexpected End of File while parsing Object()"; + r_err_str = "Unexpected EOF while parsing Object()"; return ERR_FILE_CORRUPT; } @@ -1123,7 +1124,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, String path = token.value; Ref res = ResourceLoader::load(path); if (res.is_null()) { - r_err_str = "Can't load resource at path: '" + path + "'."; + r_err_str = "Can't load resource at path: " + path; return ERR_PARSE_ERROR; } @@ -1135,7 +1136,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = res; } else { - r_err_str = "Expected string as argument for Resource()."; + r_err_str = "Expected string as argument for Resource()"; return ERR_PARSE_ERROR; } } @@ -1571,7 +1572,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = arr; } else { - r_err_str = "Unexpected identifier: '" + id + "'."; + r_err_str = vformat("Unexpected identifier '%s'", id); return ERR_PARSE_ERROR; } @@ -1590,7 +1591,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = token.value; return OK; } else { - r_err_str = "Expected value, got " + String(tk_name[token.type]) + "."; + r_err_str = vformat("Expected value, got '%s'", String(tk_name[token.type])); return ERR_PARSE_ERROR; } } @@ -1601,7 +1602,7 @@ Error VariantParser::_parse_array(Array &array, Stream *p_stream, int &line, Str while (true) { if (p_stream->is_eof()) { - r_err_str = "Unexpected End of File while parsing array"; + r_err_str = "Unexpected EOF while parsing array"; return ERR_FILE_CORRUPT; } @@ -1643,7 +1644,7 @@ Error VariantParser::_parse_dictionary(Dictionary &object, Stream *p_stream, int while (true) { if (p_stream->is_eof()) { - r_err_str = "Unexpected End of File while parsing dictionary"; + r_err_str = "Unexpected EOF while parsing dictionary"; return ERR_FILE_CORRUPT; } @@ -1775,7 +1776,7 @@ Error VariantParser::_parse_tag(Token &token, Stream *p_stream, int &line, Strin while (true) { if (p_stream->is_eof()) { - r_err_str = "Unexpected End of File while parsing tag: " + r_tag.name; + r_err_str = vformat("Unexpected EOF while parsing tag '%s'", r_tag.name); return ERR_FILE_CORRUPT; } @@ -1795,7 +1796,7 @@ Error VariantParser::_parse_tag(Token &token, Stream *p_stream, int &line, Strin } if (token.type != TK_IDENTIFIER) { - r_err_str = "Expected Identifier"; + r_err_str = "Expected identifier"; return ERR_PARSE_ERROR; } @@ -1808,6 +1809,7 @@ Error VariantParser::_parse_tag(Token &token, Stream *p_stream, int &line, Strin get_token(p_stream, token, line, r_err_str); if (token.type != TK_EQUAL) { + r_err_str = "Expected '=' after identifier"; return ERR_PARSE_ERROR; } diff --git a/doc/classes/CollisionPolygon2D.xml b/doc/classes/CollisionPolygon2D.xml index 1805683de511..9dbe465ecdc4 100644 --- a/doc/classes/CollisionPolygon2D.xml +++ b/doc/classes/CollisionPolygon2D.xml @@ -5,7 +5,7 @@ A node that provides a polygon shape to a [CollisionObject2D] parent and allows to edit it. The polygon can be concave or convex. This can give a detection shape to an [Area2D], turn [PhysicsBody2D] into a solid object, or give a hollow shape to a [StaticBody2D]. - [b]Warning:[/b] A non-uniformly scaled [CollisionShape2D] will likely not behave as expected. Make sure to keep its scale the same on all axes and adjust its shape resource instead. + [b]Warning:[/b] A non-uniformly scaled [CollisionPolygon2D] will likely not behave as expected. Make sure to keep its scale the same on all axes and adjust its polygon instead. diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index a11284a8449f..f57a94e67d27 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -168,9 +168,6 @@ Custom texture for the hue selection slider on the right. - - Custom texture for the H slider in the OKHSL color mode. - The icon for color preset drop down menu when expanded. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 1f495b87117c..35edebeb4a95 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -145,11 +145,11 @@ Displays OS native dialog for selecting files or directories in the file system. - Each filter string in the [param filters] array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. See also [member FileDialog.filters]. + Each filter string in the [param filters] array should be formatted like this: [code]*.png,*.jpg,*.jpeg;Image Files;image/png,image/jpeg[/code]. The description text of the filter is optional and can be omitted. It is recommended to set both file extension and MIME type. See also [member FileDialog.filters]. Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. [b]On Android,[/b] callback argument [code]selected_filter_index[/code] is always zero. [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, macOS, and Android. [b]Note:[/b] [param current_directory] might be ignored. - [b]Note:[/b] On Android, the filter strings in the [param filters] array should be specified using MIME types, for example:[code]image/png, image/jpeg"[/code]. Additionally, the [param mode] [constant FILE_DIALOG_MODE_OPEN_ANY] is not supported on Android. + [b]Note:[/b] Embedded file dialog and Windows file dialog support only file extensions, while Android, Linux, and macOS file dialogs also support MIME types. [b]Note:[/b] On Android and Linux, [param show_hidden] is ignored. [b]Note:[/b] On Android and macOS, native file dialogs have no title. [b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks. @@ -168,7 +168,7 @@ Displays OS native dialog for selecting files or directories in the file system with additional user selectable options. - Each filter string in the [param filters] array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. See also [member FileDialog.filters]. + Each filter string in the [param filters] array should be formatted like this: [code]*.png,*.jpg,*.jpeg;Image Files;image/png,image/jpeg[/code]. The description text of the filter is optional and can be omitted. It is recommended to set both file extension and MIME type. See also [member FileDialog.filters]. [param options] is array of [Dictionary]s with the following keys: - [code]"name"[/code] - option's name [String]. - [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used. @@ -176,6 +176,7 @@ Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code]. [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE_EXTRA] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS. [b]Note:[/b] [param current_directory] might be ignored. + [b]Note:[/b] Embedded file dialog and Windows file dialog support only file extensions, while Android, Linux, and macOS file dialogs also support MIME types. [b]Note:[/b] On Linux (X11), [param show_hidden] is ignored. [b]Note:[/b] On macOS, native file dialogs have no title. [b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks. @@ -1928,6 +1929,9 @@ Display server supports embedding a window from another process. [b]Windows, Linux (X11)[/b] + + Native file selection dialog supports MIME types as filters. + Makes the mouse cursor visible if it is hidden. @@ -2108,6 +2112,7 @@ [b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment. [b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen. [b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor. + [b]On Linux (Wayland):[/b] Equivalent to [constant WINDOW_MODE_FULLSCREEN]. [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 2a9e4088a6b5..cf8296841d44 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -109,6 +109,12 @@ Used by the inspector, set to [code]true[/code] when the property can be deleted by the user. + + Used by the inspector, set to [code]true[/code] when the property label is drawn. + + + Used by the inspector, set to [code]true[/code] when the property background is drawn. + Used by the inspector, set to [code]true[/code] when the property is drawn with the editor theme's warning color. This is used for editable children's properties. diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index e1c85d3595f5..7aad56f89c81 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -241,6 +241,9 @@ If [code]true[/code], new node created when reparenting node(s) will be positioned at the average position of the selected node(s). + + If [code]true[/code], the scene tree dock will only show nodes that match the filter, without showing parents that don't. This settings can also be changed in the Scene dock's top menu. + If [code]true[/code], the Create dialog (Create New Node/Create New Resource) will start with all its sections expanded. Otherwise, sections will be collapsed until the user starts searching (which will automatically expand sections as needed). @@ -432,6 +435,9 @@ The 3D editor gizmo color for [FogVolume] nodes. + + The 3D editor gizmo color for the [GridMap] grid. + The color override to use for 3D editor gizmos if the [Node3D] in question is part of an instantiated scene file (from the perspective of the current scene). diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 64369bec30ed..921fb0b71464 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -145,8 +145,8 @@ See also [member filters], which should be used to restrict the file types that can be selected instead of [member filename_filter] which is meant to be set by the user. - The available file type filters. Each filter string in the array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. - [b]Note:[/b] For android native dialog, MIME types are used like this: [code]image/*, application/pdf[/code]. + The available file type filters. Each filter string in the array should be formatted like this: [code]*.png,*.jpg,*.jpeg;Image Files;image/png,image/jpeg[/code]. The description text of the filter is optional and can be omitted. Both file extensions and MIME type should be always set. + [b]Note:[/b] Embedded file dialog and Windows file dialog support only file extensions, while Android, Linux, and macOS file dialogs also support MIME types. If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File"). diff --git a/doc/classes/LookAtModifier3D.xml b/doc/classes/LookAtModifier3D.xml index 2475de186882..3922c30e96ea 100644 --- a/doc/classes/LookAtModifier3D.xml +++ b/doc/classes/LookAtModifier3D.xml @@ -47,7 +47,7 @@ The ease type of the time-based interpolation. See also [enum Tween.EaseType]. - + The forward axis of the bone. This [SkeletonModifier3D] modifies the bone so that this axis points toward the [member target_node]. @@ -129,24 +129,6 @@ - - Enumerated value for the +X axis. - - - Enumerated value for the -X axis. - - - Enumerated value for the +Y axis. - - - Enumerated value for the -Y axis. - - - Enumerated value for the +Z axis. - - - Enumerated value for the -Z axis. - The bone rest position of the bone specified in [member bone] is used as origin. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index d0ec936be7ec..ec34d464d9bd 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -759,7 +759,7 @@ - Returns the object's metadata entry names as a [PackedStringArray]. + Returns the object's metadata entry names as an [Array] of [StringName]s. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 45ff4ff74f2a..60e31d3f54df 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2966,7 +2966,7 @@ [b]Note:[/b] This setting is implemented only on Windows. - If [code]true[/code], the forward renderer will fall back to OpenGL 3 if both Direct3D 12, Metal and Vulkan are not supported. + If [code]true[/code], the forward renderer will fall back to OpenGL 3 if Direct3D 12, Metal, and Vulkan are not supported. [b]Note:[/b] This setting is implemented only on Windows, Android, macOS, iOS, and Linux/X11. @@ -3024,6 +3024,12 @@ Sets the scaling 3D mode. Bilinear scaling renders at different resolution to either undersample or supersample the viewport. FidelityFX Super Resolution 1.0, abbreviated to FSR, is an upscaling technology that produces high quality images at fast framerates by using a spatially-aware upscaling algorithm. FSR is slightly more expensive than bilinear, but it produces significantly higher image quality. On particularly low-end GPUs, the added cost of FSR may not be worth it (compared to using bilinear scaling with a slightly higher resolution scale to match performance). [b]Note:[/b] FSR is only effective when using the Forward+ rendering method, not Mobile or Compatibility. If using an incompatible rendering method, FSR will fall back to bilinear scaling. + + iOS override for [member rendering/scaling_3d/mode]. This allows selecting the MetalFX spatial and MetalFX temporal scaling modes, which are exclusive to platforms where the Metal rendering driver is used. + + + macOS override for [member rendering/scaling_3d/mode]. This allows selecting the MetalFX spatial and MetalFX temporal scaling modes, which are exclusive to platforms where the Metal rendering driver is used. + Scales the 3D render buffer based on the viewport size uses an image filter specified in [member rendering/scaling_3d/mode] to scale the output image to the full viewport size. Values lower than [code]1.0[/code] can be used to speed up 3D rendering at the cost of quality (undersampling). Values greater than [code]1.0[/code] are only valid for bilinear mode and can be used to improve 3D rendering quality at a high performance cost (supersampling). See also [member rendering/anti_aliasing/quality/msaa_3d] for multi-sample antialiasing, which is significantly cheaper but only smooths the edges of polygons. diff --git a/doc/classes/Projection.xml b/doc/classes/Projection.xml index 091e0bf54f46..d32fdc9bb8f5 100644 --- a/doc/classes/Projection.xml +++ b/doc/classes/Projection.xml @@ -194,6 +194,7 @@ Returns the vertical field of view of the projection (in degrees) associated with the given horizontal field of view (in degrees) and aspect ratio. + [b]Note:[/b] Unlike most methods of [Projection], [param aspect] is expected to be 1 divided by the X:Y aspect ratio. diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index b7f95587cda6..29a2f4723f72 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -2529,6 +2529,14 @@ Maximum viewport height (in pixels). + + Returns the smallest value for [member ProjectSettings.rendering/scaling_3d/scale] when using the MetalFX temporal upscaler. + [b]Note:[/b] The returned value is multiplied by a factor of [code]1000000[/code] to preserve 6 digits of precision. It must be divided by [code]1000000.0[/code] to convert the value to a floating point number. + + + Returns the largest value for [member ProjectSettings.rendering/scaling_3d/scale] when using the MetalFX temporal upscaler. + [b]Note:[/b] The returned value is multiplied by a factor of [code]1000000[/code] to preserve 6 digits of precision. It must be divided by [code]1000000.0[/code] to convert the value to a floating point number. + Memory taken by textures. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 50d7b397b6d0..f05f431dcf36 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -4986,7 +4986,15 @@ Use AMD FidelityFX Super Resolution 2.2 upscaling for the viewport's 3D buffer. The amount of scaling can be set using [member Viewport.scaling_3d_scale]. Values less than [code]1.0[/code] will be result in the viewport being upscaled using FSR2. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] will use FSR2 at native resolution as a TAA solution. - + + Use MetalFX spatial upscaling for the viewport's 3D buffer. The amount of scaling can be set using [member Viewport.scaling_3d_scale]. Values less than [code]1.0[/code] will be result in the viewport being upscaled using MetalFX. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] disables scaling. + [b]Note:[/b] Only supported when the Metal rendering driver is in use, which limits this scaling mode to macOS and iOS. + + + Use MetalFX temporal upscaling for the viewport's 3D buffer. The amount of scaling can be set using [member Viewport.scaling_3d_scale]. Values less than [code]1.0[/code] will be result in the viewport being upscaled using MetalFX. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] will use MetalFX at native resolution as a TAA solution. + [b]Note:[/b] Only supported when the Metal rendering driver is in use, which limits this scaling mode to macOS and iOS. + + Represents the size of the [enum ViewportScaling3DMode] enum. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index af5dc9db3484..544f76d1c7eb 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -199,6 +199,12 @@ Returns the current selection first character index if a selection is active, [code]-1[/code] otherwise. Does not include BBCodes. + + + + Returns the current selection vertical line offset if a selection is active, [code]-1.0[/code] otherwise. + + diff --git a/doc/classes/SkeletonModifier3D.xml b/doc/classes/SkeletonModifier3D.xml index 7b1d70179ac4..7e9814b16f8f 100644 --- a/doc/classes/SkeletonModifier3D.xml +++ b/doc/classes/SkeletonModifier3D.xml @@ -1,7 +1,7 @@ - A Node that may modify Skeleton3D's bone. + A node that may modify Skeleton3D's bone. [SkeletonModifier3D] retrieves a target [Skeleton3D] by having a [Skeleton3D] parent. @@ -43,4 +43,24 @@ + + + Enumerated value for the +X axis. + + + Enumerated value for the -X axis. + + + Enumerated value for the +Y axis. + + + Enumerated value for the -Y axis. + + + Enumerated value for the +Z axis. + + + Enumerated value for the -Z axis. + + diff --git a/doc/classes/SubtweenTweener.xml b/doc/classes/SubtweenTweener.xml new file mode 100644 index 000000000000..c8999ac4f3d9 --- /dev/null +++ b/doc/classes/SubtweenTweener.xml @@ -0,0 +1,21 @@ + + + + Runs a [Tween] nested within another [Tween]. + + + [SubtweenTweener] is used to execute a [Tween] as one step in a sequence defined by another [Tween]. See [method Tween.tween_subtween] for more usage information. + [b]Note:[/b] [method Tween.tween_subtween] is the only correct way to create [SubtweenTweener]. Any [SubtweenTweener] created manually will not function correctly. + + + + + + + + + Sets the time in seconds after which the [SubtweenTweener] will start running the subtween. By default there's no delay. + + + + diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index 3b5b6f7844b8..45fb9f92a80a 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -473,6 +473,27 @@ [/codeblocks] + + + + + Creates and appends a [SubtweenTweener]. This method can be used to nest [param subtween] within this [Tween], allowing for the creation of more complex and composable sequences. + [codeblock] + # Subtween will rotate the object. + var subtween = create_tween() + subtween.tween_property(self, "rotation_degrees", 45.0, 1.0) + subtween.tween_property(self, "rotation_degrees", 0.0, 1.0) + + # Parent tween will execute the subtween as one of its steps. + var tween = create_tween() + tween.tween_property(self, "position:x", 500, 3.0) + tween.tween_subtween(subtween) + tween.tween_property(self, "position:x", 300, 2.0) + [/codeblock] + [b]Note:[/b] The methods [method pause], [method stop], and [method set_loops] can cause the parent [Tween] to get stuck on the subtween step; see the documentation for those methods for more information. + [b]Note:[/b] The pause and process modes set by [method set_pause_mode] and [method set_process_mode] on [param subtween] will be overridden by the parent [Tween]'s settings. + + diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 98457b5e90b3..e4fb3ee9abe8 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -503,7 +503,21 @@ Use AMD FidelityFX Super Resolution 2.2 upscaling for the viewport's 3D buffer. The amount of scaling can be set using [member Viewport.scaling_3d_scale]. Values less than [code]1.0[/code] will be result in the viewport being upscaled using FSR2. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] will use FSR2 at native resolution as a TAA solution. - + + Use the [url=https://developer.apple.com/documentation/metalfx/mtlfxspatialscaler#overview]MetalFX spatial upscaler[/url] for the viewport's 3D buffer. + The amount of scaling can be set using [member scaling_3d_scale]. + Values less than [code]1.0[/code] will be result in the viewport being upscaled using MetalFX. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] disables scaling. + More information: [url=https://developer.apple.com/documentation/metalfx]MetalFX[/url]. + [b]Note:[/b] Only supported when the Metal rendering driver is in use, which limits this scaling mode to macOS and iOS. + + + Use the [url=https://developer.apple.com/documentation/metalfx/mtlfxtemporalscaler#overview]MetalFX temporal upscaler[/url] for the viewport's 3D buffer. + The amount of scaling can be set using [member scaling_3d_scale]. To determine the minimum input scale, use the [method RenderingDevice.limit_get] method with [constant RenderingDevice.LIMIT_METALFX_TEMPORAL_SCALER_MIN_SCALE]. + Values less than [code]1.0[/code] will be result in the viewport being upscaled using MetalFX. Values greater than [code]1.0[/code] are not supported and bilinear downsampling will be used instead. A value of [code]1.0[/code] will use MetalFX at native resolution as a TAA solution. + More information: [url=https://developer.apple.com/documentation/metalfx]MetalFX[/url]. + [b]Note:[/b] Only supported when the Metal rendering driver is in use, which limits this scaling mode to macOS and iOS. + + Represents the size of the [enum Scaling3DMode] enum. diff --git a/drivers/metal/metal_device_properties.h b/drivers/metal/metal_device_properties.h index af7746b9ac54..3bb5d4965db2 100644 --- a/drivers/metal/metal_device_properties.h +++ b/drivers/metal/metal_device_properties.h @@ -84,6 +84,8 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) MetalFeatures { bool tessellationShader = false; /**< If true, tessellation shaders are supported. */ bool imageCubeArray = false; /**< If true, image cube arrays are supported. */ MTLArgumentBuffersTier argument_buffers_tier = MTLArgumentBuffersTier1; + bool metal_fx_spatial = false; /**< If true, Metal FX spatial functions are supported. */ + bool metal_fx_temporal = false; /**< If true, Metal FX temporal functions are supported. */ }; struct MetalLimits { @@ -115,6 +117,9 @@ struct MetalLimits { uint32_t maxVertexInputBindingStride; uint32_t maxDrawIndexedIndexValue; + double temporalScalerInputContentMinScale; + double temporalScalerInputContentMaxScale; + uint32_t minSubgroupSize; /**< The minimum number of threads in a SIMD-group. */ uint32_t maxSubgroupSize; /**< The maximum number of threads in a SIMD-group. */ BitField subgroupSupportedShaderStages; diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm index 2c3f99dd50c2..c2acddc68b08 100644 --- a/drivers/metal/metal_device_properties.mm +++ b/drivers/metal/metal_device_properties.mm @@ -51,6 +51,7 @@ #import "metal_device_properties.h" #import +#import #import #import @@ -100,6 +101,11 @@ features.simdReduction = [p_device supportsFamily:MTLGPUFamilyApple7]; features.argument_buffers_tier = p_device.argumentBuffersSupport; + if (@available(macOS 13.0, iOS 16.0, *)) { + features.metal_fx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:p_device]; + features.metal_fx_temporal = [MTLFXTemporalScalerDescriptor supportsDevice:p_device]; + } + MTLCompileOptions *opts = [MTLCompileOptions new]; features.mslVersionEnum = opts.languageVersion; // By default, Metal uses the most recent language version. @@ -285,6 +291,15 @@ #endif limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1; + + if (@available(macOS 14.0, iOS 17.0, *)) { + limits.temporalScalerInputContentMinScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMinScaleForDevice:p_device]; + limits.temporalScalerInputContentMaxScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMaxScaleForDevice:p_device]; + } else { + // Defaults taken from macOS 14+ + limits.temporalScalerInputContentMinScale = 1.0; + limits.temporalScalerInputContentMaxScale = 3.0; + } } MetalDeviceProperties::MetalDeviceProperties(id p_device) { diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 1d16ac964da3..a647a2223232 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -3982,6 +3982,10 @@ bool isArrayTexture(MTLTextureType p_type) { return (uint64_t)limits.subgroupSupportedShaderStages; case LIMIT_SUBGROUP_OPERATIONS: return (uint64_t)limits.subgroupSupportedOperations; + case LIMIT_METALFX_TEMPORAL_SCALER_MIN_SCALE: + return (uint64_t)((1.0 / limits.temporalScalerInputContentMaxScale) * 1000'000); + case LIMIT_METALFX_TEMPORAL_SCALER_MAX_SCALE: + return (uint64_t)((1.0 / limits.temporalScalerInputContentMinScale) * 1000'000); UNKNOWN(LIMIT_VRS_TEXEL_WIDTH); UNKNOWN(LIMIT_VRS_TEXEL_HEIGHT); UNKNOWN(LIMIT_VRS_MAX_FRAGMENT_WIDTH); @@ -4017,6 +4021,10 @@ bool isArrayTexture(MTLTextureType p_type) { return false; case SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS: return true; + case SUPPORTS_METALFX_SPATIAL: + return device_properties->features.metal_fx_spatial; + case SUPPORTS_METALFX_TEMPORAL: + return device_properties->features.metal_fx_temporal; default: return false; } diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index a9e4adf674c2..24c505aec4bc 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -32,6 +32,7 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" @@ -146,24 +147,50 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou /// |-E /// void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) { + set_hide_root(false); + updating_scene_tree = true; const String last_path = get_selected_path(); const String filter = SceneTreeDock::get_singleton()->get_filter(); + TreeItem *select_item = nullptr; + bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"); + bool should_scroll = scrolling_to_item || filter != last_filter; scrolling_to_item = false; TreeItem *scroll_item = nullptr; // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion. - List> parents; + List parents; for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) { TreeItem *parent = nullptr; + Pair move_from_to; if (parents.size()) { // Find last parent. - Pair &p = parents.front()->get(); - parent = p.first; - if (!(--p.second)) { // If no child left, remove it. + ParentItem &p = parents.front()->get(); + parent = p.tree_item; + if (!(--p.child_count)) { // If no child left, remove it. parents.pop_front(); + + if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) { + if (parent == get_root()) { + set_hide_root(true); + } else { + move_from_to.first = parent; + // Find the closest ancestor that matches the filter. + for (const ParentItem p2 : parents) { + move_from_to.second = p2.tree_item; + if (p2.matches_filter || move_from_to.second == get_root()) { + break; + } + } + + if (!move_from_to.second) { + move_from_to.second = get_root(); + } + } + } } } + // Add this node. TreeItem *item = create_item(parent); item->set_text(0, node.name); @@ -178,12 +205,17 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } item->set_metadata(0, node.id); - // Set current item as collapsed if necessary (root is never collapsed). + String current_path; if (parent) { + current_path += (String)parent->get_meta("node_path"); + + // Set current item as collapsed if necessary (root is never collapsed). if (!unfold_cache.has(node.id)) { item->set_collapsed(true); } } + item->set_meta("node_path", current_path + "/" + item->get_text(0)); + // Select previously selected node. if (debugger_id == p_debugger) { // Can use remote id. if (node.id == inspected_object_id) { @@ -196,21 +228,18 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int updating_scene_tree = true; } - item->select(0); - + select_item = item; if (should_scroll) { scroll_item = item; } } - } else { // Must use path - if (last_path == _get_path(item)) { - updating_scene_tree = false; // Force emission of new selection. - item->select(0); - if (should_scroll) { - scroll_item = item; - } - updating_scene_tree = true; + } else if (last_path == (String)item->get_meta("node_path")) { // Must use path. + updating_scene_tree = false; // Force emission of new selection. + select_item = item; + if (should_scroll) { + scroll_item = item; } + updating_scene_tree = true; } // Add buttons. @@ -242,7 +271,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int // Add in front of the parents stack if children are expected. if (node.child_count) { - parents.push_front(Pair(item, node.child_count)); + parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0)))); } else { // Apply filters. while (parent) { @@ -250,31 +279,60 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int if (filter.is_subsequence_ofn(item->get_text(0))) { break; // Filter matches, must survive. } + parent->remove_child(item); memdelete(item); - if (scroll_item == item) { + if (select_item == item || scroll_item == item) { + select_item = nullptr; scroll_item = nullptr; } + if (had_siblings) { break; // Parent must survive. } + item = parent; parent = item->get_parent(); // Check if parent expects more children. - for (const Pair &pair : parents) { - if (pair.first == item) { + for (ParentItem &pair : parents) { + if (pair.tree_item == item) { parent = nullptr; break; // Might have more children. } } } } + + // Move all children to the ancestor that matches the filter, if picked. + if (move_from_to.first) { + TreeItem *from = move_from_to.first; + TypedArray children = from->get_children(); + if (!children.is_empty()) { + for (Variant &c : children) { + TreeItem *ti = Object::cast_to(c); + from->remove_child(ti); + move_from_to.second->add_child(ti); + } + + from->get_parent()->remove_child(from); + memdelete(from); + if (select_item == from || scroll_item == from) { + select_item = nullptr; + scroll_item = nullptr; + } + } + } } debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree. + + if (select_item) { + select_item->select(0); + } if (scroll_item) { scroll_to_item(scroll_item, false); } + last_filter = filter; updating_scene_tree = false; } @@ -338,22 +396,7 @@ String EditorDebuggerTree::get_selected_path() { if (!get_selected()) { return ""; } - return _get_path(get_selected()); -} - -String EditorDebuggerTree::_get_path(TreeItem *p_item) { - ERR_FAIL_NULL_V(p_item, ""); - - if (p_item->get_parent() == nullptr) { - return "/root"; - } - String text = p_item->get_text(0); - TreeItem *cur = p_item->get_parent(); - while (cur) { - text = cur->get_text(0) + "/" + text; - cur = cur->get_parent(); - } - return "/" + text; + return get_selected()->get_meta("node_path"); } void EditorDebuggerTree::_item_menu_id_pressed(int p_option) { diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h index d048688cad24..46893d6dc3bd 100644 --- a/editor/debugger/editor_debugger_tree.h +++ b/editor/debugger/editor_debugger_tree.h @@ -40,6 +40,18 @@ class EditorDebuggerTree : public Tree { GDCLASS(EditorDebuggerTree, Tree); private: + struct ParentItem { + TreeItem *tree_item; + int child_count; + bool matches_filter; + + ParentItem(TreeItem *p_tree_item = nullptr, int p_child_count = 0, bool p_matches_filter = false) { + tree_item = p_tree_item; + child_count = p_child_count; + matches_filter = p_matches_filter; + } + }; + enum ItemMenu { ITEM_MENU_SAVE_REMOTE_NODE, ITEM_MENU_COPY_NODE_PATH, @@ -56,7 +68,6 @@ class EditorDebuggerTree : public Tree { EditorFileDialog *file_dialog = nullptr; String last_filter; - String _get_path(TreeItem *p_item); void _scene_tree_folded(Object *p_obj); void _scene_tree_selected(); void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button); diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 0a54392aea41..ae47eb075815 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -715,6 +715,15 @@ void EditorAutoloadSettings::drop_data_fw(const Point2 &p_point, const Variant & Dictionary drop_data = p_data; PackedStringArray autoloads = drop_data["autoloads"]; + // Store the initial order of the autoloads for comparison. + Vector initial_orders; + initial_orders.resize(autoload_cache.size()); + int idx = 0; + for (const AutoloadInfo &F : autoload_cache) { + initial_orders.write[idx++] = F.order; + } + + // Perform the drag-and-drop operation. Vector orders; orders.resize(autoload_cache.size()); @@ -734,10 +743,14 @@ void EditorAutoloadSettings::drop_data_fw(const Point2 &p_point, const Variant & } } - int i = 0; - + idx = 0; for (const AutoloadInfo &F : autoload_cache) { - orders.write[i++] = F.order; + orders.write[idx++] = F.order; + } + + // If the order didn't change, we shouldn't create undo/redo actions. + if (orders == initial_orders) { + return; } orders.sort(); @@ -746,10 +759,9 @@ void EditorAutoloadSettings::drop_data_fw(const Point2 &p_point, const Variant & undo_redo->create_action(TTR("Rearrange Autoloads")); - i = 0; - + idx = 0; for (const AutoloadInfo &F : autoload_cache) { - undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, orders[i++]); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, orders[idx++]); undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, F.order); } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 9b30e6790927..8ba4a994bbf3 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -3216,7 +3216,6 @@ void EditorFileSystem::reimport_files(const Vector &p_files) { } Error EditorFileSystem::reimport_append(const String &p_file, const HashMap &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { - ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process."); Vector reloads; reloads.append(p_file); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0d67ac3127ba..b54d06379720 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -37,6 +37,7 @@ #include "editor/editor_feature_profile.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" +#include "editor/editor_properties.h" #include "editor/editor_property_name_processor.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -70,6 +71,126 @@ bool EditorInspector::_property_path_matches(const String &p_property_path, cons return false; } +bool EditorInspector::_resource_properties_matches(const Ref &p_resource, const String &p_filter) { + String group; + String group_base; + String subgroup; + String subgroup_base; + + List plist; + p_resource->get_property_list(&plist, true); + + // Employ a lighter version of the update_tree() property listing to find a match. + for (PropertyInfo &p : plist) { + if (p.usage & PROPERTY_USAGE_SUBGROUP) { + subgroup = p.name; + subgroup_base = p.hint_string.get_slicec(',', 0); + + continue; + + } else if (p.usage & PROPERTY_USAGE_GROUP) { + group = p.name; + group_base = p.hint_string.get_slicec(',', 0); + subgroup = ""; + subgroup_base = ""; + + continue; + + } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + group = ""; + group_base = ""; + subgroup = ""; + subgroup_base = ""; + + continue; + + } else if (p.name.begins_with("metadata/_") || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || + (p_filter.is_empty() && restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + // Ignore properties that are not supposed to be in the inspector. + continue; + } + + if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) { + // Do not show this property in low end gfx. + continue; + } + + if (p.name == "script") { + // The script is always hidden in sub inspectors. + continue; + } + + if (p.name.begins_with("metadata/") && bool(object->call(SNAME("_hide_metadata_from_inspector")))) { + // Hide metadata from inspector if required. + continue; + } + + String path = p.name; + + // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string. + if (!subgroup.is_empty() && !subgroup_base.is_empty()) { + 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 { + 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.is_empty() && !group_base.is_empty() && subgroup.is_empty()) { + 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 = ""; + } + } + + // Add the group and subgroup to the path. + if (!subgroup.is_empty()) { + path = subgroup + "/" + path; + } + if (!group.is_empty()) { + path = group + "/" + path; + } + + // Get the property label's string. + String name_override = (path.contains_char('/')) ? path.substr(path.rfind_char('/') + 1) : path; + const int dot = name_override.find_char('.'); + if (dot != -1) { + name_override = name_override.substr(0, dot); + } + + // Remove the property from the path. + int idx = path.rfind_char('/'); + if (idx > -1) { + path = path.left(idx); + } else { + path = ""; + } + + // Check if the property matches the filter. + const String property_path = (path.is_empty() ? "" : path + "/") + name_override; + if (_property_path_matches(property_path, p_filter, property_name_style)) { + return true; + } + + // Check if the sub-resource has any properties that match the filter. + if (p.hint && p.hint == PROPERTY_HINT_RESOURCE_TYPE) { + Ref res = p_resource->get(p.name); + if (res.is_valid() && _resource_properties_matches(res, p_filter)) { + return true; + } + } + } + + return false; +} + String EditorProperty::get_tooltip_string(const String &p_string) const { // Trim to 100 characters to prevent the tooltip from being too long. constexpr int TOOLTIP_MAX_LENGTH = 100; @@ -165,6 +286,9 @@ void EditorProperty::_notification(int p_what) { if (no_children) { text_size = size.width; rect = Rect2(size.width - 1, 0, 1, height); + } else if (!draw_label) { + text_size = 0; + rect = Rect2(1, 0, size.width - 1, height); } else { text_size = MAX(0, size.width - (child_room + 4 * EDSCALE)); if (is_layout_rtl()) { @@ -269,10 +393,10 @@ void EditorProperty::_notification(int p_what) { } Ref bg_stylebox = get_theme_stylebox(SNAME("child_bg")); - if (draw_top_bg && right_child_rect != Rect2()) { + if (draw_top_bg && right_child_rect != Rect2() && draw_background) { draw_style_box(bg_stylebox, right_child_rect); } - if (bottom_child_rect != Rect2()) { + if (bottom_child_rect != Rect2() && draw_background) { draw_style_box(bg_stylebox, bottom_child_rect); } @@ -606,6 +730,25 @@ bool EditorProperty::use_keying_next() const { return false; } +void EditorProperty::set_draw_label(bool p_draw_label) { + draw_label = p_draw_label; + queue_redraw(); + queue_sort(); +} + +bool EditorProperty::is_draw_label() const { + return draw_label; +} + +void EditorProperty::set_draw_background(bool p_draw_background) { + draw_background = p_draw_background; + queue_redraw(); +} + +bool EditorProperty::is_draw_background() const { + return draw_background; +} + void EditorProperty::set_checkable(bool p_checkable) { checkable = p_checkable; queue_redraw(); @@ -1071,6 +1214,12 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only); ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only); + ClassDB::bind_method(D_METHOD("set_draw_label", "draw_label"), &EditorProperty::set_draw_label); + ClassDB::bind_method(D_METHOD("is_draw_label"), &EditorProperty::is_draw_label); + + ClassDB::bind_method(D_METHOD("set_draw_background", "draw_background"), &EditorProperty::set_draw_background); + ClassDB::bind_method(D_METHOD("is_draw_background"), &EditorProperty::is_draw_background); + ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable); ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable); @@ -1113,6 +1262,8 @@ void EditorProperty::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_label"), "set_draw_label", "is_draw_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_background"), "set_draw_background", "is_draw_background"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning"); @@ -2923,6 +3074,7 @@ void EditorInspector::update_tree() { HashMap>> favorites_to_add; Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)); + bool sub_inspectors_enabled = EDITOR_GET("interface/inspector/open_resources_in_current_inspector"); // Get the lists of editors to add the beginning. for (Ref &ped : valid_plugins) { @@ -3100,7 +3252,7 @@ void EditorInspector::update_tree() { continue; } - if (p.name.begins_with("metadata/") && bool(object->call("_hide_metadata_from_inspector"))) { + if (p.name.begins_with("metadata/") && bool(object->call(SNAME("_hide_metadata_from_inspector")))) { // Hide metadata from inspector if required. continue; } @@ -3207,10 +3359,25 @@ void EditorInspector::update_tree() { } // Ignore properties that do not fit the filter. + bool sub_inspector_use_filter = false; if (use_filter && !filter.is_empty()) { const String property_path = property_prefix + (path.is_empty() ? "" : path + "/") + name_override; if (!_property_path_matches(property_path, filter, property_name_style)) { - continue; + if (!sub_inspectors_enabled || p.hint != PROPERTY_HINT_RESOURCE_TYPE) { + continue; + } + + Ref res = object->get(p.name); + if (res.is_null()) { + continue; + } + + // Check if the sub-resource has any properties that match the filter. + if (!_resource_properties_matches(res, filter)) { + continue; + } + + sub_inspector_use_filter = true; } } @@ -3523,6 +3690,13 @@ void EditorInspector::update_tree() { } } + if (sub_inspector_use_filter) { + EditorPropertyResource *epr = Object::cast_to(ep); + if (epr) { + epr->set_use_filter(true); + } + } + Node *section_search = current_vbox->get_parent(); while (section_search) { EditorInspectorSection *section = Object::cast_to(section_search); @@ -3877,14 +4051,10 @@ void EditorInspector::set_use_filter(bool p_use) { void EditorInspector::register_text_enter(Node *p_line_edit) { search_box = Object::cast_to(p_line_edit); if (search_box) { - search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorInspector::_filter_changed)); + search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorInspector::update_tree).unbind(1)); } } -void EditorInspector::_filter_changed(const String &p_text) { - update_tree(); -} - void EditorInspector::set_use_folding(bool p_use_folding, bool p_update_tree) { use_folding = p_use_folding; diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index bc2c8112937c..5ae356320593 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -89,6 +89,8 @@ class EditorProperty : public Container { int property_usage; + bool draw_label = true; + bool draw_background = true; bool read_only = false; bool checkable = false; bool checked = false; @@ -170,6 +172,12 @@ class EditorProperty : public Container { void set_read_only(bool p_read_only); bool is_read_only() const; + void set_draw_label(bool p_draw_label); + bool is_draw_label() const; + + void set_draw_background(bool p_draw_background); + bool is_draw_background() const; + Object *get_edited_object(); StringName get_edited_property() const; inline Variant get_edited_property_value() const { @@ -496,6 +504,7 @@ class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); friend class EditorInspectorCategory; + friend class EditorPropertyResource; enum { MAX_PLUGINS = 1024 @@ -585,6 +594,7 @@ class EditorInspector : public ScrollContainer { void _property_checked(const String &p_path, bool p_checked); void _property_pinned(const String &p_path, bool p_pinned); bool _property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style); + bool _resource_properties_matches(const Ref &p_resource, const String &p_filter); void _resource_selected(const String &p_path, Ref p_resource); void _property_selected(const String &p_path, int p_focusable); @@ -604,7 +614,6 @@ class EditorInspector : public ScrollContainer { void _keying_changed(); - void _filter_changed(const String &p_text); void _parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref ped); void _vscroll_changed(double); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index cf4209e4763e..69b44a3e8f88 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3292,6 +3292,9 @@ void EditorPropertyResource::update_property() { sub_inspector->set_draw_focus_border(false); + sub_inspector->set_use_filter(use_filter); + sub_inspector->register_text_enter(parent_inspector->search_box); + sub_inspector->set_mouse_filter(MOUSE_FILTER_STOP); add_child(sub_inspector); set_bottom_editor(sub_inspector); @@ -3318,16 +3321,14 @@ void EditorPropertyResource::update_property() { _update_property_bg(); } - } else { - if (sub_inspector) { - set_bottom_editor(nullptr); - memdelete(sub_inspector); - sub_inspector = nullptr; + } else if (sub_inspector) { + set_bottom_editor(nullptr); + memdelete(sub_inspector); + sub_inspector = nullptr; - if (opened_editor) { - EditorNode::get_singleton()->hide_unused_editors(); - opened_editor = false; - } + if (opened_editor) { + EditorNode::get_singleton()->hide_unused_editors(); + opened_editor = false; } } } @@ -3357,6 +3358,13 @@ void EditorPropertyResource::set_use_sub_inspector(bool p_enable) { use_sub_inspector = p_enable; } +void EditorPropertyResource::set_use_filter(bool p_use) { + use_filter = p_use; + if (sub_inspector) { + update_property(); + } +} + void EditorPropertyResource::fold_resource() { bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); if (unfolded) { diff --git a/editor/editor_properties.h b/editor/editor_properties.h index ae9c45419525..bcdae5342d28 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -673,6 +673,7 @@ class EditorPropertyResource : public EditorProperty { bool use_sub_inspector = false; EditorInspector *sub_inspector = nullptr; bool opened_editor = false; + bool use_filter = false; void _resource_selected(const Ref &p_resource, bool p_inspect); void _resource_changed(const Ref &p_resource); @@ -701,6 +702,7 @@ class EditorPropertyResource : public EditorProperty { void expand_revertable() override; void set_use_sub_inspector(bool p_enable); + void set_use_filter(bool p_use); void fold_resource(); virtual bool is_colored(ColorationMode p_mode) override; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 6442a04f2a37..8efcc597dc4c 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -145,6 +145,16 @@ bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, return true; } + if (name == "new_item_key_name") { + r_ret = TTR("New Key:"); + return true; + } + + if (name == "new_item_value_name") { + r_ret = TTR("New Value:"); + return true; + } + if (name.begins_with("indices")) { int index = name.get_slicec('/', 1).to_int(); Variant key = dict.get_key_at_index(index); @@ -152,6 +162,13 @@ bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, return true; } + if (name.begins_with("keys")) { + int index = name.get_slicec('/', 1).to_int(); + Variant key = dict.get_key_at_index(index); + r_ret = key; + return true; + } + return false; } @@ -190,6 +207,17 @@ String EditorPropertyDictionaryObject::get_property_name_for_index(int p_index) } } +String EditorPropertyDictionaryObject::get_key_name_for_index(int p_index) { + switch (p_index) { + case NEW_KEY_INDEX: + return "new_item_key_name"; + case NEW_VALUE_INDEX: + return "new_item_value_name"; + default: + return "keys/" + itos(p_index); + } +} + String EditorPropertyDictionaryObject::get_label_for_index(int p_index) { switch (p_index) { case NEW_KEY_INDEX: @@ -930,7 +958,31 @@ void EditorPropertyDictionary::_add_key_value() { void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { HBoxContainer *hbox = memnew(HBoxContainer); + + EditorProperty *prop_key = nullptr; + if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + if (key_subtype == Variant::OBJECT) { + EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); + editor->setup("Object"); + prop_key = editor; + } else { + prop_key = EditorInspector::instantiate_property_editor(this, key_subtype, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE); + } + prop_key->set_read_only(true); + prop_key->set_selectable(false); + prop_key->set_focus_mode(Control::FOCUS_NONE); + prop_key->set_draw_background(false); + prop_key->set_use_folding(is_using_folding()); + prop_key->set_h_size_flags(SIZE_EXPAND_FILL); + prop_key->set_draw_label(false); + hbox->add_child(prop_key); + } + EditorProperty *prop = memnew(EditorPropertyNil); + prop->set_h_size_flags(SIZE_EXPAND_FILL); + if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + prop->set_draw_label(false); + } hbox->add_child(prop); bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX; @@ -958,6 +1010,7 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { Slot slot; slot.prop = prop; + slot.prop_key = prop_key; slot.object = object; slot.container = hbox; int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0); @@ -1170,6 +1223,9 @@ void EditorPropertyDictionary::update_property() { new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed)); new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected)); new_prop->set_h_size_flags(SIZE_EXPAND_FILL); + if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + new_prop->set_draw_label(false); + } new_prop->set_read_only(is_read_only()); slot.set_prop(new_prop); } else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { @@ -1187,6 +1243,9 @@ void EditorPropertyDictionary::update_property() { } slot.prop->update_property(); + if (slot.prop_key) { + slot.prop_key->update_property(); + } } updating = false; diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 53a8bcddb639..a823c56e4ed2 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -88,6 +88,7 @@ class EditorPropertyDictionaryObject : public RefCounted { String get_label_for_index(int p_index); String get_property_name_for_index(int p_index); + String get_key_name_for_index(int p_index); EditorPropertyDictionaryObject(); }; @@ -183,11 +184,14 @@ class EditorPropertyDictionary : public EditorProperty { Variant::Type type = Variant::VARIANT_MAX; bool as_id = false; EditorProperty *prop = nullptr; + EditorProperty *prop_key = nullptr; String prop_name; + String key_name; void set_index(int p_idx) { index = p_idx; prop_name = object->get_property_name_for_index(p_idx); + key_name = object->get_key_name_for_index(p_idx); update_prop_or_index(); } @@ -200,7 +204,11 @@ class EditorPropertyDictionary : public EditorProperty { void update_prop_or_index() { prop->set_object_and_property(object.ptr(), prop_name); - prop->set_label(object->get_label_for_index(index)); + if (prop_key) { + prop_key->set_object_and_property(object.ptr(), key_name); + } else { + prop->set_label(object->get_label_for_index(index)); + } } }; diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 3560174e6b51..a0baad7a1410 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -202,6 +202,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() { capitalize_string_remaps["gles2"] = "GLES2"; capitalize_string_remaps["gles3"] = "GLES3"; capitalize_string_remaps["gltf"] = "glTF"; + capitalize_string_remaps["gridmap"] = "GridMap"; capitalize_string_remaps["gpu"] = "GPU"; capitalize_string_remaps["gui"] = "GUI"; capitalize_string_remaps["guid"] = "GUID"; diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index a3712768adc8..f53f2744a225 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -522,6 +522,14 @@ void EditorResourcePreview::_bind_methods() { ADD_SIGNAL(MethodInfo("preview_invalidated", PropertyInfo(Variant::STRING, "path"))); } +void EditorResourcePreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_EXIT_TREE: { + stop(); + } break; + } +} + void EditorResourcePreview::check_for_invalidation(const String &p_path) { bool call_invalidated = false; { diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index 876b42b101d0..e901000d765c 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -123,6 +123,7 @@ class EditorResourcePreview : public Node { void _update_thumbnail_sizes(); protected: + void _notification(int p_what); static void _bind_methods(); public: diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 78dd3919147d..79454eab6f05 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -633,6 +633,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false); _initial_set("docks/scene_tree/auto_expand_to_selected", true); _initial_set("docks/scene_tree/center_node_on_reparent", false); + _initial_set("docks/scene_tree/hide_filtered_out_parents", true); // FileSystem EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16") @@ -793,6 +794,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/gridmap_grid", Color(0.8, 0.5, 0.1), "") _initial_set("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d_gizmos/gizmo_settings/bone_shape", 1, "Wire,Octahedron"); EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 6b6abd476205..2502a0e50a9b 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -367,7 +367,7 @@ Vector FileSystemDock::get_uncollapsed_paths() const { return uncollapsed_paths; } -void FileSystemDock::_update_tree(const Vector &p_uncollapsed_paths, bool p_uncollapse_root, bool p_select_in_favorites, bool p_unfold_path) { +void FileSystemDock::_update_tree(const Vector &p_uncollapsed_paths, bool p_uncollapse_root, bool p_scroll_to_selected) { // Recreate the tree. tree->clear(); tree_update_id++; @@ -436,10 +436,7 @@ void FileSystemDock::_update_tree(const Vector &p_uncollapsed_paths, boo ti->set_tooltip_text(0, favorite); ti->set_selectable(0, true); ti->set_metadata(0, favorite); - if (p_select_in_favorites && favorite == current_path) { - ti->select(0); - ti->set_as_cursor(0); - } + if (!favorite.ends_with("/")) { Array udata; udata.push_back(tree_update_id); @@ -454,12 +451,15 @@ void FileSystemDock::_update_tree(const Vector &p_uncollapsed_paths, boo } // Create the remaining of the tree. - _create_tree(root, EditorFileSystem::get_singleton()->get_filesystem(), uncollapsed_paths, p_select_in_favorites, p_unfold_path); + _create_tree(root, EditorFileSystem::get_singleton()->get_filesystem(), uncollapsed_paths, false); if (!searched_tokens.is_empty()) { _update_filtered_items(); } - tree->ensure_cursor_is_visible(); + if (p_scroll_to_selected) { + tree->ensure_cursor_is_visible(); + } + updating_tree = false; } @@ -1375,7 +1375,6 @@ void FileSystemDock::_update_history() { if (tree->is_visible()) { _update_tree(get_uncollapsed_paths()); tree->grab_focus(); - tree->ensure_cursor_is_visible(); } if (file_list_vb->is_visible()) { @@ -2717,7 +2716,7 @@ void FileSystemDock::fix_dependencies(const String &p_for_file) { void FileSystemDock::update_all() { if (tree->is_visible()) { - _update_tree(get_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths(), false, false); } if (file_list_vb->is_visible()) { diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index da47bc9abf8c..eba64a4fc61b 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -249,7 +249,7 @@ class FileSystemDock : public VBoxContainer { Ref _get_tree_item_icon(bool p_is_valid, const String &p_file_type, const String &p_icon_path); void _create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector &uncollapsed_paths, bool p_select_in_favorites, bool p_unfold_path = false); - void _update_tree(const Vector &p_uncollapsed_paths = Vector(), bool p_uncollapse_root = false, bool p_select_in_favorites = false, bool p_unfold_path = false); + void _update_tree(const Vector &p_uncollapsed_paths = Vector(), bool p_uncollapse_root = false, bool p_scroll_to_selected = true); void _navigate_to_path(const String &p_path, bool p_select_in_favorites = false); bool _update_filtered_items(TreeItem *p_tree_item = nullptr); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index daca6c6aae11..0f4d1161d745 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1218,50 +1218,83 @@ void EditorFileDialog::update_filters() { if (filters.size() > 1) { String all_filters; + String all_mime; String all_filters_full; + String all_mime_full; const int max_filters = 5; + // "All Recognized" display name. for (int i = 0; i < MIN(max_filters, filters.size()); i++) { String flt = filters[i].get_slicec(';', 0).strip_edges(); - if (i > 0) { + if (!all_filters.is_empty() && !flt.is_empty()) { all_filters += ", "; } all_filters += flt; + + String mime = filters[i].get_slicec(';', 2).strip_edges(); + if (!all_mime.is_empty() && !mime.is_empty()) { + all_mime += ", "; + } + all_mime += mime; } + + // "All Recognized" filter. for (int i = 0; i < filters.size(); i++) { String flt = filters[i].get_slicec(';', 0).strip_edges(); - if (i > 0) { + if (!all_filters_full.is_empty() && !flt.is_empty()) { all_filters_full += ","; } all_filters_full += flt; + + String mime = filters[i].get_slicec(';', 2).strip_edges(); + if (!all_mime_full.is_empty() && !mime.is_empty()) { + all_mime_full += ","; + } + all_mime_full += mime; + } + + String native_all_name; + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) { + native_all_name += all_filters; + } + if (!native_all_name.is_empty()) { + native_all_name += ", "; } + native_all_name += all_mime; if (max_filters < filters.size()) { all_filters += ", ..."; + native_all_name += ", ..."; } - String f = TTR("All Recognized") + " (" + all_filters + ")"; - filter->add_item(f); - processed_filters.push_back(all_filters_full + ";" + f); + filter->add_item(atr(ETR("All Recognized")) + " (" + all_filters + ")"); + processed_filters.push_back(all_filters_full + ";" + atr(ETR("All Recognized")) + " (" + native_all_name + ")" + ";" + all_mime_full); } for (int i = 0; i < filters.size(); i++) { String flt = filters[i].get_slicec(';', 0).strip_edges(); - String desc = filters[i].get_slice(";", 1).strip_edges(); - if (desc.length()) { - String f = desc + " (" + flt + ")"; - filter->add_item(f); - processed_filters.push_back(flt + ";" + f); + String desc = filters[i].get_slicec(';', 1).strip_edges(); + String mime = filters[i].get_slicec(';', 2).strip_edges(); + String native_name; + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_MIME)) { + native_name += flt; + } + if (!native_name.is_empty() && !mime.is_empty()) { + native_name += ", "; + } + native_name += mime; + if (!desc.is_empty()) { + filter->add_item(atr(desc) + " (" + flt + ")"); + processed_filters.push_back(flt + ";" + atr(desc) + " (" + native_name + ");" + mime); } else { - String f = "(" + flt + ")"; - filter->add_item(f); - processed_filters.push_back(flt + ";" + f); + filter->add_item("(" + flt + ")"); + processed_filters.push_back(flt + ";(" + native_name + ");" + mime); } } String f = TTR("All Files") + " (*.*)"; filter->add_item(f); - processed_filters.push_back("*.*;" + f); + processed_filters.push_back("*.*;" + f + ";application/octet-stream"); } void EditorFileDialog::clear_filters() { diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index fbe679cd15c8..575cce96f729 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -897,6 +897,15 @@ void SceneTreeEditor::_update_tree(bool p_scroll_to_selected) { return; } + Node *scene_node = get_scene_node(); + + if (node_cache.current_scene_node != scene_node) { + _reset(); + marked.clear(); + node_cache.current_scene_node = scene_node; + node_cache.force_update = true; + } + if (!update_when_invisible && !is_visible_in_tree()) { return; } @@ -908,13 +917,6 @@ void SceneTreeEditor::_update_tree(bool p_scroll_to_selected) { updating_tree = true; last_hash = hash_djb2_one_64(0); - Node *scene_node = get_scene_node(); - - if (node_cache.current_scene_node != scene_node) { - _reset(); - node_cache.current_scene_node = scene_node; - node_cache.force_update = true; - } if (node_cache.current_scene_node) { // Handle pinning/unpinning the animation player only do this once per iteration. @@ -957,47 +959,60 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select return false; } - bool keep_for_children = false; - for (TreeItem *child = p_parent->get_first_child(); child; child = child->get_next()) { - // Always keep if at least one of the children are kept. - keep_for_children = _update_filter(child, p_scroll_to_selected) || keep_for_children; - } - // Now find other reasons to keep this Node, too. PackedStringArray terms = filter.to_lower().split_spaces(); bool keep = _item_matches_all_terms(p_parent, terms); bool selectable = keep; - if (keep && !valid_types.is_empty()) { - selectable = false; + bool is_root = p_parent == tree->get_root(); + + if (keep) { Node *n = get_node(p_parent->get_metadata(0)); + if (!p_parent->is_visible() || (is_root && tree->is_root_hidden())) { + // Place back moved out children from when this item has hidden. + HashMap::Iterator I = node_cache.get(n, false); + if (I && I->value.has_moved_children) { + _update_node_subtree(I->value.node, nullptr, true); + } + } - for (const StringName &E : valid_types) { - if (n->is_class(E) || - EditorNode::get_singleton()->is_object_of_custom_type(n, E)) { - selectable = true; - break; - } else { - Ref