diff options
97 files changed, 2009 insertions, 1473 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 4a19d5bda0..0ac86bf97a 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -306,8 +306,8 @@ License: BSD-3-clause Files: ./thirdparty/libpng/ Comment: libpng -Copyright: 1995-2019, The PNG Reference Library Authors. - 2018-2019, Cosmin Truta. +Copyright: 1995-2024, The PNG Reference Library Authors. + 2018-2024, Cosmin Truta. 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. 1996-1997, Andreas Dilger. 1995-1996, Guy Eric Schalnat, Group 42, Inc. diff --git a/core/SCsub b/core/SCsub index 1f2166937a..7edf8ea88d 100644 --- a/core/SCsub +++ b/core/SCsub @@ -36,7 +36,7 @@ if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: Exit(255) # NOTE: It is safe to generate this file here, since this is still executed serially -with open("script_encryption_key.gen.cpp", "w") as f: +with open("script_encryption_key.gen.cpp", "w", encoding="utf-8", newline="\n") as f: f.write('#include "core/config/project_settings.h"\nuint8_t script_encryption_key[32]={' + txt + "};\n") diff --git a/core/core_builders.py b/core/core_builders.py index 61b7bd695c..b941d6a272 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -32,7 +32,7 @@ def make_certs_header(target, source, env): src = source[0] dst = target[0] f = open(src, "rb") - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") buf = f.read() decomp_size = len(buf) @@ -79,7 +79,7 @@ def make_authors_header(target, source, env): src = source[0] dst = target[0] f = open(src, "r", encoding="utf-8") - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef AUTHORS_GEN_H\n") @@ -141,7 +141,7 @@ def make_donors_header(target, source, env): src = source[0] dst = target[0] f = open(src, "r", encoding="utf-8") - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef DONORS_GEN_H\n") @@ -239,7 +239,7 @@ def make_license_header(target, source, env): part["copyright_index"] = len(data_list) data_list += part["Copyright"] - with open(dst, "w", encoding="utf-8") as f: + with open(dst, "w", encoding="utf-8", newline="\n") as f: f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") f.write("#ifndef LICENSE_GEN_H\n") f.write("#define LICENSE_GEN_H\n") diff --git a/core/extension/make_interface_dumper.py b/core/extension/make_interface_dumper.py index a85d62eff3..f5662bdbbb 100644 --- a/core/extension/make_interface_dumper.py +++ b/core/extension/make_interface_dumper.py @@ -5,7 +5,7 @@ def run(target, source, env): src = source[0] dst = target[0] f = open(src, "rb") - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") buf = f.read() decomp_size = len(buf) diff --git a/core/extension/make_wrappers.py b/core/extension/make_wrappers.py index 1e4634ad2c..e67ac8cfe7 100644 --- a/core/extension/make_wrappers.py +++ b/core/extension/make_wrappers.py @@ -142,7 +142,7 @@ def run(target, source, env): txt += "\n#endif\n" - with open(target[0], "w") as f: + with open(target[0], "w", encoding="utf-8", newline="\n") as f: f.write(txt) diff --git a/core/input/input_builders.py b/core/input/input_builders.py index 94c566493e..71238d6003 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -9,7 +9,7 @@ from collections import OrderedDict def make_default_controller_mappings(target, source, env): dst = target[0] - g = open(dst, "w") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write('#include "core/typedefs.h"\n') diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 6d32035d25..5edb045760 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -435,7 +435,7 @@ RID Resource::get_rid() const { #ifdef TOOLS_ENABLED -uint32_t Resource::hash_edited_version() const { +uint32_t Resource::hash_edited_version_for_preview() const { uint32_t hash = hash_murmur3_one_32(get_edited_version()); List<PropertyInfo> plist; @@ -445,7 +445,7 @@ uint32_t Resource::hash_edited_version() const { if (E.usage & PROPERTY_USAGE_STORAGE && E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) { Ref<Resource> res = get(E.name); if (res.is_valid()) { - hash = hash_murmur3_one_32(res->hash_edited_version(), hash); + hash = hash_murmur3_one_32(res->hash_edited_version_for_preview(), hash); } } } diff --git a/core/io/resource.h b/core/io/resource.h index f0f686af57..cc8a0d4387 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -125,7 +125,7 @@ public: #ifdef TOOLS_ENABLED - uint32_t hash_edited_version() const; + virtual uint32_t hash_edited_version_for_preview() const; virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; } uint64_t get_last_modified_time() const { return last_modified_time; } diff --git a/core/io/stream_peer_gzip.cpp b/core/io/stream_peer_gzip.cpp index 4daa71a22a..514bcf59b8 100644 --- a/core/io/stream_peer_gzip.cpp +++ b/core/io/stream_peer_gzip.cpp @@ -76,6 +76,7 @@ Error StreamPeerGZIP::start_decompression(bool p_is_deflate, int buffer_size) { Error StreamPeerGZIP::_start(bool p_compress, bool p_is_deflate, int buffer_size) { ERR_FAIL_COND_V(ctx != nullptr, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V_MSG(buffer_size <= 0, ERR_INVALID_PARAMETER, "Invalid buffer size. It should be a positive integer."); clear(); compressing = p_compress; rb.resize(nearest_shift(buffer_size - 1)); diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index ae70981f72..e2a8e656b7 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -201,7 +201,7 @@ def run(target, source, env): txt += "#endif // GDVIRTUAL_GEN_H\n" - with open(target[0], "w") as f: + with open(target[0], "w", encoding="utf-8", newline="\n") as f: f.write(txt) diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 40016f0904..fd5ba57615 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -218,6 +218,11 @@ <param index="1" name="before" type="bool" default="true" /> <description> Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array. + [codeblock] + var array = ["a", "b", "c", "c", "d", "e"] + print(array.bsearch("c", true)) # Prints 2, at the first matching element. + print(array.bsearch("c", false)) # Prints 4, after the last matching element, pointing to "d". + [/codeblock] [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> @@ -228,6 +233,7 @@ <param index="2" name="before" type="bool" default="true" /> <description> Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search and a custom comparison method. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array. The custom method receives two arguments (an element from the array and the value searched for) and must return [code]true[/code] if the first argument is less than the second, and return [code]false[/code] otherwise. + [b]Note:[/b] The custom method must accept the two arguments in any order, you cannot rely on that the first argument will always be from the array. [b]Note:[/b] Calling [method bsearch_custom] on an unsorted array results in unexpected behavior. </description> </method> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 923c22e871..1c69c48786 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -883,6 +883,9 @@ <member name="project_manager/default_renderer" type="String" setter="" getter=""> The renderer type that will be checked off by default when creating a new project. Accepted strings are "forward_plus", "mobile" or "gl_compatibility". </member> + <member name="project_manager/directory_naming_convention" type="int" setter="" getter=""> + Directory naming convention for the project manager. Options are "No convention" (project name is directory name), "kebab-case" (default), "snake_case", "camelCase", "PascalCase", or "Title Case". + </member> <member name="project_manager/sorting_order" type="int" setter="" getter=""> The sorting order to use in the project manager. When changing the sorting order in the project manager, this setting is set permanently in the editor settings. </member> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 009d7c03ea..d5016867a7 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -34,7 +34,7 @@ <return type="int" /> <description> Returns the sender's peer ID for the RPC currently being executed. - [b]Note:[/b] If not inside an RPC this method will return 0. + [b]Note:[/b] This method returns [code]0[/code] when called outside of an RPC. As such, the original peer ID may be lost when code execution is delayed (such as with GDScript's [code]await[/code] keyword). </description> </method> <method name="get_unique_id"> diff --git a/doc/classes/PhysicsBody2D.xml b/doc/classes/PhysicsBody2D.xml index eef159d44d..fa3bb941bf 100644 --- a/doc/classes/PhysicsBody2D.xml +++ b/doc/classes/PhysicsBody2D.xml @@ -50,7 +50,7 @@ Removes a body from the list of bodies that this body can't collide with. </description> </method> - <method name="test_move"> + <method name="test_move" keywords="check, collision, sweep"> <return type="bool" /> <param index="0" name="from" type="Transform2D" /> <param index="1" name="motion" type="Vector2" /> diff --git a/doc/classes/PhysicsBody3D.xml b/doc/classes/PhysicsBody3D.xml index 866b3e298c..5019da35c8 100644 --- a/doc/classes/PhysicsBody3D.xml +++ b/doc/classes/PhysicsBody3D.xml @@ -68,7 +68,7 @@ Locks or unlocks the specified linear or rotational [param axis] depending on the value of [param lock]. </description> </method> - <method name="test_move"> + <method name="test_move" keywords="check, collision, sweep"> <return type="bool" /> <param index="0" name="from" type="Transform3D" /> <param index="1" name="motion" type="Vector3" /> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 631bceec49..175caca598 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -122,17 +122,17 @@ [param mode] is the environment blend mode starting with the next frame. [b]Note:[/b] Not all runtimes support all environment blend modes, so it is important to check this at startup. For example: [codeblock] - func _ready(): - var xr_interface: XRInterface = XRServer.find_interface("OpenXR") - if xr_interface and xr_interface.is_initialized(): - var vp: Viewport = get_viewport() - vp.use_xr = true - var acceptable_modes = [ XRInterface.XR_ENV_BLEND_MODE_OPAQUE, XRInterface.XR_ENV_BLEND_MODE_ADDITIVE ] - var modes = xr_interface.get_supported_environment_blend_modes() - for mode in acceptable_modes: - if mode in modes: - xr_interface.set_environment_blend_mode(mode) - break + func _ready(): + var xr_interface: XRInterface = XRServer.find_interface("OpenXR") + if xr_interface and xr_interface.is_initialized(): + var vp: Viewport = get_viewport() + vp.use_xr = true + var acceptable_modes = [XRInterface.XR_ENV_BLEND_MODE_OPAQUE, XRInterface.XR_ENV_BLEND_MODE_ADDITIVE] + var modes = xr_interface.get_supported_environment_blend_modes() + for mode in acceptable_modes: + if mode in modes: + xr_interface.set_environment_blend_mode(mode) + break [/codeblock] </description> </method> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index f711038fdf..51d67d3456 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -437,7 +437,7 @@ class State: class TagState: - def __init__(self, raw: str, name: str, arguments: List[str], closing: bool) -> None: + def __init__(self, raw: str, name: str, arguments: str, closing: bool) -> None: self.raw = raw self.name = name @@ -891,9 +891,9 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: class_name = class_def.name if dry_run: - f = open(os.devnull, "w", encoding="utf-8") + f = open(os.devnull, "w", encoding="utf-8", newline="\n") else: - f = open(os.path.join(output_dir, f"class_{class_name.lower()}.rst"), "w", encoding="utf-8") + f = open(os.path.join(output_dir, f"class_{class_name.lower()}.rst"), "w", encoding="utf-8", newline="\n") # Remove the "Edit on Github" button from the online docs page. f.write(":github_url: hide\n\n") @@ -1691,9 +1691,9 @@ def make_link(url: str, title: str) -> str: def make_rst_index(grouped_classes: Dict[str, List[str]], dry_run: bool, output_dir: str) -> None: if dry_run: - f = open(os.devnull, "w", encoding="utf-8") + f = open(os.devnull, "w", encoding="utf-8", newline="\n") else: - f = open(os.path.join(output_dir, "index.rst"), "w", encoding="utf-8") + f = open(os.path.join(output_dir, "index.rst"), "w", encoding="utf-8", newline="\n") # Remove the "Edit on Github" button from the online docs page, and disallow user-contributed notes # on the index page. User-contributed notes are allowed on individual class pages. @@ -1762,7 +1762,7 @@ def is_in_tagset(tag_text: str, tagset: List[str]) -> bool: # Tag with arguments. if tag_text.startswith(tag + " "): return True - # Tag with arguments, special case for [url]. + # Tag with arguments, special case for [url], [color], and [font]. if tag_text.startswith(tag + "="): return True @@ -1771,17 +1771,22 @@ def is_in_tagset(tag_text: str, tagset: List[str]) -> bool: def get_tag_and_args(tag_text: str) -> TagState: tag_name = tag_text - arguments: List[str] = [] + arguments: str = "" + delim_pos = -1 + + space_pos = tag_text.find(" ") + if space_pos >= 0: + delim_pos = space_pos + + # Special case for [url], [color], and [font]. assign_pos = tag_text.find("=") - if assign_pos >= 0: - tag_name = tag_text[:assign_pos] - arguments = [tag_text[assign_pos + 1 :].strip()] - else: - space_pos = tag_text.find(" ") - if space_pos >= 0: - tag_name = tag_text[:space_pos] - arguments = [tag_text[space_pos + 1 :].strip()] + if assign_pos >= 0 and (delim_pos < 0 or assign_pos < delim_pos): + delim_pos = assign_pos + + if delim_pos >= 0: + tag_name = tag_text[:delim_pos] + arguments = tag_text[delim_pos + 1 :].strip() closing = False if tag_name.startswith("/"): @@ -1969,11 +1974,14 @@ def format_text_block( state, ) - tag_text = "\n::\n" + if "lang=text" in tag_state.arguments.split(" "): + tag_text = "\n.. code::\n" + else: + tag_text = "\n::\n" inside_code = True inside_code_tag = tag_state.name - ignore_code_warnings = "skip-lint" in tag_state.arguments + ignore_code_warnings = "skip-lint" in tag_state.arguments.split(" ") elif is_in_tagset(tag_state.name, ["code"]): tag_text = "``" @@ -1981,7 +1989,7 @@ def format_text_block( inside_code = True inside_code_tag = "code" - ignore_code_warnings = "skip-lint" in tag_state.arguments + ignore_code_warnings = "skip-lint" in tag_state.arguments.split(" ") escape_pre = True if not ignore_code_warnings: @@ -2078,7 +2086,7 @@ def format_text_block( # Cross-references to items in this or other class documentation pages. elif is_in_tagset(tag_state.name, RESERVED_CROSSLINK_TAGS): - link_target: str = tag_state.arguments[0] if len(tag_state.arguments) > 0 else "" + link_target: str = tag_state.arguments if link_target == "": print_error( @@ -2238,7 +2246,7 @@ def format_text_block( # Formatting directives. elif is_in_tagset(tag_state.name, ["url"]): - url_target = tag_state.arguments[0] if len(tag_state.arguments) > 0 else "" + url_target = tag_state.arguments if url_target == "": print_error( @@ -2439,7 +2447,7 @@ def format_codeblock( opening_formatted = tag_state.name if len(tag_state.arguments) > 0: - opening_formatted += " " + " ".join(tag_state.arguments) + opening_formatted += " " + tag_state.arguments code_text = post_text[len(f"[{opening_formatted}]") : end_pos] post_text = post_text[end_pos:] diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 64f2d1f203..8ea1f52d15 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -737,12 +737,17 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { ad->start_counting_ticks(); if (avail_frames > 0 && ad->audio_output.audio_client) { + UINT32 buffer_size; UINT32 cur_frames; bool invalidated = false; - HRESULT hr = ad->audio_output.audio_client->GetCurrentPadding(&cur_frames); + HRESULT hr = ad->audio_output.audio_client->GetBufferSize(&buffer_size); + if (hr != S_OK) { + ERR_PRINT("WASAPI: GetBufferSize error"); + } + hr = ad->audio_output.audio_client->GetCurrentPadding(&cur_frames); if (hr == S_OK) { // Check how much frames are available on the WASAPI buffer - UINT32 write_frames = MIN(ad->buffer_frames - cur_frames, avail_frames); + UINT32 write_frames = MIN(buffer_size - cur_frames, avail_frames); if (write_frames > 0) { BYTE *buffer = nullptr; hr = ad->audio_output.render_client->GetBuffer(write_frames, &buffer); diff --git a/editor/SCsub b/editor/SCsub index c88fd5e16a..67ded244cf 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -11,7 +11,7 @@ import editor_builders def _make_doc_data_class_path(to_path): # NOTE: It is safe to generate this file here, since this is still executed serially - g = open(os.path.join(to_path, "doc_data_class_path.gen.h"), "w", encoding="utf-8") + g = open(os.path.join(to_path, "doc_data_class_path.gen.h"), "w", encoding="utf-8", newline="\n") g.write("static const int _doc_data_class_path_count = " + str(len(env.doc_class_path)) + ";\n") g.write("struct _DocDataClassPath { const char* name; const char* path; };\n") @@ -41,7 +41,7 @@ if env.editor_build: reg_exporters += "}\n" # NOTE: It is safe to generate this file here, since this is still executed serially - with open("register_exporters.gen.cpp", "w", encoding="utf-8") as f: + with open("register_exporters.gen.cpp", "w", encoding="utf-8", newline="\n") as f: f.write(reg_exporters_inc) f.write(reg_exporters) diff --git a/editor/editor_builders.py b/editor/editor_builders.py index 98a64cfd23..7cac984129 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -16,7 +16,7 @@ from platform_methods import subprocess_main def make_doc_header(target, source, env): dst = target[0] - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") buf = "" docbegin = "" docend = "" @@ -53,7 +53,7 @@ def make_doc_header(target, source, env): def make_translations_header(target, source, env, category): dst = target[0] - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef _{}_TRANSLATIONS_H\n".format(category.upper())) diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 3e11b4a5ff..83e71292a3 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1333,24 +1333,28 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir, p_file->store_line("::" + p_dir->get_path() + "::" + String::num(p_dir->modified_time)); for (int i = 0; i < p_dir->files.size(); i++) { - if (!p_dir->files[i]->import_group_file.is_empty()) { - group_file_cache.insert(p_dir->files[i]->import_group_file); + const EditorFileSystemDirectory::FileInfo *file_info = p_dir->files[i]; + if (!file_info->import_group_file.is_empty()) { + group_file_cache.insert(file_info->import_group_file); } - String type = p_dir->files[i]->type; - if (p_dir->files[i]->resource_script_class) { - type += "/" + String(p_dir->files[i]->resource_script_class); - } - String s = p_dir->files[i]->file + "::" + type + "::" + itos(p_dir->files[i]->uid) + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid) + "::" + p_dir->files[i]->import_group_file + "::" + p_dir->files[i]->script_class_name + "<>" + p_dir->files[i]->script_class_extends + "<>" + p_dir->files[i]->script_class_icon_path; - s += "::"; - for (int j = 0; j < p_dir->files[i]->deps.size(); j++) { - if (j > 0) { - s += "<>"; - } - s += p_dir->files[i]->deps[j]; + String type = file_info->type; + if (file_info->resource_script_class) { + type += "/" + String(file_info->resource_script_class); } - p_file->store_line(s); + PackedStringArray cache_string; + cache_string.append(file_info->file); + cache_string.append(type); + cache_string.append(itos(file_info->uid)); + cache_string.append(itos(file_info->modified_time)); + cache_string.append(itos(file_info->import_modified_time)); + cache_string.append(itos(file_info->import_valid)); + cache_string.append(file_info->import_group_file); + cache_string.append(String("<>").join({ file_info->script_class_name, file_info->script_class_extends, file_info->script_class_icon_path })); + cache_string.append(String("<>").join(file_info->deps)); + + p_file->store_line(String("::").join(cache_string)); } for (int i = 0; i < p_dir->subdirs.size(); i++) { diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index f94b0dba05..022cf82426 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -47,6 +47,20 @@ #include "editor/themes/editor_scale.h" #include "scene/gui/line_edit.h" +#include "modules/modules_enabled.gen.h" // For gdscript, mono. + +// For syntax highlighting. +#ifdef MODULE_GDSCRIPT_ENABLED +#include "modules/gdscript/editor/gdscript_highlighter.h" +#include "modules/gdscript/gdscript.h" +#endif + +// For syntax highlighting. +#ifdef MODULE_MONO_ENABLED +#include "editor/plugins/script_editor_plugin.h" +#include "modules/mono/csharp_script.h" +#endif + #define CONTRIBUTE_URL vformat("%s/contributing/documentation/updating_the_class_reference.html", VERSION_DOCS_URL) #ifdef MODULE_MONO_ENABLED @@ -346,7 +360,7 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is link_t = link_t.trim_suffix("[]"); display_t = display_t.trim_suffix("[]"); - class_desc->push_meta("#Array"); // class + class_desc->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class class_desc->add_text("Array"); class_desc->pop(); // meta class_desc->add_text("["); @@ -360,14 +374,14 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is } if (is_enum_type) { - class_desc->push_meta("$" + link_t); // enum + class_desc->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum } else { - class_desc->push_meta("#" + link_t); // class + class_desc->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class } } class_desc->add_text(display_t); if (can_ref) { - class_desc->pop(); // Pushed meta above. + class_desc->pop(); // meta if (add_array) { class_desc->add_text("]"); } else if (is_bitfield) { @@ -489,7 +503,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview const bool is_documented = p_method.is_deprecated || p_method.is_experimental || !p_method.description.strip_edges().is_empty(); if (p_overview && is_documented) { - class_desc->push_meta("@method " + p_method.name); + class_desc->push_meta("@method " + p_method.name, RichTextLabel::META_UNDERLINE_ON_HOVER); } class_desc->push_color(theme_cache.headline_color); @@ -1196,7 +1210,7 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.headline_color); if (describe) { - class_desc->push_meta("@member " + prop.name); + class_desc->push_meta("@member " + prop.name, RichTextLabel::META_UNDERLINE_ON_HOVER); } class_desc->add_text(prop.name); @@ -2298,18 +2312,27 @@ void EditorHelp::_help_callback(const String &p_topic) { } } -static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class = "") { - DocTools *doc = EditorHelp::get_doc_data(); - String base_path; +static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class) { + const DocTools *doc = EditorHelp::get_doc_data(); - Ref<Font> doc_font = p_owner_node->get_theme_font(SNAME("doc"), EditorStringName(EditorFonts)); - Ref<Font> doc_bold_font = p_owner_node->get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts)); - Ref<Font> doc_italic_font = p_owner_node->get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts)); - Ref<Font> doc_code_font = p_owner_node->get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts)); - Ref<Font> doc_kbd_font = p_owner_node->get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts)); + bool is_native = false; + { + const HashMap<String, DocData::ClassDoc>::ConstIterator E = doc->class_list.find(p_class); + if (E && !E->value.is_script_doc) { + is_native = true; + } + } - int doc_code_font_size = p_owner_node->get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)); - int doc_kbd_font_size = p_owner_node->get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts)); + const bool using_tab_indent = int(EDITOR_GET("text_editor/behavior/indent/type")) == 0; + + const Ref<Font> doc_font = p_owner_node->get_theme_font(SNAME("doc"), EditorStringName(EditorFonts)); + const Ref<Font> doc_bold_font = p_owner_node->get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts)); + const Ref<Font> doc_italic_font = p_owner_node->get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts)); + const Ref<Font> doc_code_font = p_owner_node->get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts)); + const Ref<Font> doc_kbd_font = p_owner_node->get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts)); + + const int doc_code_font_size = p_owner_node->get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)); + const int doc_kbd_font_size = p_owner_node->get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts)); const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); const Color code_color = p_owner_node->get_theme_color(SNAME("code_color"), SNAME("EditorHelp")); @@ -2330,7 +2353,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control // Select the correct code examples. switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) { case 0: // GDScript - bbcode = bbcode.replace("[gdscript", "[codeblock"); // Tag can have extra arguments. + bbcode = bbcode.replace("[gdscript", "[codeblock lang=gdscript"); // Tag can have extra arguments. bbcode = bbcode.replace("[/gdscript]", "[/codeblock]"); for (int pos = bbcode.find("[csharp"); pos != -1; pos = bbcode.find("[csharp")) { @@ -2347,7 +2370,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } break; case 1: // C# - bbcode = bbcode.replace("[csharp", "[codeblock"); // Tag can have extra arguments. + bbcode = bbcode.replace("[csharp", "[codeblock lang=csharp"); // Tag can have extra arguments. bbcode = bbcode.replace("[/csharp]", "[/codeblock]"); for (int pos = bbcode.find("[gdscript"); pos != -1; pos = bbcode.find("[gdscript")) { @@ -2364,8 +2387,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } break; case 2: // GDScript and C# - bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock"); // Tag can have extra arguments. - bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock"); // Tag can have extra arguments. + bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock lang=csharp"); // Tag can have extra arguments. + bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock lang=gdscript"); // Tag can have extra arguments. bbcode = bbcode.replace("[/csharp]", "[/codeblock]"); bbcode = bbcode.replace("[/gdscript]", "[/codeblock]"); @@ -2378,17 +2401,11 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control bbcode = bbcode.replace("[codeblocks]", ""); bbcode = bbcode.replace("[/codeblocks]", ""); - // Remove extra new lines around code blocks. - bbcode = bbcode.replace("[codeblock]\n", "[codeblock]"); - bbcode = bbcode.replace("[codeblock skip-lint]\n", "[codeblock skip-lint]"); // Extra argument to silence validation warnings. - bbcode = bbcode.replace("\n[/codeblock]", "[/codeblock]"); + // Remove `\n` here because `\n` is replaced by `\n\n` later. + // Will be compensated when parsing `[/codeblock]`. bbcode = bbcode.replace("[/codeblock]\n", "[/codeblock]"); List<String> tag_stack; - bool code_tag = false; - bool codeblock_tag = false; - const bool using_tab_indent = int(EDITOR_GET("text_editor/behavior/indent/type")) == 0; - StringBuilder codeblock_text; int pos = 0; while (pos < bbcode.length()) { @@ -2399,33 +2416,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } if (brk_pos > pos) { - String text = bbcode.substr(pos, brk_pos - pos); - if (codeblock_tag && using_tab_indent) { - // Replace the code block's space indentation with tabs. - StringBuilder builder; - PackedStringArray text_lines = text.split("\n"); - for (const String &line : text_lines) { - String stripped_line = line.dedent(); - int space_count = line.length() - stripped_line.length(); - - if (builder.num_strings_appended() > 0) { - builder.append("\n"); - } - if (space_count > 0) { - builder.append(String("\t").repeat(MAX(space_count / 4, 1)) + stripped_line); - } else { - builder.append(line); - } - } - text = builder.as_string(); - } - if (!code_tag && !codeblock_tag) { - text = text.replace("\n", "\n\n"); - } - if (codeblock_tag) { - codeblock_text.append(text); - } - p_rt->add_text(text); + p_rt->add_text(bbcode.substr(pos, brk_pos - pos).replace("\n", "\n\n")); } if (brk_pos == bbcode.length()) { @@ -2435,16 +2426,11 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control int brk_end = bbcode.find_char(']', brk_pos + 1); if (brk_end == -1) { - String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos); - if (!code_tag && !codeblock_tag) { - text = text.replace("\n", "\n\n"); - } - p_rt->add_text(text); - + p_rt->add_text(bbcode.substr(brk_pos, bbcode.length() - brk_pos).replace("\n", "\n\n")); break; } - String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); + const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); if (tag.begins_with("/")) { bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length()); @@ -2458,64 +2444,32 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control tag_stack.pop_front(); pos = brk_end + 1; if (tag != "/img") { - if (code_tag) { - p_rt->pop(); // color - p_rt->pop(); // background color - p_rt->pop(); // font size - } else if (codeblock_tag) { - p_rt->pop(); // color - p_rt->pop(); // cell - - // Copy codeblock button. - p_rt->push_cell(); - p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99)); - p_rt->set_cell_padding(Rect2(0, 10 * EDSCALE, 0, 10 * EDSCALE)); - p_rt->set_cell_size_override(Vector2(1, 1), Vector2(10, 10) * EDSCALE); - p_rt->push_meta("^" + codeblock_text.as_string(), RichTextLabel::META_UNDERLINE_ON_HOVER); - codeblock_text = StringBuilder(); - p_rt->add_image(p_owner_node->get_editor_theme_icon(SNAME("ActionCopy")), 24 * EDSCALE, 24 * EDSCALE, Color(link_property_color, 0.3), INLINE_ALIGNMENT_BOTTOM_TO, Rect2(), Variant(), false, TTR("Click to copy.")); - p_rt->pop(); // meta - p_rt->pop(); // cell - - p_rt->pop(); // table - p_rt->pop(); // font size - - if (pos < bbcode.length()) { - p_rt->add_newline(); - } - } - p_rt->pop(); // Pops font for codetags & codeblocks, anything else for other tags. - } - code_tag = false; - codeblock_tag = false; - - } else if (code_tag || codeblock_tag) { - p_rt->add_text("["); - pos = brk_pos + 1; - if (codeblock_tag) { - codeblock_text.append("["); + p_rt->pop(); } } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) { const int tag_end = tag.find_char(' '); const String link_tag = tag.substr(0, tag_end); const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); - // Use monospace font to make clickable references - // easier to distinguish from inline code and other text. - p_rt->push_font(doc_code_font); - p_rt->push_font_size(doc_code_font_size); - Color target_color = link_color; + RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER; if (link_tag == "method" || link_tag == "constructor" || link_tag == "operator") { target_color = link_method_color; } else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme_item") { target_color = link_property_color; } else if (link_tag == "annotation") { target_color = link_annotation_color; + } else { + // Better visibility for constants, enums, etc. + underline_mode = RichTextLabel::META_UNDERLINE_ALWAYS; } + // Use monospace font to make clickable references + // easier to distinguish from inline code and other text. + p_rt->push_font(doc_code_font); + p_rt->push_font_size(doc_code_font_size); p_rt->push_color(target_color); - p_rt->push_meta("@" + link_tag + " " + link_target); + p_rt->push_meta("@" + link_tag + " " + link_target, underline_mode); if (link_tag == "member" && ((!link_target.contains(".") && (p_class == "ProjectSettings" || p_class == "EditorSettings")) || @@ -2541,11 +2495,10 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control p_rt->pop(); // meta p_rt->pop(); // color - - p_rt->pop(); // font size + p_rt->pop(); // font_size p_rt->pop(); // font - pos = brk_end + 1; + pos = brk_end + 1; } else if (tag.begins_with("param ")) { const int tag_end = tag.find_char(' '); const String param_name = tag.substr(tag_end + 1, tag.length()).lstrip(" "); @@ -2553,41 +2506,40 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control // Use monospace font with translucent background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); p_rt->push_font_size(doc_code_font_size); - p_rt->push_bgcolor(param_bg_color); p_rt->push_color(code_color); + p_rt->add_text(param_name); - p_rt->pop(); - p_rt->pop(); - p_rt->pop(); // font size + p_rt->pop(); // color + p_rt->pop(); // bgcolor + p_rt->pop(); // font_size p_rt->pop(); // font - pos = brk_end + 1; + pos = brk_end + 1; } else if (tag == p_class) { // Use a bold font when class reference tags are in their own page. p_rt->push_font(doc_bold_font); p_rt->add_text(tag); - p_rt->pop(); + p_rt->pop(); // font pos = brk_end + 1; - } else if (doc->class_list.has(tag)) { // Use a monospace font for class reference tags such as [Node2D] or [SceneTree]. p_rt->push_font(doc_code_font); p_rt->push_font_size(doc_code_font_size); p_rt->push_color(type_color); - p_rt->push_meta("#" + tag); + p_rt->push_meta("#" + tag, RichTextLabel::META_UNDERLINE_ON_HOVER); + p_rt->add_text(tag); - p_rt->pop(); - p_rt->pop(); - p_rt->pop(); // Font size - p_rt->pop(); // Font + p_rt->pop(); // meta + p_rt->pop(); // color + p_rt->pop(); // font_size + p_rt->pop(); // font pos = brk_end + 1; - } else if (tag == "b") { // Use bold font. p_rt->push_font(doc_bold_font); @@ -2601,42 +2553,139 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code" || tag.begins_with("code ")) { + int end_pos = bbcode.find("[/code]", brk_end + 1); + if (end_pos < 0) { + end_pos = bbcode.length(); + } + // Use monospace font with darkened background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); p_rt->push_font_size(doc_code_font_size); p_rt->push_bgcolor(code_bg_color); p_rt->push_color(code_color.lerp(p_owner_node->get_theme_color(SNAME("error_color"), EditorStringName(Editor)), 0.6)); - code_tag = true; - pos = brk_end + 1; - tag_stack.push_front("code"); + p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1))); + + p_rt->pop(); // color + p_rt->pop(); // bgcolor + p_rt->pop(); // font_size + p_rt->pop(); // font + + pos = end_pos + 7; // `len("[/code]")`. } else if (tag == "codeblock" || tag.begins_with("codeblock ")) { + int end_pos = bbcode.find("[/codeblock]", brk_end + 1); + if (end_pos < 0) { + end_pos = bbcode.length(); + } + + const String codeblock_text = bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)).strip_edges(); + + String codeblock_copy_text = codeblock_text; + if (using_tab_indent) { + // Replace the code block's space indentation with tabs. + StringBuilder builder; + PackedStringArray text_lines = codeblock_copy_text.split("\n"); + for (const String &line : text_lines) { + const String stripped_line = line.dedent(); + const int space_count = line.length() - stripped_line.length(); + + if (builder.num_strings_appended() > 0) { + builder.append("\n"); + } + if (space_count > 0) { + builder.append(String("\t").repeat(MAX(space_count / 4, 1)) + stripped_line); + } else { + builder.append(line); + } + } + codeblock_copy_text = builder.as_string(); + } + + String lang; + const PackedStringArray args = tag.trim_prefix("codeblock").split(" ", false); + for (int i = args.size() - 1; i >= 0; i--) { + if (args[i].begins_with("lang=")) { + lang = args[i].trim_prefix("lang="); + break; + } + } + // Use monospace font with darkened background color to make code easier to distinguish from other text. // Use a single-column table with cell row background color instead of `[bgcolor]`. // This makes the background color highlight cover the entire block, rather than individual lines. p_rt->push_font(doc_code_font); p_rt->push_font_size(doc_code_font_size); - p_rt->push_table(2); + p_rt->push_cell(); p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99)); p_rt->set_cell_padding(Rect2(10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE)); p_rt->push_color(code_dark_color); - codeblock_tag = true; - pos = brk_end + 1; - tag_stack.push_front("codeblock"); + bool codeblock_printed = false; + +#ifdef MODULE_GDSCRIPT_ENABLED + if (!codeblock_printed && (lang.is_empty() || lang == "gdscript")) { + EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_GDSCRIPT, codeblock_text, is_native); + codeblock_printed = true; + } +#endif + +#ifdef MODULE_MONO_ENABLED + if (!codeblock_printed && lang == "csharp") { + EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_CSHARP, codeblock_text, is_native); + codeblock_printed = true; + } +#endif + + if (!codeblock_printed) { + p_rt->add_text(codeblock_text); + codeblock_printed = true; + } + + p_rt->pop(); // color + p_rt->pop(); // cell + + // Copy codeblock button. + p_rt->push_cell(); + p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99)); + p_rt->set_cell_padding(Rect2(0, 10 * EDSCALE, 0, 10 * EDSCALE)); + p_rt->set_cell_size_override(Vector2(1, 1), Vector2(10, 10) * EDSCALE); + p_rt->push_meta("^" + codeblock_copy_text, RichTextLabel::META_UNDERLINE_ON_HOVER); + p_rt->add_image(p_owner_node->get_editor_theme_icon(SNAME("ActionCopy")), 24 * EDSCALE, 24 * EDSCALE, Color(link_property_color, 0.3), INLINE_ALIGNMENT_BOTTOM_TO, Rect2(), Variant(), false, TTR("Click to copy.")); + p_rt->pop(); // meta + p_rt->pop(); // cell + + p_rt->pop(); // table + p_rt->pop(); // font_size + p_rt->pop(); // font + + pos = end_pos + 12; // `len("[/codeblock]")`. + + // Compensate for `\n` removed before the loop. + if (pos < bbcode.length()) { + p_rt->add_newline(); + } } else if (tag == "kbd") { + int end_pos = bbcode.find("[/kbd]", brk_end + 1); + if (end_pos < 0) { + end_pos = bbcode.length(); + } + // Use keyboard font with custom color and background color. p_rt->push_font(doc_kbd_font); p_rt->push_font_size(doc_kbd_font_size); p_rt->push_bgcolor(kbd_bg_color); p_rt->push_color(kbd_color); - code_tag = true; // Though not strictly a code tag, logic is similar. - pos = brk_end + 1; - tag_stack.push_front(tag); + p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1))); + p_rt->pop(); // color + p_rt->pop(); // bgcolor + p_rt->pop(); // font_size + p_rt->pop(); // font + + pos = end_pos + 6; // `len("[/kbd]")`. } else if (tag == "center") { // Align to center. p_rt->push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, Control::TEXT_DIRECTION_AUTO, ""); @@ -2712,9 +2761,9 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control if (end == -1) { end = bbcode.length(); } - String image = bbcode.substr(brk_end + 1, end - brk_end - 1); - p_rt->add_image(ResourceLoader::load(base_path.path_join(image), "Texture2D"), width, height, Color(1, 1, 1), INLINE_ALIGNMENT_CENTER, Rect2(), Variant(), false, String(), size_in_percent); + String image_path = bbcode.substr(brk_end + 1, end - brk_end - 1); + p_rt->add_image(ResourceLoader::load(image_path, "Texture2D"), width, height, Color(1, 1, 1), INLINE_ALIGNMENT_CENTER, Rect2(), Variant(), false, String(), size_in_percent); pos = end; tag_stack.push_front("img"); @@ -2725,11 +2774,9 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front("color"); - } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()); - - Ref<Font> font = ResourceLoader::load(base_path.path_join(fnt), "Font"); + String font_path = tag.substr(5, tag.length()); + Ref<Font> font = ResourceLoader::load(font_path, "Font"); if (font.is_valid()) { p_rt->push_font(font); } else { @@ -2738,12 +2785,18 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front("font"); - } else { - p_rt->add_text("["); // ignore + p_rt->add_text("["); // Ignore. pos = brk_pos + 1; } } + + // Close unclosed tags. + for (const String &tag : tag_stack) { + if (tag != "img") { + p_rt->pop(); + } + } } void EditorHelp::_add_text(const String &p_bbcode) { @@ -2882,7 +2935,16 @@ void EditorHelp::_notification(int p_what) { } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - if (!EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help")) { + bool need_update = false; + if (EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help")) { + need_update = true; + } +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + if (!need_update && EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme/highlighting")) { + need_update = true; + } +#endif + if (!need_update) { break; } [[fallthrough]]; @@ -3076,10 +3138,11 @@ String EditorHelpBit::get_class_description(const StringName &p_class_name) cons } String description; - HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + + const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { // Non-native class shouldn't be cached, nor translated. - bool is_native = ClassDB::class_exists(p_class_name); + const bool is_native = !E->value.is_script_doc; description = is_native ? DTR(E->value.brief_description) : E->value.brief_description; if (is_native) { @@ -3100,11 +3163,13 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c } String description; - // Non-native properties shouldn't be cached, nor translated. - bool is_native = ClassDB::class_exists(p_class_name); - DocTools *dd = EditorHelp::get_doc_data(); - HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name); + + const DocTools *dd = EditorHelp::get_doc_data(); + const HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name); if (E) { + // Non-native properties shouldn't be cached, nor translated. + const bool is_native = !E->value.is_script_doc; + for (const DocData::PropertyDoc &property : E->value.properties) { String description_current = is_native ? DTR(property.description) : property.description; @@ -3112,7 +3177,7 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c const String enum_name = class_enum.size() >= 2 ? class_enum[1] : ""; if (!enum_name.is_empty()) { // Classes can use enums from other classes, so check from which it came. - HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_enum[0]); + const HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_enum[0]); if (enum_class) { for (DocData::ConstantDoc val : enum_class->value.constants) { // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector. @@ -3151,10 +3216,11 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con } String description; - HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + + const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { // Non-native methods shouldn't be cached, nor translated. - bool is_native = ClassDB::class_exists(p_class_name); + const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &method : E->value.methods) { String description_current = is_native ? DTR(method.description) : method.description; @@ -3182,10 +3248,11 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con } String description; - HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + + const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { // Non-native signals shouldn't be cached, nor translated. - bool is_native = ClassDB::class_exists(p_class_name); + const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &signal : E->value.signals) { String description_current = is_native ? DTR(signal.description) : signal.description; @@ -3213,12 +3280,13 @@ String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, } String description; + bool found = false; - DocTools *dd = EditorHelp::get_doc_data(); + const DocTools *dd = EditorHelp::get_doc_data(); HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name); while (E) { // Non-native theme items shouldn't be cached, nor translated. - bool is_native = ClassDB::class_exists(p_class_name); + const bool is_native = !E->value.is_script_doc; for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) { String description_current = is_native ? DTR(theme_item.description) : theme_item.description; @@ -3257,7 +3325,7 @@ void EditorHelpBit::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); rich_text->clear(); - _add_text_to_rt(text, rich_text, this); + _add_text_to_rt(text, rich_text, this, doc_class_name); rich_text->reset_size(); // Force recalculating size after parsing bbcode. } break; } @@ -3266,7 +3334,7 @@ void EditorHelpBit::_notification(int p_what) { void EditorHelpBit::set_text(const String &p_text) { text = p_text; rich_text->clear(); - _add_text_to_rt(text, rich_text, this); + _add_text_to_rt(text, rich_text, this, doc_class_name); } EditorHelpBit::EditorHelpBit() { @@ -3304,6 +3372,8 @@ void EditorHelpTooltip::parse_tooltip(const String &p_text) { const String &property_name = slices[2]; const String &property_args = slices[3]; + doc_class_name = class_name; + String formatted_text; // Exclude internal properties, they are not documented. @@ -3357,6 +3427,170 @@ EditorHelpTooltip::EditorHelpTooltip(const String &p_text, const String &p_custo get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); } +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) +/// EditorHelpHighlighter /// + +EditorHelpHighlighter *EditorHelpHighlighter::singleton = nullptr; + +void EditorHelpHighlighter::create_singleton() { + ERR_FAIL_COND(singleton != nullptr); + singleton = memnew(EditorHelpHighlighter); +} + +void EditorHelpHighlighter::free_singleton() { + ERR_FAIL_NULL(singleton); + memdelete(singleton); + singleton = nullptr; +} + +EditorHelpHighlighter *EditorHelpHighlighter::get_singleton() { + return singleton; +} + +EditorHelpHighlighter::HighlightData EditorHelpHighlighter::_get_highlight_data(Language p_language, const String &p_source, bool p_use_cache) { + switch (p_language) { + case LANGUAGE_GDSCRIPT: +#ifndef MODULE_GDSCRIPT_ENABLED + ERR_FAIL_V_MSG(HighlightData(), "GDScript module is disabled."); +#endif + break; + case LANGUAGE_CSHARP: +#ifndef MODULE_MONO_ENABLED + ERR_FAIL_V_MSG(HighlightData(), "Mono module is disabled."); +#endif + break; + default: + ERR_FAIL_V_MSG(HighlightData(), "Invalid parameter \"p_language\"."); + } + + if (p_use_cache) { + const HashMap<String, HighlightData>::ConstIterator E = highlight_data_caches[p_language].find(p_source); + if (E) { + return E->value; + } + } + + text_edits[p_language]->set_text(p_source); + scripts[p_language]->set_source_code(p_source); + highlighters[p_language]->_update_cache(); + + HighlightData result; + + int source_offset = 0; + int result_index = 0; + for (int i = 0; i < text_edits[p_language]->get_line_count(); i++) { + const Dictionary dict = highlighters[p_language]->_get_line_syntax_highlighting_impl(i); + + result.resize(result.size() + dict.size()); + + const Variant *key = nullptr; + int prev_column = -1; + while ((key = dict.next(key)) != nullptr) { + const int column = *key; + ERR_FAIL_COND_V(column <= prev_column, HighlightData()); + prev_column = column; + + const Color color = dict[*key].operator Dictionary().get("color", Color()); + + result.write[result_index] = { source_offset + column, color }; + result_index++; + } + + source_offset += text_edits[p_language]->get_line(i).length() + 1; // Plus newline. + } + + if (p_use_cache) { + highlight_data_caches[p_language][p_source] = result; + } + + return result; +} + +void EditorHelpHighlighter::highlight(RichTextLabel *p_rich_text_label, Language p_language, const String &p_source, bool p_use_cache) { + ERR_FAIL_NULL(p_rich_text_label); + + const HighlightData highlight_data = _get_highlight_data(p_language, p_source, p_use_cache); + + if (!highlight_data.is_empty()) { + for (int i = 1; i < highlight_data.size(); i++) { + const Pair<int, Color> &prev = highlight_data[i - 1]; + const Pair<int, Color> &curr = highlight_data[i]; + p_rich_text_label->push_color(prev.second); + p_rich_text_label->add_text(p_source.substr(prev.first, curr.first - prev.first)); + p_rich_text_label->pop(); // color + } + + const Pair<int, Color> &last = highlight_data[highlight_data.size() - 1]; + p_rich_text_label->push_color(last.second); + p_rich_text_label->add_text(p_source.substr(last.first)); + p_rich_text_label->pop(); // color + } +} + +void EditorHelpHighlighter::reset_cache() { + const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + +#ifdef MODULE_GDSCRIPT_ENABLED + highlight_data_caches[LANGUAGE_GDSCRIPT].clear(); + text_edits[LANGUAGE_GDSCRIPT]->add_theme_color_override("font_color", text_color); +#endif + +#ifdef MODULE_MONO_ENABLED + highlight_data_caches[LANGUAGE_CSHARP].clear(); + text_edits[LANGUAGE_CSHARP]->add_theme_color_override("font_color", text_color); +#endif +} + +EditorHelpHighlighter::EditorHelpHighlighter() { +#ifdef MODULE_GDSCRIPT_ENABLED + TextEdit *gdscript_text_edit = memnew(TextEdit); + + Ref<GDScript> gdscript; + gdscript.instantiate(); + + Ref<GDScriptSyntaxHighlighter> gdscript_highlighter; + gdscript_highlighter.instantiate(); + gdscript_highlighter->set_text_edit(gdscript_text_edit); + gdscript_highlighter->_set_edited_resource(gdscript); + + text_edits[LANGUAGE_GDSCRIPT] = gdscript_text_edit; + scripts[LANGUAGE_GDSCRIPT] = gdscript; + highlighters[LANGUAGE_GDSCRIPT] = gdscript_highlighter; +#endif + +#ifdef MODULE_MONO_ENABLED + TextEdit *csharp_text_edit = memnew(TextEdit); + + Ref<CSharpScript> csharp; + csharp.instantiate(); + + Ref<EditorStandardSyntaxHighlighter> csharp_highlighter; + csharp_highlighter.instantiate(); + csharp_highlighter->set_text_edit(csharp_text_edit); + csharp_highlighter->_set_edited_resource(csharp); + + text_edits[LANGUAGE_CSHARP] = csharp_text_edit; + scripts[LANGUAGE_CSHARP] = csharp; + highlighters[LANGUAGE_CSHARP] = csharp_highlighter; +#endif +} + +EditorHelpHighlighter::~EditorHelpHighlighter() { +#ifdef MODULE_GDSCRIPT_ENABLED + memdelete(text_edits[LANGUAGE_GDSCRIPT]); + scripts[LANGUAGE_GDSCRIPT].unref(); + highlighters[LANGUAGE_GDSCRIPT].unref(); +#endif + +#ifdef MODULE_MONO_ENABLED + memdelete(text_edits[LANGUAGE_CSHARP]); + scripts[LANGUAGE_CSHARP].unref(); + highlighters[LANGUAGE_CSHARP].unref(); +#endif +} + +#endif // defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + /// FindBar /// FindBar::FindBar() { diff --git a/editor/editor_help.h b/editor/editor_help.h index 5018f6570d..f8686b964a 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -44,6 +44,8 @@ #include "scene/gui/text_edit.h" #include "scene/main/timer.h" +#include "modules/modules_enabled.gen.h" // For gdscript, mono. + class FindBar : public HBoxContainer { GDCLASS(FindBar, HBoxContainer); @@ -265,6 +267,7 @@ class EditorHelpBit : public MarginContainer { String text; protected: + String doc_class_name; String custom_description; static void _bind_methods(); @@ -297,4 +300,41 @@ public: EditorHelpTooltip(const String &p_text = String(), const String &p_custom_description = String()); }; +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) +class EditorSyntaxHighlighter; + +class EditorHelpHighlighter { +public: + enum Language { + LANGUAGE_GDSCRIPT, + LANGUAGE_CSHARP, + LANGUAGE_MAX, + }; + +private: + using HighlightData = Vector<Pair<int, Color>>; + + static EditorHelpHighlighter *singleton; + + HashMap<String, HighlightData> highlight_data_caches[LANGUAGE_MAX]; + + TextEdit *text_edits[LANGUAGE_MAX]; + Ref<Script> scripts[LANGUAGE_MAX]; + Ref<EditorSyntaxHighlighter> highlighters[LANGUAGE_MAX]; + + HighlightData _get_highlight_data(Language p_language, const String &p_source, bool p_use_cache); + +public: + static void create_singleton(); + static void free_singleton(); + static EditorHelpHighlighter *get_singleton(); + + void highlight(RichTextLabel *p_rich_text_label, Language p_language, const String &p_source, bool p_use_cache); + void reset_cache(); + + EditorHelpHighlighter(); + virtual ~EditorHelpHighlighter(); +}; +#endif // defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + #endif // EDITOR_HELP_H diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 83a7e115c9..a14c6e8462 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2009,6 +2009,10 @@ Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo Array output; for (const PropertyInfo &pi : p_list) { + if (!(pi.usage & PROPERTY_USAGE_EDITOR)) { + continue; + } + if (pi.name.begins_with(array_element_prefix)) { String str = pi.name.trim_prefix(array_element_prefix); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e1b69def14..b8d87cfe9f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -165,6 +165,8 @@ #include <stdio.h> #include <stdlib.h> +#include "modules/modules_enabled.gen.h" // For gdscript, mono. + EditorNode *EditorNode::singleton = nullptr; static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; @@ -576,6 +578,9 @@ void EditorNode::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { EditorHelp::generate_doc(); +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + EditorHelpHighlighter::create_singleton(); +#endif } break; case NOTIFICATION_PROCESS: { @@ -801,6 +806,12 @@ void EditorNode::_notification(int p_what) { _update_update_spinner(); _update_vsync_mode(); } + +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + if (EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme/highlighting")) { + EditorHelpHighlighter::get_singleton()->reset_cache(); + } +#endif } break; } } @@ -7420,6 +7431,9 @@ EditorNode::~EditorNode() { remove_print_handler(&print_handler); EditorHelp::cleanup_doc(); +#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) + EditorHelpHighlighter::free_singleton(); +#endif memdelete(editor_selection); memdelete(editor_plugins_over); memdelete(editor_plugins_force_over); diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 94bf15ae66..ddf230dfdb 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -269,7 +269,7 @@ void EditorResourcePreview::_iterate() { if (item.resource.is_valid()) { Dictionary preview_metadata; _generate_preview(texture, small_texture, item, String(), preview_metadata); - _preview_ready(item.path, item.resource->hash_edited_version(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata); + _preview_ready(item.path, item.resource->hash_edited_version_for_preview(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata); return; } @@ -407,7 +407,7 @@ void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p String path_id = "ID:" + itos(p_res->get_instance_id()); - if (cache.has(path_id) && cache[path_id].last_hash == p_res->hash_edited_version()) { + if (cache.has(path_id) && cache[path_id].last_hash == p_res->hash_edited_version_for_preview()) { cache[path_id].order = order++; p_receiver->call(p_receiver_func, path_id, cache[path_id].preview, cache[path_id].small_preview, p_userdata); return; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index d0b633b136..29652c04b5 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -838,6 +838,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // TRANSLATORS: Project Manager here refers to the tool used to create/manage Godot projects. EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/sorting_order", 0, "Last Edited,Name,Path") + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/directory_naming_convention", 1, "No convention,kebab-case,snake_case,camelCase,PascalCase,Title Case") #if defined(WEB_ENABLED) // Web platform only supports `gl_compatibility`. diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 6d690cf31b..965cb39df3 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -398,8 +398,24 @@ FindInFilesDialog::FindInFilesDialog() { } void FindInFilesDialog::set_search_text(const String &text) { - _search_text_line_edit->set_text(text); - _on_search_text_modified(text); + if (_mode == SEARCH_MODE) { + if (!text.is_empty()) { + _search_text_line_edit->set_text(text); + _on_search_text_modified(text); + } + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + _search_text_line_edit->select_all(); + } else if (_mode == REPLACE_MODE) { + if (!text.is_empty()) { + _search_text_line_edit->set_text(text); + callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(); + _replace_text_line_edit->select_all(); + _on_search_text_modified(text); + } else { + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + _search_text_line_edit->select_all(); + } + } } void FindInFilesDialog::set_replace_text(const String &text) { @@ -464,9 +480,6 @@ void FindInFilesDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { - // Doesn't work more than once if not deferred... - callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); - _search_text_line_edit->select_all(); // Extensions might have changed in the meantime, we clean them and instance them again. for (int i = 0; i < _filters_container->get_child_count(); i++) { _filters_container->get_child(i)->queue_free(); diff --git a/editor/icons/editor_icons_builders.py b/editor/icons/editor_icons_builders.py index 378eb323db..4fe74881ed 100644 --- a/editor/icons/editor_icons_builders.py +++ b/editor/icons/editor_icons_builders.py @@ -85,7 +85,7 @@ def make_editor_icons_action(target, source, env): s.write("#endif\n") - with open(dst, "w") as f: + with open(dst, "w", encoding="utf-8", newline="\n") as f: f.write(s.getvalue()) s.close() diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index f0de608cf5..721eccdfdd 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -443,24 +443,27 @@ void SceneImportSettingsDialog::_update_view_gizmos() { return; } for (const KeyValue<String, NodeData> &e : node_map) { + // Skip import nodes that aren't MeshInstance3D. + const MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(e.value.node); + if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) { + continue; + } + + // Determine if the mesh collider should be visible. bool show_collider_view = false; if (e.value.settings.has(SNAME("generate/physics"))) { show_collider_view = e.value.settings[SNAME("generate/physics")]; } - MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(e.value.node); - if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) { - // Nothing to do. - continue; - } - + // Get the collider_view MeshInstance3D. TypedArray<Node> descendants = mesh_node->find_children("collider_view", "MeshInstance3D"); - CRASH_COND_MSG(descendants.is_empty(), "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`."); + MeshInstance3D *collider_view = Object::cast_to<MeshInstance3D>(descendants[0].operator Object *()); - MeshInstance3D *collider_view = static_cast<MeshInstance3D *>(descendants[0].operator Object *()); - collider_view->set_visible(show_collider_view); - if (generate_collider) { + // Regenerate the physics collider for this MeshInstance3D if either: + // - A regeneration is requested for the selected import node. + // - The collider is being made visible. + if ((generate_collider && e.key == selected_id) || (show_collider_view && !collider_view->is_visible())) { // This collider_view doesn't have a mesh so we need to generate a new one. Ref<ImporterMesh> mesh; mesh.instantiate(); @@ -524,6 +527,9 @@ void SceneImportSettingsDialog::_update_view_gizmos() { collider_view->set_mesh(collider_view_mesh); collider_view->set_transform(transform); } + + // Set the collider visibility. + collider_view->set_visible(show_collider_view); } generate_collider = false; diff --git a/editor/plugins/gizmos/fog_volume_gizmo_plugin.cpp b/editor/plugins/gizmos/fog_volume_gizmo_plugin.cpp index b7bba8f8d7..dd91d7dfe3 100644 --- a/editor/plugins/gizmos/fog_volume_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/fog_volume_gizmo_plugin.cpp @@ -34,10 +34,12 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/fog_volume.h" FogVolumeGizmoPlugin::FogVolumeGizmoPlugin() { + helper.instantiate(); Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/fog_volume", Color(0.5, 0.7, 1)); create_material("shape_material", gizmo_color); gizmo_color.a = 0.15; @@ -48,6 +50,9 @@ FogVolumeGizmoPlugin::FogVolumeGizmoPlugin() { create_handle_material("handles"); } +FogVolumeGizmoPlugin::~FogVolumeGizmoPlugin() { +} + bool FogVolumeGizmoPlugin::has_gizmo(Node3D *p_spatial) { return (Object::cast_to<FogVolume>(p_spatial) != nullptr); } @@ -61,63 +66,40 @@ int FogVolumeGizmoPlugin::get_priority() const { } String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - return "Size"; + return helper->box_get_handle_name(p_id); } Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { return Vector3(p_gizmo->get_node_3d()->call("get_size")); } -void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Node3D *sn = p_gizmo->get_node_3d(); - - Transform3D gt = sn->get_global_transform(); - Transform3D gi = gt.affine_inverse(); - - Vector3 ray_from = p_camera->project_ray_origin(p_point); - Vector3 ray_dir = p_camera->project_ray_normal(p_point); - - Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; +void FogVolumeGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { + helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); +} - Vector3 axis; - axis[p_id] = 1.0; - Vector3 ra, rb; - Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); - float d = ra[p_id] * 2; - if (Node3DEditor::get_singleton()->is_snap_enabled()) { - d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); - } +void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + FogVolume *fog_volume = Object::cast_to<FogVolume>(p_gizmo->get_node_3d()); + Vector3 size = fog_volume->get_size(); - if (d < 0.001) { - d = 0.001; - } + Vector3 sg[2]; + helper->get_segment(p_camera, p_point, sg); - Vector3 he = sn->call("get_size"); - he[p_id] = d; - sn->call("set_size", he); + Vector3 position; + helper->box_set_handle(sg, p_id, size, position); + fog_volume->set_size(size); + fog_volume->set_global_position(position); } void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Node3D *sn = p_gizmo->get_node_3d(); - - if (p_cancel) { - sn->call("set_size", p_restore); - return; - } - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Change Fog Volume Size")); - ur->add_do_method(sn, "set_size", sn->call("get_size")); - ur->add_undo_method(sn, "set_size", p_restore); - ur->commit_action(); + helper->box_commit_handle(TTR("Change FogVolume Size"), p_cancel, p_gizmo->get_node_3d()); } void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Node3D *cs = p_gizmo->get_node_3d(); + FogVolume *fog_volume = Object::cast_to<FogVolume>(p_gizmo->get_node_3d()); p_gizmo->clear(); - if (RS::FogVolumeShape(int(p_gizmo->get_node_3d()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { + if (fog_volume->get_shape() != RS::FOG_VOLUME_SHAPE_WORLD) { const Ref<Material> material = get_material("shape_material", p_gizmo); const Ref<Material> material_internal = @@ -127,7 +109,7 @@ void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector<Vector3> lines; AABB aabb; - aabb.size = cs->call("get_size").operator Vector3(); + aabb.size = fog_volume->get_size(); aabb.position = aabb.size / -2; for (int i = 0; i < 12; i++) { @@ -137,13 +119,7 @@ void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { lines.push_back(b); } - Vector<Vector3> handles; - - for (int i = 0; i < 3; i++) { - Vector3 ax; - ax[i] = cs->call("get_size").operator Vector3()[i] / 2; - handles.push_back(ax); - } + Vector<Vector3> handles = helper->box_get_handles(fog_volume->get_size()); p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); diff --git a/editor/plugins/gizmos/fog_volume_gizmo_plugin.h b/editor/plugins/gizmos/fog_volume_gizmo_plugin.h index 7f9c68a957..dd84075ca1 100644 --- a/editor/plugins/gizmos/fog_volume_gizmo_plugin.h +++ b/editor/plugins/gizmos/fog_volume_gizmo_plugin.h @@ -33,9 +33,13 @@ #include "editor/plugins/node_3d_editor_gizmos.h" +class Gizmo3DHelper; + class FogVolumeGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(FogVolumeGizmoPlugin, EditorNode3DGizmoPlugin); + Ref<Gizmo3DHelper> helper; + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; @@ -44,10 +48,12 @@ public: String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override; void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; FogVolumeGizmoPlugin(); + ~FogVolumeGizmoPlugin(); }; #endif // FOG_VOLUME_GIZMO_PLUGIN_H diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 04d4c6d779..86df57c469 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -58,6 +58,15 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, String node_type = state->get_node_type(i); String parent_path = state->get_node_path(i, true); + // Handle instanced scenes. + if (node_type.is_empty()) { + Ref<PackedScene> instance = state->get_node_instance(i); + if (instance.is_valid()) { + Ref<SceneState> _state = instance->get_state(); + node_type = _state->get_node_type(0); + } + } + // Find the `auto_translate_mode` property. bool auto_translating = true; bool auto_translate_mode_found = false; @@ -118,7 +127,8 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, for (int j = 0; j < state->get_node_property_count(i); j++) { String property_name = state->get_node_property_name(i, j); - if (!lookup_properties.has(property_name) || (exception_list.has(node_type) && exception_list[node_type].has(property_name))) { + + if (!match_property(property_name, node_type)) { continue; } @@ -134,15 +144,6 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, parsed_strings.append_array(temp); r_ids_ctx_plural->append_array(ids_context_plural); } - } else if ((node_type == "MenuButton" || node_type == "OptionButton") && property_name == "items") { - Vector<String> str_values = property_value; - int incr_value = node_type == "MenuButton" ? PopupMenu::ITEM_PROPERTY_SIZE : OptionButton::ITEM_PROPERTY_SIZE; - for (int k = 0; k < str_values.size(); k += incr_value) { - String desc = str_values[k].get_slice(";", 1).strip_edges(); - if (!desc.is_empty()) { - parsed_strings.push_back(desc); - } - } } else if (node_type == "FileDialog" && property_name == "filters") { // Extract FileDialog's filters property with values in format "*.png ; PNG Images","*.gd ; GDScript Files". Vector<String> str_values = property_value; @@ -167,14 +168,32 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, return OK; } +bool PackedSceneEditorTranslationParserPlugin::match_property(const String &p_property_name, const String &p_node_type) { + for (const KeyValue<String, Vector<String>> &exception : exception_list) { + const String &exception_node_type = exception.key; + if (ClassDB::is_parent_class(p_node_type, exception_node_type)) { + const Vector<String> &exception_properties = exception.value; + for (const String &exception_property : exception_properties) { + if (p_property_name.match(exception_property)) { + return false; + } + } + } + } + for (const String &lookup_property : lookup_properties) { + if (p_property_name.match(lookup_property)) { + return true; + } + } + return false; +} + PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlugin() { // Scene Node's properties containing strings that will be fetched for translation. lookup_properties.insert("text"); - lookup_properties.insert("tooltip_text"); - lookup_properties.insert("placeholder_text"); - lookup_properties.insert("items"); + lookup_properties.insert("*_text"); + lookup_properties.insert("popup/*/text"); lookup_properties.insert("title"); - lookup_properties.insert("dialog_text"); lookup_properties.insert("filters"); lookup_properties.insert("script"); diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index d1a92ded32..0a5cc1c2a6 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -43,6 +43,7 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; + bool match_property(const String &p_property_name, const String &p_node_type); virtual void get_recognized_extensions(List<String> *r_extensions) const override; PackedSceneEditorTranslationParserPlugin(); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 02e2a9f487..b6a4a14117 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -482,6 +482,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (p_just_update) { Link &link = links[p_id]; + link.visual_node = vsnode.ptr(); link.graph_element = node; link.preview_box = nullptr; link.preview_pos = -1; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 4187bf5a32..b951e9453e 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -618,14 +618,15 @@ void ProjectManager::_new_project() { } void ProjectManager::_rename_project() { - const HashSet<String> &selected_list = project_list->get_selected_project_keys(); + const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects(); if (selected_list.size() == 0) { return; } - for (const String &E : selected_list) { - project_dialog->set_project_path(E); + for (const ProjectList::Item &E : selected_list) { + project_dialog->set_project_name(E.project_name); + project_dialog->set_project_path(E.path); project_dialog->set_mode(ProjectDialog::MODE_RENAME); project_dialog->show_dialog(); } diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 3af9509bbe..350bb5bb9f 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -41,349 +41,382 @@ #include "editor/themes/editor_icons.h" #include "editor/themes/editor_scale.h" #include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" #include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" -void ProjectDialog::_set_message(const String &p_msg, MessageType p_type, InputType input_type) { +void ProjectDialog::_set_message(const String &p_msg, MessageType p_type, InputType p_input_type) { msg->set_text(p_msg); - Ref<Texture2D> current_path_icon = status_rect->get_texture(); - Ref<Texture2D> current_install_icon = install_status_rect->get_texture(); - Ref<Texture2D> new_icon; + get_ok_button()->set_disabled(p_type == MESSAGE_ERROR); + Ref<Texture2D> new_icon; switch (p_type) { case MESSAGE_ERROR: { msg->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), EditorStringName(Editor))); - msg->set_modulate(Color(1, 1, 1, 1)); new_icon = get_editor_theme_icon(SNAME("StatusError")); - } break; case MESSAGE_WARNING: { msg->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); - msg->set_modulate(Color(1, 1, 1, 1)); new_icon = get_editor_theme_icon(SNAME("StatusWarning")); - } break; case MESSAGE_SUCCESS: { - msg->remove_theme_color_override("font_color"); - msg->set_modulate(Color(1, 1, 1, 0)); + msg->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), EditorStringName(Editor))); new_icon = get_editor_theme_icon(SNAME("StatusSuccess")); - } break; } - if (current_path_icon != new_icon && input_type == PROJECT_PATH) { - status_rect->set_texture(new_icon); - } else if (current_install_icon != new_icon && input_type == INSTALL_PATH) { + if (p_input_type == PROJECT_PATH) { + project_status_rect->set_texture(new_icon); + } else if (p_input_type == INSTALL_PATH) { install_status_rect->set_texture(new_icon); } } static bool is_zip_file(Ref<DirAccess> p_d, const String &p_path) { - return p_path.ends_with(".zip") && p_d->file_exists(p_path); + return p_path.get_extension() == "zip" && p_d->file_exists(p_path); } -String ProjectDialog::_test_path() { +void ProjectDialog::_validate_path() { + _set_message("", MESSAGE_SUCCESS, PROJECT_PATH); + _set_message("", MESSAGE_SUCCESS, INSTALL_PATH); + + if (project_name->get_text().strip_edges().is_empty()) { + _set_message(TTR("It would be a good idea to name your project."), MESSAGE_ERROR); + return; + } + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - const String base_path = project_path->get_text(); - String valid_path, valid_install_path; - bool is_zip = false; - if (d->change_dir(base_path) == OK) { - valid_path = base_path; - } else if (is_zip_file(d, base_path)) { - valid_path = base_path; - is_zip = true; - } else if (d->change_dir(base_path.strip_edges()) == OK) { - valid_path = base_path.strip_edges(); - } else if (is_zip_file(d, base_path.strip_edges())) { - valid_path = base_path.strip_edges(); - is_zip = true; - } - - if (valid_path.is_empty()) { - _set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR); - get_ok_button()->set_disabled(true); - return ""; - } - - if (mode == MODE_IMPORT && is_zip) { - if (d->change_dir(install_path->get_text()) == OK) { - valid_install_path = install_path->get_text(); - } else if (d->change_dir(install_path->get_text().strip_edges()) == OK) { - valid_install_path = install_path->get_text().strip_edges(); + String path = project_path->get_text().simplify_path(); + + String target_path = path; + InputType target_path_input_type = PROJECT_PATH; + + if (mode == MODE_IMPORT) { + if (path.get_file().strip_edges() == "project.godot") { + path = path.get_base_dir(); + project_path->set_text(path); } - if (valid_install_path.is_empty()) { - _set_message(TTR("The install path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH); - get_ok_button()->set_disabled(true); - return ""; + if (is_zip_file(d, path)) { + zip_path = path; + } else if (is_zip_file(d, path.strip_edges())) { + zip_path = path.strip_edges(); + } else { + zip_path = ""; } - } - if (mode == MODE_IMPORT || mode == MODE_RENAME) { - if (!d->file_exists("project.godot")) { - if (is_zip) { - Ref<FileAccess> io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); + if (!zip_path.is_empty()) { + target_path = install_path->get_text().simplify_path(); + target_path_input_type = INSTALL_PATH; - unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io); - if (!pkg) { - _set_message(TTR("Error opening package file (it's not in ZIP format)."), MESSAGE_ERROR); - get_ok_button()->set_disabled(true); - unzClose(pkg); - return ""; - } + create_dir->show(); + install_path_container->show(); - int ret = unzGoToFirstFile(pkg); - while (ret == UNZ_OK) { - unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - if (ret != UNZ_OK) { - break; - } + Ref<FileAccess> io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); - if (String::utf8(fname).ends_with("project.godot")) { - break; - } + unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io); + if (!pkg) { + _set_message(TTR("Invalid \".zip\" project file; it is not in ZIP format."), MESSAGE_ERROR); + unzClose(pkg); + return; + } - ret = unzGoToNextFile(pkg); - } + int ret = unzGoToFirstFile(pkg); + while (ret == UNZ_OK) { + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); - if (ret == UNZ_END_OF_LIST_OF_FILE) { - _set_message(TTR("Invalid \".zip\" project file; it doesn't contain a \"project.godot\" file."), MESSAGE_ERROR); - get_ok_button()->set_disabled(true); - unzClose(pkg); - return ""; + String name = String::utf8(fname); + if (name.get_file() == "project.godot") { + break; // ret == UNZ_OK. } + ret = unzGoToNextFile(pkg); + } + + if (ret == UNZ_END_OF_LIST_OF_FILE) { + _set_message(TTR("Invalid \".zip\" project file; it doesn't contain a \"project.godot\" file."), MESSAGE_ERROR); unzClose(pkg); + return; + } - // check if the specified install folder is empty, even though this is not an error, it is good to check here - d->list_dir_begin(); - is_folder_empty = true; - String n = d->get_next(); - while (!n.is_empty()) { - if (!n.begins_with(".")) { - // Allow `.`, `..` (reserved current/parent folder names) - // and hidden files/folders to be present. - // For instance, this lets users initialize a Git repository - // and still be able to create a project in the directory afterwards. - is_folder_empty = false; - break; - } - n = d->get_next(); - } - d->list_dir_end(); + unzClose(pkg); + } else if (d->dir_exists(path) && d->file_exists(path.path_join("project.godot"))) { + zip_path = ""; - if (!is_folder_empty) { - _set_message(TTR("Please choose an empty install folder."), MESSAGE_WARNING, INSTALL_PATH); - get_ok_button()->set_disabled(true); - return ""; - } + create_dir->hide(); + install_path_container->hide(); - } else { - _set_message(TTR("Please choose a \"project.godot\", a directory with it, or a \".zip\" file."), MESSAGE_ERROR); - install_path_container->hide(); - get_ok_button()->set_disabled(true); - return ""; - } + _set_message(TTR("Valid project found at path."), MESSAGE_SUCCESS); + } else { + create_dir->hide(); + install_path_container->hide(); - } else if (is_zip) { - _set_message(TTR("The install directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH); - get_ok_button()->set_disabled(true); - return ""; + _set_message(TTR("Please choose a \"project.godot\", a directory with one, or a \".zip\" file."), MESSAGE_ERROR); + return; } + } - } else { - // Check if the specified folder is empty, even though this is not an error, it is good to check here. - d->list_dir_begin(); - is_folder_empty = true; - String n = d->get_next(); - while (!n.is_empty()) { - if (!n.begins_with(".")) { - // Allow `.`, `..` (reserved current/parent folder names) - // and hidden files/folders to be present. - // For instance, this lets users initialize a Git repository - // and still be able to create a project in the directory afterwards. - is_folder_empty = false; - break; - } - n = d->get_next(); - } - d->list_dir_end(); + if (target_path.is_empty() || target_path.is_relative_path()) { + _set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type); + return; + } - if (!is_folder_empty) { - if (valid_path == OS::get_singleton()->get_environment("HOME") || valid_path == OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS) || valid_path == OS::get_singleton()->get_executable_path().get_base_dir()) { - _set_message(TTR("You cannot save a project in the selected path. Please make a new folder or choose a new path."), MESSAGE_ERROR); - get_ok_button()->set_disabled(true); - return ""; - } + if (target_path.get_file() != OS::get_singleton()->get_safe_dir_name(target_path.get_file())) { + _set_message(TTR("The directory name specified contains invalid characters or trailing whitespace."), MESSAGE_ERROR, target_path_input_type); + return; + } - _set_message(TTR("The selected path is not empty. Choosing an empty folder is highly recommended."), MESSAGE_WARNING); - get_ok_button()->set_disabled(false); - return valid_path; - } + String working_dir = d->get_current_dir(); + String executable_dir = OS::get_singleton()->get_executable_path().get_base_dir(); + if (target_path == working_dir || target_path == executable_dir) { + _set_message(TTR("Creating a project at the engine's working directory or executable directory is not allowed, as it would prevent the project manager from starting."), MESSAGE_ERROR, target_path_input_type); + return; } - _set_message(""); - _set_message("", MESSAGE_SUCCESS, INSTALL_PATH); - get_ok_button()->set_disabled(false); - return valid_path; -} + // TODO: The following 5 lines could be simplified if OS.get_user_home_dir() or SYSTEM_DIR_HOME is implemented. See: https://github.com/godotengine/godot-proposals/issues/4851. +#ifdef WINDOWS_ENABLED + String home_dir = OS::get_singleton()->get_environment("USERPROFILE"); +#else + String home_dir = OS::get_singleton()->get_environment("HOME"); +#endif + String documents_dir = OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS); + if (target_path == home_dir || target_path == documents_dir) { + _set_message(TTR("You cannot save a project at the selected path. Please create a subfolder or choose a new path."), MESSAGE_ERROR, target_path_input_type); + return; + } -void ProjectDialog::_update_path(const String &p_path) { - String sp = _test_path(); - if (!sp.is_empty()) { - // If the project name is empty or default, infer the project name from the selected folder name - if (project_name->get_text().strip_edges().is_empty() || project_name->get_text().strip_edges() == TTR("New Game Project")) { - sp = sp.replace("\\", "/"); - int lidx = sp.rfind("/"); + is_folder_empty = true; + if (mode == MODE_NEW || mode == MODE_INSTALL || (mode == MODE_IMPORT && target_path_input_type == InputType::INSTALL_PATH)) { + if (create_dir->is_pressed()) { + if (!d->dir_exists(target_path.get_base_dir())) { + _set_message(TTR("The parent directory of the path specified doesn't exist."), MESSAGE_ERROR, target_path_input_type); + return; + } - if (lidx != -1) { - sp = sp.substr(lidx + 1, sp.length()).capitalize(); + if (d->dir_exists(target_path)) { + // The path is not necessarily empty here, but we will update the message later if it isn't. + _set_message(TTR("The project folder already exists and is empty."), MESSAGE_SUCCESS, target_path_input_type); + } else { + _set_message(TTR("The project folder will be automatically created."), MESSAGE_SUCCESS, target_path_input_type); + } + } else { + if (!d->dir_exists(target_path)) { + _set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR, target_path_input_type); + return; } - if (sp.is_empty() && mode == MODE_IMPORT) { - sp = TTR("Imported Project"); + + // The path is not necessarily empty here, but we will update the message later if it isn't. + _set_message(TTR("The project folder exists and is empty."), MESSAGE_SUCCESS, target_path_input_type); + } + + // Check if the directory is empty. Not an error, but we want to warn the user. + if (d->change_dir(target_path) == OK) { + d->list_dir_begin(); + String n = d->get_next(); + while (!n.is_empty()) { + if (n[0] != '.') { + // Allow `.`, `..` (reserved current/parent folder names) + // and hidden files/folders to be present. + // For instance, this lets users initialize a Git repository + // and still be able to create a project in the directory afterwards. + is_folder_empty = false; + break; + } + n = d->get_next(); } + d->list_dir_end(); - project_name->set_text(sp); - _text_changed(sp); + if (!is_folder_empty) { + _set_message(TTR("The selected path is not empty. Choosing an empty folder is highly recommended."), MESSAGE_WARNING, target_path_input_type); + } } } +} - if (!created_folder_path.is_empty() && created_folder_path != p_path) { - _remove_created_folder(); +String ProjectDialog::_get_target_path() { + if (mode == MODE_NEW || mode == MODE_INSTALL) { + return project_path->get_text(); + } else if (mode == MODE_IMPORT) { + return install_path->get_text(); + } else { + ERR_FAIL_V(""); } } - -void ProjectDialog::_path_text_changed(const String &p_path) { - Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (mode == MODE_IMPORT && is_zip_file(d, p_path)) { - install_path->set_text(p_path.get_base_dir()); - install_path_container->show(); - } else if (mode == MODE_IMPORT && is_zip_file(d, p_path.strip_edges())) { - install_path->set_text(p_path.strip_edges().get_base_dir()); - install_path_container->show(); +void ProjectDialog::_set_target_path(const String &p_text) { + if (mode == MODE_NEW || mode == MODE_INSTALL) { + project_path->set_text(p_text); + } else if (mode == MODE_IMPORT) { + install_path->set_text(p_text); } else { - install_path_container->hide(); + ERR_FAIL(); + } +} + +void ProjectDialog::_update_target_auto_dir() { + String new_auto_dir; + if (mode == MODE_NEW || mode == MODE_INSTALL) { + new_auto_dir = project_name->get_text(); + } else if (mode == MODE_IMPORT) { + new_auto_dir = project_path->get_text().get_file().get_basename(); + } + int naming_convention = (int)EDITOR_GET("project_manager/directory_naming_convention"); + switch (naming_convention) { + case 0: // No convention + break; + case 1: // kebab-case + new_auto_dir = new_auto_dir.to_lower().replace(" ", "-"); + break; + case 2: // snake_case + new_auto_dir = new_auto_dir.to_snake_case(); + break; + case 3: // camelCase + new_auto_dir = new_auto_dir.to_camel_case(); + break; + case 4: // PascalCase + new_auto_dir = new_auto_dir.to_pascal_case(); + break; + case 5: // Title Case + new_auto_dir = new_auto_dir.capitalize(); + break; + default: + ERR_FAIL_MSG("Invalid directory naming convention."); + break; + } + new_auto_dir = OS::get_singleton()->get_safe_dir_name(new_auto_dir); + + if (create_dir->is_pressed()) { + String target_path = _get_target_path(); + + if (target_path.get_file() == auto_dir) { + // Update target dir name to new project name / ZIP name. + target_path = target_path.get_base_dir().path_join(new_auto_dir); + } + + _set_target_path(target_path); } - _update_path(p_path.simplify_path()); + auto_dir = new_auto_dir; } -void ProjectDialog::_file_selected(const String &p_path) { - // If not already shown. - show_dialog(); +void ProjectDialog::_create_dir_toggled(bool p_pressed) { + String target_path = _get_target_path(); - String p = p_path; - if (mode == MODE_IMPORT) { - if (p.ends_with("project.godot")) { - p = p.get_base_dir(); - install_path_container->hide(); - get_ok_button()->set_disabled(false); - } else if (p.ends_with(".zip")) { - install_path->set_text(p.get_base_dir()); - install_path_container->show(); - get_ok_button()->set_disabled(false); + if (create_dir->is_pressed()) { + // (Re-)append target dir name. + if (last_custom_target_dir.is_empty()) { + target_path = target_path.path_join(auto_dir); } else { - _set_message(TTR("Please choose a \"project.godot\" or \".zip\" file."), MESSAGE_ERROR); - get_ok_button()->set_disabled(true); - return; + target_path = target_path.path_join(last_custom_target_dir); + } + } else { + // Save and remove target dir name. + if (target_path.get_file() == auto_dir) { + last_custom_target_dir = ""; + } else { + last_custom_target_dir = target_path.get_file(); } + target_path = target_path.get_base_dir(); } - String sp = p.simplify_path(); - project_path->set_text(sp); - _update_path(sp); - if (p.ends_with(".zip")) { - callable_mp((Control *)install_path, &Control::grab_focus).call_deferred(); - } else { - callable_mp((Control *)get_ok_button(), &Control::grab_focus).call_deferred(); + _set_target_path(target_path); + _validate_path(); +} + +void ProjectDialog::_project_name_changed() { + if (mode == MODE_NEW || mode == MODE_INSTALL) { + _update_target_auto_dir(); } + + _validate_path(); } -void ProjectDialog::_path_selected(const String &p_path) { - // If not already shown. - show_dialog(); +void ProjectDialog::_project_path_changed() { + if (mode == MODE_IMPORT) { + _update_target_auto_dir(); + } - String sp = p_path.simplify_path(); - project_path->set_text(sp); - _update_path(sp); - callable_mp((Control *)get_ok_button(), &Control::grab_focus).call_deferred(); + _validate_path(); } -void ProjectDialog::_install_path_selected(const String &p_path) { - String sp = p_path.simplify_path(); - install_path->set_text(sp); - _update_path(sp); - callable_mp((Control *)get_ok_button(), &Control::grab_focus).call_deferred(); +void ProjectDialog::_install_path_changed() { + _validate_path(); } -void ProjectDialog::_browse_path() { - fdialog->set_current_dir(project_path->get_text()); +void ProjectDialog::_browse_project_path() { + if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) { + // Select last ZIP file. + fdialog_project->set_current_path(project_path->get_text()); + } else if ((mode == MODE_NEW || mode == MODE_INSTALL) && create_dir->is_pressed()) { + // Select parent directory of project path. + fdialog_project->set_current_dir(project_path->get_text().get_base_dir()); + } else { + // Select project path. + fdialog_project->set_current_dir(project_path->get_text()); + } if (mode == MODE_IMPORT) { - fdialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_ANY); - fdialog->clear_filters(); - fdialog->add_filter("project.godot", vformat("%s %s", VERSION_NAME, TTR("Project"))); - fdialog->add_filter("*.zip", TTR("ZIP File")); + fdialog_project->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_ANY); + fdialog_project->clear_filters(); + fdialog_project->add_filter("project.godot", vformat("%s %s", VERSION_NAME, TTR("Project"))); + fdialog_project->add_filter("*.zip", TTR("ZIP File")); } else { - fdialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); + fdialog_project->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); } - fdialog->popup_file_dialog(); + fdialog_project->popup_file_dialog(); } void ProjectDialog::_browse_install_path() { - fdialog_install->set_current_dir(install_path->get_text()); + ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT."); + + if (create_dir->is_pressed()) { + // Select parent directory of install path. + fdialog_install->set_current_dir(install_path->get_text().get_base_dir()); + } else { + // Select install path. + fdialog_install->set_current_dir(install_path->get_text()); + } + fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); fdialog_install->popup_file_dialog(); } -void ProjectDialog::_create_folder() { - const String project_name_no_edges = project_name->get_text().strip_edges(); - if (project_name_no_edges.is_empty() || !created_folder_path.is_empty() || project_name_no_edges.ends_with(".")) { - _set_message(TTR("Invalid project name."), MESSAGE_WARNING); - return; +void ProjectDialog::_project_path_selected(const String &p_path) { + if (create_dir->is_pressed() && (mode == MODE_NEW || mode == MODE_INSTALL)) { + // Replace parent directory, but keep target dir name. + project_path->set_text(p_path.path_join(project_path->get_text().get_file())); + } else { + project_path->set_text(p_path); } - Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (d->change_dir(project_path->get_text()) == OK) { - if (!d->dir_exists(project_name_no_edges)) { - if (d->make_dir(project_name_no_edges) == OK) { - d->change_dir(project_name_no_edges); - String dir_str = d->get_current_dir(); - project_path->set_text(dir_str); - _update_path(dir_str); - created_folder_path = d->get_current_dir(); - create_dir->set_disabled(true); - } else { - dialog_error->set_text(TTR("Couldn't create folder.")); - dialog_error->popup_centered(); - } - } else { - dialog_error->set_text(TTR("There is already a folder in this path with the specified name.")); - dialog_error->popup_centered(); - } - } -} + _project_path_changed(); -void ProjectDialog::_text_changed(const String &p_text) { - if (mode != MODE_NEW) { - return; + if (install_path->is_visible_in_tree()) { + // ZIP is selected; focus install path. + install_path->grab_focus(); + } else { + get_ok_button()->grab_focus(); } +} - _test_path(); +void ProjectDialog::_install_path_selected(const String &p_path) { + ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT."); - if (p_text.strip_edges().is_empty()) { - _set_message(TTR("It would be a good idea to name your project."), MESSAGE_ERROR); + if (create_dir->is_pressed()) { + // Replace parent directory, but keep target dir name. + install_path->set_text(p_path.path_join(install_path->get_text().get_file())); + } else { + install_path->set_text(p_path); } -} -void ProjectDialog::_nonempty_confirmation_ok_pressed() { - is_folder_empty = true; - ok_pressed(); + _install_path_changed(); + + get_ok_button()->grab_focus(); } void ProjectDialog::_renderer_selected() { @@ -417,233 +450,216 @@ void ProjectDialog::_renderer_selected() { } } -void ProjectDialog::_remove_created_folder() { - if (!created_folder_path.is_empty()) { - Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - d->remove(created_folder_path); - - create_dir->set_disabled(false); - created_folder_path = ""; - } +void ProjectDialog::_nonempty_confirmation_ok_pressed() { + is_folder_empty = true; + ok_pressed(); } void ProjectDialog::ok_pressed() { - String dir = project_path->get_text(); + // Before we create a project, check that the target folder is empty. + // If not, we need to ask the user if they're sure they want to do this. + if (!is_folder_empty) { + ConfirmationDialog *cd = memnew(ConfirmationDialog); + cd->set_title(TTR("Warning: This folder is not empty")); + cd->set_text(TTR("You are about to create a Godot project in a non-empty folder.\nThe entire contents of this folder will be imported as project resources!\n\nAre you sure you wish to continue?")); + cd->get_ok_button()->connect("pressed", callable_mp(this, &ProjectDialog::_nonempty_confirmation_ok_pressed)); + get_parent()->add_child(cd); + cd->popup_centered(); + return; + } - if (mode == MODE_RENAME) { - String dir2 = _test_path(); - if (dir2.is_empty()) { - _set_message(TTR("Invalid project path (changed anything?)."), MESSAGE_ERROR); + String path = project_path->get_text(); + + if (mode == MODE_NEW) { + if (create_dir->is_pressed()) { + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!d->dir_exists(path) && d->make_dir(path) != OK) { + _set_message(TTR("Couldn't create project directory, check permissions."), MESSAGE_ERROR); + return; + } + } + + PackedStringArray project_features = ProjectSettings::get_required_features(); + ProjectSettings::CustomMap initial_settings; + + // Be sure to change this code if/when renderers are changed. + // Default values are "forward_plus" for the main setting, "mobile" for the mobile override, + // and "gl_compatibility" for the web override. + String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method")); + initial_settings["rendering/renderer/rendering_method"] = renderer_type; + + EditorSettings::get_singleton()->set("project_manager/default_renderer", renderer_type); + EditorSettings::get_singleton()->save(); + + if (renderer_type == "forward_plus") { + project_features.push_back("Forward Plus"); + } else if (renderer_type == "mobile") { + project_features.push_back("Mobile"); + } else if (renderer_type == "gl_compatibility") { + project_features.push_back("GL Compatibility"); + // Also change the default rendering method for the mobile override. + initial_settings["rendering/renderer/rendering_method.mobile"] = "gl_compatibility"; + } else { + WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub."); + } + + project_features.sort(); + initial_settings["application/config/features"] = project_features; + initial_settings["application/config/name"] = project_name->get_text().strip_edges(); + initial_settings["application/config/icon"] = "res://icon.svg"; + + Error err = ProjectSettings::get_singleton()->save_custom(path.path_join("project.godot"), initial_settings, Vector<String>(), false); + if (err != OK) { + _set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); return; } - // Load project.godot as ConfigFile to set the new name. - ConfigFile cfg; - String project_godot = dir2.path_join("project.godot"); - Error err = cfg.load(project_godot); + // Store default project icon in SVG format. + Ref<FileAccess> fa_icon = FileAccess::open(path.path_join("icon.svg"), FileAccess::WRITE, &err); if (err != OK) { - _set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR); - } else { - cfg.set_value("application", "config/name", project_name->get_text().strip_edges()); - err = cfg.save(project_godot); - if (err != OK) { - _set_message(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err), MESSAGE_ERROR); - } + _set_message(TTR("Couldn't create icon.svg in project path."), MESSAGE_ERROR); + return; } + fa_icon->store_string(get_default_project_icon()); - hide(); - emit_signal(SNAME("projects_updated")); + EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), path); + } - } else { - if (mode == MODE_IMPORT) { - if (project_path->get_text().ends_with(".zip")) { - mode = MODE_INSTALL; - ok_pressed(); + // Two cases for importing a ZIP. + switch (mode) { + case MODE_IMPORT: { + if (zip_path.is_empty()) { + break; + } + path = install_path->get_text().simplify_path(); + [[fallthrough]]; + } + case MODE_INSTALL: { + ERR_FAIL_COND(zip_path.is_empty()); + + Ref<FileAccess> io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + + unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io); + if (!pkg) { + dialog_error->set_text(TTR("Error opening package file, not in ZIP format.")); + dialog_error->popup_centered(); return; } - } else { - if (mode == MODE_NEW) { - // Before we create a project, check that the target folder is empty. - // If not, we need to ask the user if they're sure they want to do this. - if (!is_folder_empty) { - ConfirmationDialog *cd = memnew(ConfirmationDialog); - cd->set_title(TTR("Warning: This folder is not empty")); - cd->set_text(TTR("You are about to create a Godot project in a non-empty folder.\nThe entire contents of this folder will be imported as project resources!\n\nAre you sure you wish to continue?")); - cd->get_ok_button()->connect("pressed", callable_mp(this, &ProjectDialog::_nonempty_confirmation_ok_pressed)); - get_parent()->add_child(cd); - cd->popup_centered(); - cd->grab_focus(); - return; + // Find the first directory with a "project.godot". + String zip_root; + int ret = unzGoToFirstFile(pkg); + while (ret == UNZ_OK) { + unz_file_info info; + char fname[16384]; + unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); + + String name = String::utf8(fname); + if (name.get_file() == "project.godot") { + zip_root = name.get_base_dir(); + break; } - PackedStringArray project_features = ProjectSettings::get_required_features(); - ProjectSettings::CustomMap initial_settings; - - // Be sure to change this code if/when renderers are changed. - // Default values are "forward_plus" for the main setting, "mobile" for the mobile override, - // and "gl_compatibility" for the web override. - String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method")); - initial_settings["rendering/renderer/rendering_method"] = renderer_type; - - EditorSettings::get_singleton()->set("project_manager/default_renderer", renderer_type); - EditorSettings::get_singleton()->save(); - - if (renderer_type == "forward_plus") { - project_features.push_back("Forward Plus"); - } else if (renderer_type == "mobile") { - project_features.push_back("Mobile"); - } else if (renderer_type == "gl_compatibility") { - project_features.push_back("GL Compatibility"); - // Also change the default rendering method for the mobile override. - initial_settings["rendering/renderer/rendering_method.mobile"] = "gl_compatibility"; - } else { - WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub."); - } - - project_features.sort(); - initial_settings["application/config/features"] = project_features; - initial_settings["application/config/name"] = project_name->get_text().strip_edges(); - initial_settings["application/config/icon"] = "res://icon.svg"; - - if (ProjectSettings::get_singleton()->save_custom(dir.path_join("project.godot"), initial_settings, Vector<String>(), false) != OK) { - _set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); - } else { - // Store default project icon in SVG format. - Error err; - Ref<FileAccess> fa_icon = FileAccess::open(dir.path_join("icon.svg"), FileAccess::WRITE, &err); - fa_icon->store_string(get_default_project_icon()); - - if (err != OK) { - _set_message(TTR("Couldn't create icon.svg in project path."), MESSAGE_ERROR); - } - EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), dir); - } - } else if (mode == MODE_INSTALL) { - if (project_path->get_text().ends_with(".zip")) { - dir = install_path->get_text(); - zip_path = project_path->get_text(); - } + ret = unzGoToNextFile(pkg); + } - Ref<FileAccess> io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); + if (ret == UNZ_END_OF_LIST_OF_FILE) { + _set_message(TTR("Invalid \".zip\" project file; it doesn't contain a \"project.godot\" file."), MESSAGE_ERROR); + unzClose(pkg); + return; + } - unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io); - if (!pkg) { - dialog_error->set_text(TTR("Error opening package file, not in ZIP format.")); - dialog_error->popup_centered(); + if (create_dir->is_pressed()) { + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!d->dir_exists(path) && d->make_dir(path) != OK) { + _set_message(TTR("Couldn't create project directory, check permissions."), MESSAGE_ERROR); return; } + } - // Find the zip_root - String zip_root; - int ret = unzGoToFirstFile(pkg); - while (ret == UNZ_OK) { - unz_file_info info; - char fname[16384]; - unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - - String name = String::utf8(fname); - if (name.ends_with("project.godot")) { - zip_root = name.substr(0, name.rfind("project.godot")); - break; + ret = unzGoToFirstFile(pkg); + + Vector<String> failed_files; + while (ret == UNZ_OK) { + //get filename + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + ERR_FAIL_COND_MSG(ret != UNZ_OK, "Failed to get current file info."); + + String rel_path = String::utf8(fname).trim_prefix(zip_root); + if (rel_path.is_empty()) { // Root. + } else if (rel_path.ends_with("/")) { // Directory. + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + da->make_dir(path.path_join(rel_path)); + } else { // File. + Vector<uint8_t> uncomp_data; + uncomp_data.resize(info.uncompressed_size); + + unzOpenCurrentFile(pkg); + ret = unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size()); + ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", rel_path)); + unzCloseCurrentFile(pkg); + + Ref<FileAccess> f = FileAccess::open(path.path_join(rel_path), FileAccess::WRITE); + if (f.is_valid()) { + f->store_buffer(uncomp_data.ptr(), uncomp_data.size()); + } else { + failed_files.push_back(rel_path); } - - ret = unzGoToNextFile(pkg); } - ret = unzGoToFirstFile(pkg); + ret = unzGoToNextFile(pkg); + } - Vector<String> failed_files; + unzClose(pkg); - while (ret == UNZ_OK) { - //get filename - unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - if (ret != UNZ_OK) { + if (failed_files.size()) { + String err_msg = TTR("The following files failed extraction from package:") + "\n\n"; + for (int i = 0; i < failed_files.size(); i++) { + if (i > 15) { + err_msg += "\nAnd " + itos(failed_files.size() - i) + " more files."; break; } - - String path = String::utf8(fname); - - if (path.is_empty() || path == zip_root || !zip_root.is_subsequence_of(path)) { - // - } else if (path.ends_with("/")) { // a dir - path = path.substr(0, path.length() - 1); - String rel_path = path.substr(zip_root.length()); - - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - da->make_dir(dir.path_join(rel_path)); - } else { - Vector<uint8_t> uncomp_data; - uncomp_data.resize(info.uncompressed_size); - String rel_path = path.substr(zip_root.length()); - - //read - unzOpenCurrentFile(pkg); - ret = unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size()); - ERR_BREAK_MSG(ret < 0, vformat("An error occurred while attempting to read from file: %s. This file will not be used.", rel_path)); - unzCloseCurrentFile(pkg); - - Ref<FileAccess> f = FileAccess::open(dir.path_join(rel_path), FileAccess::WRITE); - if (f.is_valid()) { - f->store_buffer(uncomp_data.ptr(), uncomp_data.size()); - } else { - failed_files.push_back(rel_path); - } - } - - ret = unzGoToNextFile(pkg); + err_msg += failed_files[i] + "\n"; } - unzClose(pkg); - - if (failed_files.size()) { - String err_msg = TTR("The following files failed extraction from package:") + "\n\n"; - for (int i = 0; i < failed_files.size(); i++) { - if (i > 15) { - err_msg += "\nAnd " + itos(failed_files.size() - i) + " more files."; - break; - } - err_msg += failed_files[i] + "\n"; - } - - dialog_error->set_text(err_msg); - dialog_error->popup_centered(); - - } else if (!project_path->get_text().ends_with(".zip")) { - dialog_error->set_text(TTR("Package installed successfully!")); - dialog_error->popup_centered(); - } + dialog_error->set_text(err_msg); + dialog_error->popup_centered(); + return; } - } - - dir = dir.replace("\\", "/"); - if (dir.ends_with("/")) { - dir = dir.substr(0, dir.length() - 1); - } - - hide(); - emit_signal(SNAME("project_created"), dir); + } break; + default: { + } break; } -} - -void ProjectDialog::cancel_pressed() { - _remove_created_folder(); - - project_path->clear(); - _update_path(""); - project_name->clear(); - _text_changed(""); - if (status_rect->get_texture() == get_editor_theme_icon(SNAME("StatusError"))) { - msg->show(); + if (mode == MODE_RENAME || mode == MODE_INSTALL) { + // Load project.godot as ConfigFile to set the new name. + ConfigFile cfg; + String project_godot = path.path_join("project.godot"); + Error err = cfg.load(project_godot); + if (err != OK) { + dialog_error->set_text(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err)); + dialog_error->popup_centered(); + return; + } + cfg.set_value("application", "config/name", project_name->get_text().strip_edges()); + err = cfg.save(project_godot); + if (err != OK) { + dialog_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err)); + dialog_error->popup_centered(); + return; + } } - if (install_status_rect->get_texture() == get_editor_theme_icon(SNAME("StatusError"))) { - msg->show(); + hide(); + if (mode == MODE_NEW || mode == MODE_IMPORT || mode == MODE_INSTALL) { + emit_signal(SNAME("project_created"), path); + } else if (mode == MODE_RENAME) { + emit_signal(SNAME("projects_updated")); } } @@ -659,6 +675,10 @@ void ProjectDialog::set_mode(Mode p_mode) { mode = p_mode; } +void ProjectDialog::set_project_name(const String &p_name) { + project_name->set_text(p_name); +} + void ProjectDialog::set_project_path(const String &p_path) { project_path->set_text(p_path); } @@ -666,120 +686,96 @@ void ProjectDialog::set_project_path(const String &p_path) { void ProjectDialog::ask_for_path_and_show() { // Workaround: for the file selection dialog content to be rendered we need to show its parent dialog. show_dialog(); - _set_message(""); - - _browse_path(); + _browse_project_path(); } void ProjectDialog::show_dialog() { if (mode == MODE_RENAME) { + // Name and path are set in `ProjectManager::_rename_project`. project_path->set_editable(false); - browse->hide(); - install_browse->hide(); set_title(TTR("Rename Project")); set_ok_button_text(TTR("Rename")); + + create_dir->hide(); + project_status_rect->hide(); + project_browse->hide(); + name_container->show(); - status_rect->hide(); - msg->hide(); install_path_container->hide(); - install_status_rect->hide(); renderer_container->hide(); default_files_container->hide(); - get_ok_button()->set_disabled(false); - - // Fetch current name from project.godot to prefill the text input. - ConfigFile cfg; - String project_godot = project_path->get_text().path_join("project.godot"); - Error err = cfg.load(project_godot); - if (err != OK) { - _set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR); - status_rect->show(); - msg->show(); - get_ok_button()->set_disabled(true); - } else { - String cur_name = cfg.get_value("application", "config/name", ""); - project_name->set_text(cur_name); - _text_changed(cur_name); - } callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); - - create_dir->hide(); - + callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else { - fav_dir = EDITOR_GET("filesystem/directories/default_project_path"); + String proj = TTR("New Game Project"); + project_name->set_text(proj); + project_path->set_editable(true); + + String fav_dir = EDITOR_GET("filesystem/directories/default_project_path"); if (!fav_dir.is_empty()) { project_path->set_text(fav_dir); - fdialog->set_current_dir(fav_dir); + install_path->set_text(fav_dir); + fdialog_project->set_current_dir(fav_dir); } else { Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); project_path->set_text(d->get_current_dir()); - fdialog->set_current_dir(d->get_current_dir()); - } - - if (project_name->get_text().is_empty()) { - String proj = TTR("New Game Project"); - project_name->set_text(proj); - _text_changed(proj); + install_path->set_text(d->get_current_dir()); + fdialog_project->set_current_dir(d->get_current_dir()); } - project_path->set_editable(true); - browse->set_disabled(false); - browse->show(); - install_browse->set_disabled(false); - install_browse->show(); create_dir->show(); - status_rect->show(); - install_status_rect->show(); - msg->show(); + project_status_rect->show(); + project_browse->show(); if (mode == MODE_IMPORT) { set_title(TTR("Import Existing Project")); set_ok_button_text(TTR("Import & Edit")); + name_container->hide(); install_path_container->hide(); renderer_container->hide(); default_files_container->hide(); - project_path->grab_focus(); + // Project path dialog is also opened; no need to change focus. } else if (mode == MODE_NEW) { set_title(TTR("Create New Project")); set_ok_button_text(TTR("Create & Edit")); + name_container->show(); install_path_container->hide(); renderer_container->show(); default_files_container->show(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); callable_mp(project_name, &LineEdit::select_all).call_deferred(); - } else if (mode == MODE_INSTALL) { set_title(TTR("Install Project:") + " " + zip_title); set_ok_button_text(TTR("Install & Edit")); + project_name->set_text(zip_title); + name_container->show(); install_path_container->hide(); renderer_container->hide(); default_files_container->hide(); - project_path->grab_focus(); + + callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(); } - _test_path(); + auto_dir = ""; + last_custom_target_dir = ""; + _update_target_auto_dir(); + if (create_dir->is_pressed()) { + // Append `auto_dir` to target path. + _create_dir_toggled(true); + } } - popup_centered(Size2(500, 0) * EDSCALE); -} - -void ProjectDialog::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - create_dir->set_icon(get_editor_theme_icon(SNAME("FolderCreate"))); - } break; + _validate_path(); - case NOTIFICATION_WM_CLOSE_REQUEST: { - _remove_created_folder(); - } break; - } + popup_centered(Size2(500, 0) * EDSCALE); } void ProjectDialog::_bind_methods() { @@ -798,27 +794,29 @@ ProjectDialog::ProjectDialog() { l->set_text(TTR("Project Name:")); name_container->add_child(l); - HBoxContainer *pnhb = memnew(HBoxContainer); - name_container->add_child(pnhb); - project_name = memnew(LineEdit); project_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); - pnhb->add_child(project_name); + name_container->add_child(project_name); - create_dir = memnew(Button); - pnhb->add_child(create_dir); - create_dir->set_text(TTR("Create Folder")); - create_dir->connect("pressed", callable_mp(this, &ProjectDialog::_create_folder)); + project_path_container = memnew(VBoxContainer); + vb->add_child(project_path_container); - path_container = memnew(VBoxContainer); - vb->add_child(path_container); + HBoxContainer *pphb_label = memnew(HBoxContainer); + project_path_container->add_child(pphb_label); l = memnew(Label); l->set_text(TTR("Project Path:")); - path_container->add_child(l); + l->set_h_size_flags(Control::SIZE_EXPAND_FILL); + pphb_label->add_child(l); + + create_dir = memnew(CheckButton); + create_dir->set_text(TTR("Create Folder")); + create_dir->set_pressed(true); + pphb_label->add_child(create_dir); + create_dir->connect("toggled", callable_mp(this, &ProjectDialog::_create_dir_toggled)); HBoxContainer *pphb = memnew(HBoxContainer); - path_container->add_child(pphb); + project_path_container->add_child(pphb); project_path = memnew(LineEdit); project_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -841,14 +839,14 @@ ProjectDialog::ProjectDialog() { iphb->add_child(install_path); // status icon - status_rect = memnew(TextureRect); - status_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); - pphb->add_child(status_rect); + project_status_rect = memnew(TextureRect); + project_status_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + pphb->add_child(project_status_rect); - browse = memnew(Button); - browse->set_text(TTR("Browse")); - browse->connect("pressed", callable_mp(this, &ProjectDialog::_browse_path)); - pphb->add_child(browse); + project_browse = memnew(Button); + project_browse->set_text(TTR("Browse")); + project_browse->connect("pressed", callable_mp(this, &ProjectDialog::_browse_project_path)); + pphb->add_child(project_browse); // install status icon install_status_rect = memnew(TextureRect); @@ -863,6 +861,7 @@ ProjectDialog::ProjectDialog() { msg = memnew(Label); msg->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); msg->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + msg->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); vb->add_child(msg); // Renderer selection. @@ -957,20 +956,20 @@ ProjectDialog::ProjectDialog() { spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); default_files_container->add_child(spacer); - fdialog = memnew(EditorFileDialog); - fdialog->set_previews_enabled(false); //Crucial, otherwise the engine crashes. - fdialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + fdialog_project = memnew(EditorFileDialog); + fdialog_project->set_previews_enabled(false); //Crucial, otherwise the engine crashes. + fdialog_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); fdialog_install = memnew(EditorFileDialog); fdialog_install->set_previews_enabled(false); //Crucial, otherwise the engine crashes. fdialog_install->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - add_child(fdialog); + add_child(fdialog_project); add_child(fdialog_install); - project_name->connect("text_changed", callable_mp(this, &ProjectDialog::_text_changed)); - project_path->connect("text_changed", callable_mp(this, &ProjectDialog::_path_text_changed)); - install_path->connect("text_changed", callable_mp(this, &ProjectDialog::_update_path)); - fdialog->connect("dir_selected", callable_mp(this, &ProjectDialog::_path_selected)); - fdialog->connect("file_selected", callable_mp(this, &ProjectDialog::_file_selected)); + project_name->connect("text_changed", callable_mp(this, &ProjectDialog::_project_name_changed).unbind(1)); + project_path->connect("text_changed", callable_mp(this, &ProjectDialog::_project_path_changed).unbind(1)); + install_path->connect("text_changed", callable_mp(this, &ProjectDialog::_install_path_changed).unbind(1)); + fdialog_project->connect("dir_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); + fdialog_project->connect("file_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); fdialog_install->connect("dir_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); diff --git a/editor/project_manager/project_dialog.h b/editor/project_manager/project_dialog.h index dcc5cf71f8..1418edc57f 100644 --- a/editor/project_manager/project_dialog.h +++ b/editor/project_manager/project_dialog.h @@ -34,6 +34,7 @@ #include "scene/gui/dialogs.h" class Button; +class CheckButton; class EditorFileDialog; class LineEdit; class OptionButton; @@ -65,11 +66,11 @@ private: Mode mode = MODE_NEW; bool is_folder_empty = true; - Button *browse = nullptr; + CheckButton *create_dir = nullptr; + Button *project_browse = nullptr; Button *install_browse = nullptr; - Button *create_dir = nullptr; VBoxContainer *name_container = nullptr; - VBoxContainer *path_container = nullptr; + VBoxContainer *project_path_container = nullptr; VBoxContainer *install_path_container = nullptr; VBoxContainer *renderer_container = nullptr; @@ -78,54 +79,63 @@ private: Ref<ButtonGroup> renderer_button_group; Label *msg = nullptr; - LineEdit *project_path = nullptr; LineEdit *project_name = nullptr; + LineEdit *project_path = nullptr; LineEdit *install_path = nullptr; - TextureRect *status_rect = nullptr; + TextureRect *project_status_rect = nullptr; TextureRect *install_status_rect = nullptr; OptionButton *vcs_metadata_selection = nullptr; - EditorFileDialog *fdialog = nullptr; + EditorFileDialog *fdialog_project = nullptr; EditorFileDialog *fdialog_install = nullptr; AcceptDialog *dialog_error = nullptr; String zip_path; String zip_title; - String fav_dir; - String created_folder_path; + void _set_message(const String &p_msg, MessageType p_type, InputType input_type = PROJECT_PATH); + void _validate_path(); - void _set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS, InputType input_type = PROJECT_PATH); + // Project path for MODE_NEW and MODE_INSTALL. Install path for MODE_IMPORT. + // Install path is only visible when importing a ZIP. + String _get_target_path(); + void _set_target_path(const String &p_text); - String _test_path(); - void _update_path(const String &p_path); - void _path_text_changed(const String &p_path); - void _path_selected(const String &p_path); - void _file_selected(const String &p_path); - void _install_path_selected(const String &p_path); + // Calculated from project name / ZIP name. + String auto_dir; + + // Updates `auto_dir`. If the target path dir name is equal to `auto_dir` (the default state), the target path is also updated. + void _update_target_auto_dir(); - void _browse_path(); + // While `create_dir` is disabled, stores the last target path dir name, or an empty string if equal to `auto_dir`. + String last_custom_target_dir; + void _create_dir_toggled(bool p_pressed); + + void _project_name_changed(); + void _project_path_changed(); + void _install_path_changed(); + + void _browse_project_path(); void _browse_install_path(); - void _create_folder(); - void _text_changed(const String &p_text); - void _nonempty_confirmation_ok_pressed(); + void _project_path_selected(const String &p_path); + void _install_path_selected(const String &p_path); + void _renderer_selected(); - void _remove_created_folder(); + void _nonempty_confirmation_ok_pressed(); void ok_pressed() override; - void cancel_pressed() override; protected: - void _notification(int p_what); static void _bind_methods(); public: - void set_zip_path(const String &p_path); - void set_zip_title(const String &p_title); void set_mode(Mode p_mode); + void set_project_name(const String &p_name); void set_project_path(const String &p_path); + void set_zip_path(const String &p_path); + void set_zip_title(const String &p_title); void ask_for_path_and_show(); void show_dialog(); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 6ca128138e..cd043e165e 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -146,7 +146,7 @@ void ScriptCreateDialog::_notification(int p_what) { void ScriptCreateDialog::_path_hbox_sorted() { if (is_visible()) { int filename_start_pos = file_path->get_text().rfind("/") + 1; - int filename_end_pos = file_path->get_text().length(); + int filename_end_pos = file_path->get_text().get_basename().length(); if (!is_built_in) { file_path->select(filename_start_pos, filename_end_pos); diff --git a/editor/template_builders.py b/editor/template_builders.py index efed567d46..d5932a08fe 100644 --- a/editor/template_builders.py +++ b/editor/template_builders.py @@ -85,7 +85,7 @@ def make_templates(target, source, env): s.write("\n#endif\n") - with open(dst, "w") as f: + with open(dst, "w", encoding="utf-8", newline="\n") as f: f.write(s.getvalue()) s.close() diff --git a/editor/themes/editor_theme_builders.py b/editor/themes/editor_theme_builders.py index 19b346db58..b503c37c4b 100644 --- a/editor/themes/editor_theme_builders.py +++ b/editor/themes/editor_theme_builders.py @@ -12,7 +12,7 @@ from platform_methods import subprocess_main def make_fonts_header(target, source, env): dst = target[0] - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef _EDITOR_FONTS_H\n") diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 6849d87923..8aa2fe41ce 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1852,6 +1852,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme Ref<StyleBoxFlat> editor_log_button_pressed = style_flat_button_pressed->duplicate(); editor_log_button_pressed->set_border_width(SIDE_BOTTOM, 2 * EDSCALE); editor_log_button_pressed->set_border_color(p_config.accent_color); + + p_theme->set_stylebox("normal", "EditorLogFilterButton", style_flat_button); + p_theme->set_stylebox("hover", "EditorLogFilterButton", style_flat_button_hover); p_theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed); } diff --git a/gles3_builders.py b/gles3_builders.py index f1cdf7ac8c..9280d3f0dd 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -211,7 +211,7 @@ def build_gles3_header( else: out_file = optional_output_filename - fd = open(out_file, "w") + fd = open(out_file, "w", encoding="utf-8", newline="\n") defspec = 0 defvariant = "" diff --git a/glsl_builders.py b/glsl_builders.py index ce52ec247d..406677ac9e 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -165,7 +165,7 @@ public: #endif """ - with open(out_file, "w") as fd: + with open(out_file, "w", encoding="utf-8", newline="\n") as fd: fd.write(shader_template) @@ -224,7 +224,7 @@ static const char {out_file_base}[] = {{ #endif """ - with open(out_file, "w") as f: + with open(out_file, "w", encoding="utf-8", newline="\n") as f: f.write(shader_template) diff --git a/main/main_builders.py b/main/main_builders.py index 59d9d7dc3d..69277e49df 100644 --- a/main/main_builders.py +++ b/main/main_builders.py @@ -14,7 +14,7 @@ def make_splash(target, source, env): with open(src, "rb") as f: buf = f.read() - with open(dst, "w") as g: + with open(dst, "w", encoding="utf-8", newline="\n") as g: g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef BOOT_SPLASH_H\n") g.write("#define BOOT_SPLASH_H\n") @@ -34,7 +34,7 @@ def make_splash_editor(target, source, env): with open(src, "rb") as f: buf = f.read() - with open(dst, "w") as g: + with open(dst, "w", encoding="utf-8", newline="\n") as g: g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef BOOT_SPLASH_EDITOR_H\n") g.write("#define BOOT_SPLASH_EDITOR_H\n") @@ -55,7 +55,7 @@ def make_app_icon(target, source, env): with open(src, "rb") as f: buf = f.read() - with open(dst, "w") as g: + with open(dst, "w", encoding="utf-8", newline="\n") as g: g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef APP_ICON_H\n") g.write("#define APP_ICON_H\n") diff --git a/methods.py b/methods.py index 7e889195c0..81eda7a3f1 100644 --- a/methods.py +++ b/methods.py @@ -230,7 +230,7 @@ def generate_version_header(module_version_string=""): # NOTE: It is safe to generate these files here, since this is still executed serially. - f = open("core/version_generated.gen.h", "w") + f = open("core/version_generated.gen.h", "w", encoding="utf-8", newline="\n") f.write( """/* THIS FILE IS GENERATED DO NOT EDIT */ #ifndef VERSION_GENERATED_GEN_H @@ -253,7 +253,7 @@ def generate_version_header(module_version_string=""): ) f.close() - fhash = open("core/version_hash.gen.cpp", "w") + fhash = open("core/version_hash.gen.cpp", "w", encoding="utf-8", newline="\n") fhash.write( """/* THIS FILE IS GENERATED DO NOT EDIT */ #include "core/version.h" @@ -384,7 +384,7 @@ def is_module(path): def write_disabled_classes(class_list): - f = open("core/disabled_classes.gen.h", "w") + f = open("core/disabled_classes.gen.h", "w", encoding="utf-8", newline="\n") f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") f.write("#ifndef DISABLED_CLASSES_GEN_H\n") f.write("#define DISABLED_CLASSES_GEN_H\n\n") @@ -435,7 +435,7 @@ void uninitialize_modules(ModuleInitializationLevel p_level) { ) # NOTE: It is safe to generate this file here, since this is still executed serially - with open("modules/register_module_types.gen.cpp", "w") as f: + with open("modules/register_module_types.gen.cpp", "w", encoding="utf-8", newline="\n") as f: f.write(modules_cpp) @@ -757,7 +757,7 @@ def generate_cpp_hint_file(filename): pass else: try: - with open(filename, "w") as fd: + with open(filename, "w", encoding="utf-8", newline="\n") as fd: fd.write("#define GDCLASS(m_class, m_inherits)\n") except OSError: print("Could not write cpp.hint file.") @@ -1062,7 +1062,7 @@ def show_progress(env): def progress_finish(target, source, env): nonlocal node_count, progressor try: - with open(node_count_fname, "w") as f: + with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f: f.write("%d\n" % node_count) progressor.delete(progressor.file_list()) except Exception: @@ -1092,7 +1092,7 @@ def dump(env): def non_serializable(obj): return "<<non-serializable: %s>>" % (type(obj).__qualname__) - with open(".scons_env.json", "w") as f: + with open(".scons_env.json", "w", encoding="utf-8", newline="\n") as f: dump(env.Dictionary(), f, indent=4, default=non_serializable) @@ -1294,7 +1294,7 @@ def generate_vs_project(env, original_args, project_name="godot"): filters_template = filters_template.replace("%%HASH%%", md5) - with open(f"{project_name}.vcxproj.filters", "w") as f: + with open(f"{project_name}.vcxproj.filters", "w", encoding="utf-8", newline="\n") as f: f.write(filters_template) envsources = [] @@ -1469,7 +1469,9 @@ def generate_vs_project(env, original_args, project_name="godot"): cmd = " ^& ".join(common_build_prefix + [" ".join([commands] + cmd_clean)]) props_template = props_template.replace("%%CLEAN%%", cmd) - with open(f"{project_name}.{platform}.{target}.{arch}.generated.props", "w") as f: + with open( + f"{project_name}.{platform}.{target}.{arch}.generated.props", "w", encoding="utf-8", newline="\n" + ) as f: f.write(props_template) proj_uuid = str(uuid.uuid4()) @@ -1572,7 +1574,7 @@ def generate_vs_project(env, original_args, project_name="godot"): proj_template = proj_template.replace("%%DEFAULT_ITEMS%%", "\n ".join(all_items)) proj_template = proj_template.replace("%%PROPERTIES%%", "\n ".join(properties)) - with open(f"{project_name}.vcxproj", "w") as f: + with open(f"{project_name}.vcxproj", "w", encoding="utf-8", newline="\n") as f: f.write(proj_template) if not get_bool(original_args, "vsproj_props_only", False): @@ -1583,7 +1585,7 @@ def generate_vs_project(env, original_args, project_name="godot"): sln_template = sln_template.replace("%%SECTION1%%", "\n ".join(section1)) sln_template = sln_template.replace("%%SECTION2%%", "\n ".join(section2)) - with open(f"{project_name}.sln", "w") as f: + with open(f"{project_name}.sln", "w", encoding="utf-8", newline="\n") as f: f.write(sln_template) if get_bool(original_args, "vsproj_gen_only", True): diff --git a/misc/scripts/copyright_headers.py b/misc/scripts/copyright_headers.py index a5e2f0c05d..8fb793976c 100755 --- a/misc/scripts/copyright_headers.py +++ b/misc/scripts/copyright_headers.py @@ -90,6 +90,6 @@ while line != "": # Dump everything until EOF fileread.close() # Write -filewrite = open(fname.strip(), "w") +filewrite = open(fname.strip(), "w", encoding="utf-8", newline="\n") filewrite.write(text) filewrite.close() diff --git a/misc/scripts/dotnet_format.py b/misc/scripts/dotnet_format.py index de414bbe30..83265be7c5 100644 --- a/misc/scripts/dotnet_format.py +++ b/misc/scripts/dotnet_format.py @@ -10,7 +10,7 @@ for path in [ "modules/mono/SdkPackageVersions.props", ]: os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "w") as f: + with open(path, "w", encoding="utf-8", newline="\n") as f: f.write("<Project />") # Avoid importing GeneratedIncludes.props. diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index a68d65e8d3..d98580b771 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -95,7 +95,7 @@ print(get_stack()) [/codeblock] Starting from [code]_ready()[/code], [code]bar()[/code] would print: - [codeblock] + [codeblock lang=text] [{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}] [/codeblock] [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server. @@ -116,7 +116,7 @@ print(d.values()) [/codeblock] Prints out: - [codeblock] + [codeblock lang=text] [@subpath, @path, foo] [, res://test.gd, bar] [/codeblock] @@ -190,7 +190,7 @@ <description> Like [method @GlobalScope.print], but includes the current stack frame when running with the debugger turned on. The output in the console may look like the following: - [codeblock] + [codeblock lang=text] Test print At: res://test.gd:15:_process() [/codeblock] @@ -202,7 +202,7 @@ <description> Prints a stack trace at the current code location. See also [method get_stack]. The output in the console may look like the following: - [codeblock] + [codeblock lang=text] Frame 0 - res://test.gd:16 in function '_process' [/codeblock] [b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server. @@ -232,7 +232,7 @@ print(array[i]) [/codeblock] Output: - [codeblock] + [codeblock lang=text] 9 6 3 @@ -243,7 +243,7 @@ print(i / 10.0) [/codeblock] Output: - [codeblock] + [codeblock lang=text] 0.3 0.2 0.1 diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index aae45274e0..49341cb670 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3460,6 +3460,7 @@ enum DocLineState { DOC_LINE_NORMAL, DOC_LINE_IN_CODE, DOC_LINE_IN_CODEBLOCK, + DOC_LINE_IN_KBD, }; static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) { @@ -3505,21 +3506,23 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons from = rb_pos + 1; String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1); - if (tag == "code") { + if (tag == "code" || tag.begins_with("code ")) { r_state = DOC_LINE_IN_CODE; - } else if (tag == "codeblock") { + } else if (tag == "codeblock" || tag.begins_with("codeblock ")) { if (lb_pos == 0) { line_join = "\n"; } else { result += line.substr(buffer_start, lb_pos - buffer_start) + '\n'; } - result += "[codeblock]"; + result += "[" + tag + "]"; if (from < len) { result += '\n'; } r_state = DOC_LINE_IN_CODEBLOCK; buffer_start = from; + } else if (tag == "kbd") { + r_state = DOC_LINE_IN_KBD; } } break; case DOC_LINE_IN_CODE: { @@ -3529,7 +3532,7 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons break; } - from = pos + 7; + from = pos + 7; // `len("[/code]")`. r_state = DOC_LINE_NORMAL; } break; @@ -3540,7 +3543,7 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons break; } - from = pos + 12; + from = pos + 12; // `len("[/codeblock]")`. if (pos == 0) { line_join = "\n"; @@ -3555,6 +3558,17 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons r_state = DOC_LINE_NORMAL; buffer_start = from; } break; + case DOC_LINE_IN_KBD: { + int pos = line.find("[/kbd]", from); + if (pos < 0) { + process = false; + break; + } + + from = pos + 6; // `len("[/kbd]")`. + + r_state = DOC_LINE_NORMAL; + } break; } } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 8489fc08c1..03d830741b 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -105,7 +105,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { Error GDScriptLanguageProtocol::LSPeer::send_data() { int sent = 0; - if (!res_queue.is_empty()) { + while (!res_queue.is_empty()) { CharString c_res = res_queue[0]; if (res_sent < c_res.size()) { Error err = connection->put_partial_data((const uint8_t *)c_res.get_data() + res_sent, c_res.size() - res_sent - 1, sent); @@ -229,7 +229,9 @@ void GDScriptLanguageProtocol::initialized(const Variant &p_params) { notify_client("gdscript/capabilities", capabilities.to_json()); } -void GDScriptLanguageProtocol::poll() { +void GDScriptLanguageProtocol::poll(int p_limit_usec) { + uint64_t target_ticks = OS::get_singleton()->get_ticks_usec() + p_limit_usec; + if (server->is_connection_available()) { on_client_connected(); } @@ -244,16 +246,22 @@ void GDScriptLanguageProtocol::poll() { E = clients.begin(); continue; } else { - if (peer->connection->get_available_bytes() > 0) { + Error err = OK; + while (peer->connection->get_available_bytes() > 0) { latest_client_id = E->key; - Error err = peer->handle_data(); - if (err != OK && err != ERR_BUSY) { - on_client_disconnected(E->key); - E = clients.begin(); - continue; + err = peer->handle_data(); + if (err != OK || OS::get_singleton()->get_ticks_usec() >= target_ticks) { + break; } } - Error err = peer->send_data(); + + if (err != OK && err != ERR_BUSY) { + on_client_disconnected(E->key); + E = clients.begin(); + continue; + } + + err = peer->send_data(); if (err != OK && err != ERR_BUSY) { on_client_disconnected(E->key); E = clients.begin(); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index a4d9dc6b1d..f29abaa337 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -105,7 +105,7 @@ public: _FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; } _FORCE_INLINE_ bool is_initialized() const { return _initialized; } - void poll(); + void poll(int p_limit_usec); Error start(int p_port, const IPAddress &p_bind_ip); void stop(); diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 9ba41352f2..3df26ea576 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -44,6 +44,7 @@ GDScriptLanguageServer::GDScriptLanguageServer() { _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); _EDITOR_DEF("network/language_server/use_thread", use_thread); + _EDITOR_DEF("network/language_server/poll_limit_usec", poll_limit_usec); } void GDScriptLanguageServer::_notification(int p_what) { @@ -58,7 +59,7 @@ void GDScriptLanguageServer::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { if (started && !use_thread) { - protocol.poll(); + protocol.poll(poll_limit_usec); } } break; @@ -70,7 +71,8 @@ void GDScriptLanguageServer::_notification(int p_what) { String remote_host = String(_EDITOR_GET("network/language_server/remote_host")); int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (remote_host != host || remote_port != port || remote_use_thread != use_thread) { + int remote_poll_limit = (int)_EDITOR_GET("network/language_server/poll_limit_usec"); + if (remote_host != host || remote_port != port || remote_use_thread != use_thread || remote_poll_limit != poll_limit_usec) { stop(); start(); } @@ -83,7 +85,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata); while (self->thread_running) { // Poll 20 times per second - self->protocol.poll(); + self->protocol.poll(self->poll_limit_usec); OS::get_singleton()->delay_usec(50000); } } @@ -92,6 +94,7 @@ void GDScriptLanguageServer::start() { host = String(_EDITOR_GET("network/language_server/remote_host")); port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); + poll_limit_usec = (int)_EDITOR_GET("network/language_server/poll_limit_usec"); if (protocol.start(port, IPAddress(host)) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index e845d139bf..2ace5ca446 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -47,6 +47,7 @@ class GDScriptLanguageServer : public EditorPlugin { bool use_thread = false; String host = "127.0.0.1"; int port = 6005; + int poll_limit_usec = 100000; static void thread_main(void *p_userdata); private: diff --git a/modules/modules_builders.py b/modules/modules_builders.py index 13d5a2075a..20eea9d993 100644 --- a/modules/modules_builders.py +++ b/modules/modules_builders.py @@ -7,7 +7,7 @@ from platform_methods import subprocess_main def generate_modules_enabled(target, source, env): - with open(target[0].path, "w") as f: + with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: for module in env.module_list: f.write("#define %s\n" % ("MODULE_" + module.upper() + "_ENABLED")) @@ -15,7 +15,7 @@ def generate_modules_enabled(target, source, env): def generate_modules_tests(target, source, env): import os - with open(target[0].path, "w") as f: + with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: for header in source: f.write('#include "%s"\n' % (os.path.normpath(header.path))) diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index aa6f6ef05e..9ed87c7a8c 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -312,7 +312,7 @@ def generate_sdk_package_versions(): ) # We write in ../SdkPackageVersions.props. - with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8") as f: + with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8", newline="\n") as f: f.write(props) f.close() @@ -340,7 +340,7 @@ def generate_sdk_package_versions(): ) os.makedirs(generators_dir, exist_ok=True) - with open(os.path.join(generators_dir, "Common.Constants.cs"), "w", newline="\n", encoding="utf-8") as f: + with open(os.path.join(generators_dir, "Common.Constants.cs"), "w", encoding="utf-8", newline="\n") as f: f.write(constants) f.close() diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 5cb177676c..eb45ade285 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -125,6 +125,20 @@ const Vector<String> ignored_types = {}; // Don't check against all C# reserved words, as many cases are GDScript-specific. const Vector<String> langword_check = { "true", "false", "null" }; +// The following properties currently need to be defined with `new` to avoid warnings. We treat +// them as a special case instead of silencing the warnings altogether, to be warned if more +// shadowing appears. +const Vector<String> prop_allowed_inherited_member_hiding = { + "ArrayMesh.BlendShapeMode", + "Button.TextDirection", + "Label.TextDirection", + "LineEdit.TextDirection", + "LinkButton.TextDirection", + "MenuBar.TextDirection", + "RichTextLabel.TextDirection", + "TextEdit.TextDirection", +}; + void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) { // C interface for enums is the same as that of 'uint32_t'. Remember to apply // any of the changes done here to the 'uint32_t' type interface as well. @@ -2569,6 +2583,10 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(MEMBER_BEGIN "public "); + if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) { + p_output.append("new "); + } + if (p_itype.is_singleton) { p_output.append("static "); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs index 036a26328a..c2d3050adc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs @@ -6,7 +6,10 @@ namespace Godot; #nullable enable +// TODO: Disabled because it is a false positive, see https://github.com/dotnet/roslyn-analyzers/issues/6151 +#pragma warning disable CA1001 // Types that own disposable fields should be disposable public partial struct Variant : IDisposable +#pragma warning restore CA1001 { internal godot_variant.movable NativeVar; private object? _obj; diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index 527e02f855..0e62824adc 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -96,7 +96,7 @@ for f in all_files: os.makedirs(d) shutil.copy2(f, d) -with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: +with open(os.path.join(dest_dir, "kernels/hash.h"), "w", encoding="utf-8", newline="\n") as hash_file: hash_file.write( f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 @@ -105,7 +105,7 @@ with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: """ ) -with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: +with open(os.path.join(dest_dir, "kernels/config.h"), "w", encoding="utf-8", newline="\n") as config_file: config_file.write( """// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 @@ -193,7 +193,9 @@ with open("CMakeLists.txt", "r") as cmake_file: minor_version = int(re.compile(r"EMBREE_VERSION_MINOR\s(\d+)").findall(cmake_content)[0]) patch_version = int(re.compile(r"EMBREE_VERSION_PATCH\s(\d+)").findall(cmake_content)[0]) -with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as config_file: +with open( + os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w", encoding="utf-8", newline="\n" +) as config_file: config_file.write( f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 09d9b2dd67..79950eaac3 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -9,7 +9,7 @@ env_text_server_adv = env_modules.Clone() def make_icu_data(target, source, env): dst = target[0].srcnode().abspath - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n") diff --git a/modules/text_server_adv/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py index 327097a3df..8456149973 100644 --- a/modules/text_server_adv/gdextension_build/methods.py +++ b/modules/text_server_adv/gdextension_build/methods.py @@ -83,7 +83,7 @@ def disable_warnings(self): def make_icu_data(target, source, env): dst = target[0].srcnode().abspath - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n") @@ -108,7 +108,7 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) - f = open(f"{target}/Resource/Info.plist", "w") + f = open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n') f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n') diff --git a/modules/text_server_fb/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py index 327097a3df..8456149973 100644 --- a/modules/text_server_fb/gdextension_build/methods.py +++ b/modules/text_server_fb/gdextension_build/methods.py @@ -83,7 +83,7 @@ def disable_warnings(self): def make_icu_data(target, source, env): dst = target[0].srcnode().abspath - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n") @@ -108,7 +108,7 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) - f = open(f"{target}/Resource/Info.plist", "w") + f = open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") f.write(f'<?xml version="1.0" encoding="UTF-8"?>\n') f.write(f'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n') diff --git a/platform/SCsub b/platform/SCsub index 5194a19518..e432cebd48 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -19,7 +19,7 @@ reg_apis += "}\n\n" unreg_apis += "}\n" # NOTE: It is safe to generate this file here, since this is still execute serially -with open("register_platform_apis.gen.cpp", "w", encoding="utf-8") as f: +with open("register_platform_apis.gen.cpp", "w", encoding="utf-8", newline="\n") as f: f.write(reg_apis_inc) f.write(reg_apis) f.write(unreg_apis) diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 59c17b5816..80b6029c9d 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -585,9 +585,9 @@ void DisplayServerWayland::screen_set_keep_on(bool p_enable) { bool DisplayServerWayland::screen_is_kept_on() const { #ifdef DBUS_ENABLED return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID) || screensaver_inhibited; -#endif - +#else return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID); +#endif } Vector<DisplayServer::WindowID> DisplayServerWayland::get_window_list() const { diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 8167642345..0216ad862c 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -1231,8 +1231,6 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat // Pointer handling. if (capabilities & WL_SEAT_CAPABILITY_POINTER) { ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor); - ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface); - wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss); wl_surface_commit(ss->cursor_surface); ss->wl_pointer = wl_seat_get_pointer(wl_seat); @@ -1313,11 +1311,9 @@ void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callbac SeatState *ss = (SeatState *)data; ERR_FAIL_NULL(ss); - ss->cursor_time_ms = time_ms; + ss->cursor_frame_callback = nullptr; - ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface); - wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss); - wl_surface_commit(ss->cursor_surface); + ss->cursor_time_ms = time_ms; seat_state_update_cursor(ss); } @@ -2192,9 +2188,14 @@ void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ts || !ss) { + if (!ss) { return; } @@ -2214,9 +2215,14 @@ void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_too void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ts || !ss) { + if (!ss) { return; } @@ -2237,9 +2243,14 @@ void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_table void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ts || !ss) { + if (!ss) { return; } @@ -2389,9 +2400,14 @@ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) { TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ts || !ss) { + if (!ss) { return; } @@ -2899,7 +2915,18 @@ void WaylandThread::seat_state_update_cursor(SeatState *p_ss) { // compositor do it for us (badly). scale = 1; } else if (wl_cursor) { - int frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms); + int frame_idx = 0; + + if (wl_cursor->image_count > 1) { + // The cursor is animated. + frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms); + + if (!p_ss->cursor_frame_callback) { + // Since it's animated, we'll re-update it the next frame. + p_ss->cursor_frame_callback = wl_surface_frame(p_ss->cursor_surface); + wl_callback_add_listener(p_ss->cursor_frame_callback, &cursor_frame_callback_listener, p_ss); + } + } struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx]; diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 9083c2a288..135779582d 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -36,7 +36,7 @@ def generate_bundle(target, source, env): version = get_build_version(False) short_version = get_build_version(True) with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin: - with open(app_dir + "/Contents/Info.plist", "wt") as fout: + with open(app_dir + "/Contents/Info.plist", "wt", encoding="utf-8", newline="\n") as fout: for line in fin: line = line.replace("$version", version) line = line.replace("$short_version", short_version) diff --git a/platform/macos/detect.py b/platform/macos/detect.py index ed9e59ae05..3c8b1ebee1 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -242,10 +242,17 @@ def configure(env: "SConsEnvironment"): env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"]) if not env["use_volk"]: env.Append(LINKFLAGS=["-lMoltenVK"]) - mvk_path = detect_mvk(env, "macos-arm64_x86_64") + + mvk_path = "" + arch_variants = ["macos-arm64_x86_64", "macos-" + env["arch"]] + for arch in arch_variants: + mvk_path = detect_mvk(env, arch) + if mvk_path != "": + mvk_path = os.path.join(mvk_path, arch) + break if mvk_path != "": - env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")]) + env.Append(LINKFLAGS=["-L" + mvk_path]) else: print( "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path." diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 42f8b205c5..0884119278 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2573,7 +2573,7 @@ struct Win32InputTextDialogInit { const Callable &callback; }; -static constexpr int scale_with_dpi(int p_pos, int p_dpi) { +static int scale_with_dpi(int p_pos, int p_dpi) { return IsProcessDPIAware() ? (p_pos * p_dpi / 96) : p_pos; } diff --git a/platform_methods.py b/platform_methods.py index a05298bfa5..91c1388288 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -40,7 +40,7 @@ def run_in_subprocess(builder_function): args = (target, source, filtered_env) data = dict(fn=function_name, args=args) json_path = os.path.join(os.environ["TMP"], uuid.uuid4().hex + ".json") - with open(json_path, "wt") as json_file: + with open(json_path, "wt", encoding="utf-8", newline="\n") as json_file: json.dump(data, json_file, indent=2) json_file_size = os.stat(json_path).st_size @@ -138,7 +138,7 @@ def generate_export_icons(platform_path, platform_name): # NOTE: It is safe to generate this file here, since this is still executed serially. wf = export_path + "/" + name + "_svg.gen.h" - with open(wf, "w") as svgw: + with open(wf, "w", encoding="utf-8", newline="\n") as svgw: svgw.write(svg_str) diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 57732e95ca..7db2b7201b 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1453,6 +1453,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { t->value = Animation::blend_variant(t->value, value, blend); } } else { + t->use_discrete = true; if (seeked) { int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { @@ -1476,7 +1477,6 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } } - t->use_discrete = true; } } break; case Animation::TYPE_METHOD: { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1ee6a8dca7..c8d2d71c2a 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1068,32 +1068,6 @@ void Viewport::canvas_parent_mark_dirty(Node *p_node) { } } -void Viewport::_update_audio_listener_2d() { - if (AudioServer::get_singleton()) { - AudioServer::get_singleton()->notify_listener_changed(); - } -} - -void Viewport::set_as_audio_listener_2d(bool p_enable) { - ERR_MAIN_THREAD_GUARD; - if (p_enable == is_audio_listener_2d_enabled) { - return; - } - - is_audio_listener_2d_enabled = p_enable; - _update_audio_listener_2d(); -} - -bool Viewport::is_audio_listener_2d() const { - ERR_READ_THREAD_GUARD_V(false); - return is_audio_listener_2d_enabled; -} - -AudioListener2D *Viewport::get_audio_listener_2d() const { - ERR_READ_THREAD_GUARD_V(nullptr); - return audio_listener_2d; -} - void Viewport::enable_canvas_transform_override(bool p_enable) { ERR_MAIN_THREAD_GUARD; if (override_canvas_transform == p_enable) { @@ -1162,25 +1136,6 @@ Transform2D Viewport::get_global_canvas_transform() const { return global_canvas_transform; } -void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { - camera_2d = p_camera_2d; -} - -void Viewport::_audio_listener_2d_set(AudioListener2D *p_listener) { - if (audio_listener_2d == p_listener) { - return; - } else if (audio_listener_2d) { - audio_listener_2d->clear_current(); - } - audio_listener_2d = p_listener; -} - -void Viewport::_audio_listener_2d_remove(AudioListener2D *p_listener) { - if (audio_listener_2d == p_listener) { - audio_listener_2d = nullptr; - } -} - void Viewport::_canvas_layer_add(CanvasLayer *p_canvas_layer) { canvas_layers.insert(p_canvas_layer); } @@ -1272,40 +1227,11 @@ Ref<World2D> Viewport::get_world_2d() const { return world_2d; } -Camera2D *Viewport::get_camera_2d() const { - ERR_READ_THREAD_GUARD_V(nullptr); - return camera_2d; -} - Transform2D Viewport::get_final_transform() const { ERR_READ_THREAD_GUARD_V(Transform2D()); return stretch_transform * global_canvas_transform; } -void Viewport::assign_next_enabled_camera_2d(const StringName &p_camera_group) { - ERR_MAIN_THREAD_GUARD; - List<Node *> camera_list; - get_tree()->get_nodes_in_group(p_camera_group, &camera_list); - - Camera2D *new_camera = nullptr; - for (Node *E : camera_list) { - Camera2D *cam = Object::cast_to<Camera2D>(E); - if (!cam) { - continue; // Non-camera node (e.g. ParallaxBackground). - } - - if (cam->is_enabled()) { - new_camera = cam; - break; - } - } - - _camera_2d_set(new_camera); - if (!camera_2d) { - set_canvas_transform(Transform2D()); - } -} - void Viewport::_update_canvas_items(Node *p_node) { if (p_node != this) { Window *w = Object::cast_to<Window>(p_node); @@ -2644,76 +2570,6 @@ void Viewport::_drop_physics_mouseover(bool p_paused_only) { #endif // _3D_DISABLED } -void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference) { - List<ObjectID> to_erase; - List<ObjectID> to_mouse_exit; - - for (const KeyValue<ObjectID, uint64_t> &E : physics_2d_mouseover) { - if (!p_clean_all_frames && E.value == p_frame_reference) { - continue; - } - - Object *o = ObjectDB::get_instance(E.key); - if (o) { - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - if (co && co->is_inside_tree()) { - if (p_clean_all_frames && p_paused_only && co->can_process()) { - continue; - } - to_mouse_exit.push_back(E.key); - } - } - to_erase.push_back(E.key); - } - - while (to_erase.size()) { - physics_2d_mouseover.erase(to_erase.front()->get()); - to_erase.pop_front(); - } - - // Per-shape. - List<Pair<ObjectID, int>> shapes_to_erase; - List<Pair<ObjectID, int>> shapes_to_mouse_exit; - - for (KeyValue<Pair<ObjectID, int>, uint64_t> &E : physics_2d_shape_mouseover) { - if (!p_clean_all_frames && E.value == p_frame_reference) { - continue; - } - - Object *o = ObjectDB::get_instance(E.key.first); - if (o) { - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - if (co && co->is_inside_tree()) { - if (p_clean_all_frames && p_paused_only && co->can_process()) { - continue; - } - shapes_to_mouse_exit.push_back(E.key); - } - } - shapes_to_erase.push_back(E.key); - } - - while (shapes_to_erase.size()) { - physics_2d_shape_mouseover.erase(shapes_to_erase.front()->get()); - shapes_to_erase.pop_front(); - } - - while (to_mouse_exit.size()) { - Object *o = ObjectDB::get_instance(to_mouse_exit.front()->get()); - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - co->_mouse_exit(); - to_mouse_exit.pop_front(); - } - - while (shapes_to_mouse_exit.size()) { - Pair<ObjectID, int> e = shapes_to_mouse_exit.front()->get(); - Object *o = ObjectDB::get_instance(e.first); - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - co->_mouse_shape_exit(e.second); - shapes_to_mouse_exit.pop_front(); - } -} - void Viewport::_gui_grab_click_focus(Control *p_control) { gui.mouse_click_grabber = p_control; callable_mp(this, &Viewport::_post_gui_grab_click_focus).call_deferred(); @@ -4065,6 +3921,150 @@ bool Viewport::get_canvas_cull_mask_bit(uint32_t p_layer) const { return (canvas_cull_mask & (1 << p_layer)); } +void Viewport::_update_audio_listener_2d() { + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } +} + +void Viewport::_audio_listener_2d_set(AudioListener2D *p_audio_listener) { + if (audio_listener_2d == p_audio_listener) { + return; + } else if (audio_listener_2d) { + audio_listener_2d->clear_current(); + } + audio_listener_2d = p_audio_listener; +} + +void Viewport::_audio_listener_2d_remove(AudioListener2D *p_audio_listener) { + if (audio_listener_2d == p_audio_listener) { + audio_listener_2d = nullptr; + } +} + +void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { + camera_2d = p_camera_2d; +} + +void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference) { + List<ObjectID> to_erase; + List<ObjectID> to_mouse_exit; + + for (const KeyValue<ObjectID, uint64_t> &E : physics_2d_mouseover) { + if (!p_clean_all_frames && E.value == p_frame_reference) { + continue; + } + + Object *o = ObjectDB::get_instance(E.key); + if (o) { + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + if (co && co->is_inside_tree()) { + if (p_clean_all_frames && p_paused_only && co->can_process()) { + continue; + } + to_mouse_exit.push_back(E.key); + } + } + to_erase.push_back(E.key); + } + + while (to_erase.size()) { + physics_2d_mouseover.erase(to_erase.front()->get()); + to_erase.pop_front(); + } + + // Per-shape. + List<Pair<ObjectID, int>> shapes_to_erase; + List<Pair<ObjectID, int>> shapes_to_mouse_exit; + + for (KeyValue<Pair<ObjectID, int>, uint64_t> &E : physics_2d_shape_mouseover) { + if (!p_clean_all_frames && E.value == p_frame_reference) { + continue; + } + + Object *o = ObjectDB::get_instance(E.key.first); + if (o) { + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + if (co && co->is_inside_tree()) { + if (p_clean_all_frames && p_paused_only && co->can_process()) { + continue; + } + shapes_to_mouse_exit.push_back(E.key); + } + } + shapes_to_erase.push_back(E.key); + } + + while (shapes_to_erase.size()) { + physics_2d_shape_mouseover.erase(shapes_to_erase.front()->get()); + shapes_to_erase.pop_front(); + } + + while (to_mouse_exit.size()) { + Object *o = ObjectDB::get_instance(to_mouse_exit.front()->get()); + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + co->_mouse_exit(); + to_mouse_exit.pop_front(); + } + + while (shapes_to_mouse_exit.size()) { + Pair<ObjectID, int> e = shapes_to_mouse_exit.front()->get(); + Object *o = ObjectDB::get_instance(e.first); + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + co->_mouse_shape_exit(e.second); + shapes_to_mouse_exit.pop_front(); + } +} + +AudioListener2D *Viewport::get_audio_listener_2d() const { + ERR_READ_THREAD_GUARD_V(nullptr); + return audio_listener_2d; +} + +void Viewport::set_as_audio_listener_2d(bool p_enable) { + ERR_MAIN_THREAD_GUARD; + if (p_enable == is_audio_listener_2d_enabled) { + return; + } + + is_audio_listener_2d_enabled = p_enable; + _update_audio_listener_2d(); +} + +bool Viewport::is_audio_listener_2d() const { + ERR_READ_THREAD_GUARD_V(false); + return is_audio_listener_2d_enabled; +} + +Camera2D *Viewport::get_camera_2d() const { + ERR_READ_THREAD_GUARD_V(nullptr); + return camera_2d; +} + +void Viewport::assign_next_enabled_camera_2d(const StringName &p_camera_group) { + ERR_MAIN_THREAD_GUARD; + List<Node *> camera_list; + get_tree()->get_nodes_in_group(p_camera_group, &camera_list); + + Camera2D *new_camera = nullptr; + for (Node *E : camera_list) { + Camera2D *cam = Object::cast_to<Camera2D>(E); + if (!cam) { + continue; // Non-camera node (e.g. ParallaxBackground). + } + + if (cam->is_enabled()) { + new_camera = cam; + break; + } + } + + _camera_2d_set(new_camera); + if (!camera_2d) { + set_canvas_transform(Transform2D()); + } +} + #ifndef _3D_DISABLED AudioListener3D *Viewport::get_audio_listener_3d() const { ERR_READ_THREAD_GUARD_V(nullptr); @@ -4640,10 +4640,6 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false)); #endif // DISABLE_DEPRECATED - ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d); - ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); - ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d); - ClassDB::bind_method(D_METHOD("get_mouse_position"), &Viewport::get_mouse_position); ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Viewport::warp_mouse); ClassDB::bind_method(D_METHOD("update_mouse_cursor_state"), &Viewport::update_mouse_cursor_state); @@ -4712,6 +4708,10 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("_process_picking"), &Viewport::_process_picking); + ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); + ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d); + ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d); + #ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("set_world_3d", "world_3d"), &Viewport::set_world_3d); ClassDB::bind_method(D_METHOD("get_world_3d"), &Viewport::get_world_3d); @@ -4786,7 +4786,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_2d"), "set_as_audio_listener_2d", "is_audio_listener_2d"); #ifndef _3D_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_3d"), "set_as_audio_listener_3d", "is_audio_listener_3d"); -#endif +#endif // _3D_DISABLED ADD_GROUP("Physics", "physics_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking"), "set_physics_object_picking", "get_physics_object_picking"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking_sort"), "set_physics_object_picking_sort", "get_physics_object_picking_sort"); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 2904e3e156..29ccdc5426 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -218,17 +218,12 @@ private: Viewport *parent = nullptr; Viewport *gui_parent = nullptr; // Whose gui.tooltip_popup it is. - AudioListener2D *audio_listener_2d = nullptr; - Camera2D *camera_2d = nullptr; HashSet<CanvasLayer *> canvas_layers; RID viewport; RID current_canvas; RID subwindow_canvas; - bool is_audio_listener_2d_enabled = false; - RID internal_audio_listener_2d; - bool override_canvas_transform = false; Transform2D canvas_transform_override; @@ -267,13 +262,6 @@ private: bool handle_input_locally = true; bool local_input_handled = false; - // Collider to frame - HashMap<ObjectID, uint64_t> physics_2d_mouseover; - // Collider & shape to frame - HashMap<Pair<ObjectID, int>, uint64_t, PairHash<ObjectID, int>> physics_2d_shape_mouseover; - // Cleans up colliders corresponding to old frames or all of them. - void _cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference = 0); - Ref<World2D> world_2d; StringName input_group; @@ -447,13 +435,6 @@ private: bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check); - friend class AudioListener2D; - void _audio_listener_2d_set(AudioListener2D *p_listener); - void _audio_listener_2d_remove(AudioListener2D *p_listener); - - friend class Camera2D; - void _camera_2d_set(Camera2D *p_camera_2d); - friend class CanvasLayer; void _canvas_layer_add(CanvasLayer *p_canvas_layer); void _canvas_layer_remove(CanvasLayer *p_canvas_layer); @@ -483,6 +464,7 @@ private: uint64_t event_count = 0; void _process_dirty_canvas_parent_orders(); + void _propagate_world_2d_changed(Node *p_node); protected: void _set_size(const Size2i &p_size, const Size2i &p_size_2d_override, bool p_allocated); @@ -494,6 +476,7 @@ protected: void _notification(int p_what); void _process_picking(); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; public: void canvas_parent_mark_dirty(Node *p_node); @@ -501,11 +484,6 @@ public: uint64_t get_processed_events_count() const { return event_count; } - AudioListener2D *get_audio_listener_2d() const; - Camera2D *get_camera_2d() const; - void set_as_audio_listener_2d(bool p_enable); - bool is_audio_listener_2d() const; - void update_canvas_items(); Rect2 get_visible_rect() const; @@ -528,7 +506,6 @@ public: Transform2D get_global_canvas_transform() const; virtual Transform2D get_final_transform() const; - void assign_next_enabled_camera_2d(const StringName &p_camera_group); void gui_set_root_order_dirty(); @@ -689,16 +666,43 @@ public: virtual bool is_attached_in_viewport() const { return false; }; virtual bool is_sub_viewport() const { return false; }; +private: + // 2D audio, camera, and physics. (don't put World2D here because World2D is needed for Control nodes). + friend class AudioListener2D; // Needs _audio_listener_2d_set and _audio_listener_2d_remove + AudioListener2D *audio_listener_2d = nullptr; + void _audio_listener_2d_set(AudioListener2D *p_audio_listener); + void _audio_listener_2d_remove(AudioListener2D *p_audio_listener); + bool is_audio_listener_2d_enabled = false; + RID internal_audio_listener_2d; + + friend class Camera2D; // Needs _camera_2d_set + Camera2D *camera_2d = nullptr; + void _camera_2d_set(Camera2D *p_camera_2d); + + // Collider to frame + HashMap<ObjectID, uint64_t> physics_2d_mouseover; + // Collider & shape to frame + HashMap<Pair<ObjectID, int>, uint64_t, PairHash<ObjectID, int>> physics_2d_shape_mouseover; + // Cleans up colliders corresponding to old frames or all of them. + void _cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference = 0); + +public: + AudioListener2D *get_audio_listener_2d() const; + void set_as_audio_listener_2d(bool p_enable); + bool is_audio_listener_2d() const; + + Camera2D *get_camera_2d() const; + void assign_next_enabled_camera_2d(const StringName &p_camera_group); + #ifndef _3D_DISABLED +private: + // 3D audio, camera, physics, and world. bool use_xr = false; friend class AudioListener3D; AudioListener3D *audio_listener_3d = nullptr; HashSet<AudioListener3D *> audio_listener_3d_set; bool is_audio_listener_3d_enabled = false; RID internal_audio_listener_3d; - AudioListener3D *get_audio_listener_3d() const; - void set_as_audio_listener_3d(bool p_enable); - bool is_audio_listener_3d() const; void _update_audio_listener_3d(); void _listener_transform_3d_changed_notify(); void _audio_listener_3d_set(AudioListener3D *p_listener); @@ -729,13 +733,24 @@ public: friend class Camera3D; Camera3D *camera_3d = nullptr; HashSet<Camera3D *> camera_3d_set; - Camera3D *get_camera_3d() const; void _camera_3d_transform_changed_notify(); void _camera_3d_set(Camera3D *p_camera); bool _camera_3d_add(Camera3D *p_camera); //true if first void _camera_3d_remove(Camera3D *p_camera); void _camera_3d_make_next_current(Camera3D *p_exclude); + Ref<World3D> world_3d; + Ref<World3D> own_world_3d; + void _own_world_3d_changed(); + void _propagate_enter_world_3d(Node *p_node); + void _propagate_exit_world_3d(Node *p_node); + +public: + AudioListener3D *get_audio_listener_3d() const; + void set_as_audio_listener_3d(bool p_enable); + bool is_audio_listener_3d() const; + + Camera3D *get_camera_3d() const; void enable_camera_3d_override(bool p_enable); bool is_camera_3d_override_enabled() const; @@ -748,24 +763,16 @@ public: void set_disable_3d(bool p_disable); bool is_3d_disabled() const; - Ref<World3D> world_3d; - Ref<World3D> own_world_3d; void set_world_3d(const Ref<World3D> &p_world_3d); Ref<World3D> get_world_3d() const; Ref<World3D> find_world_3d() const; - void _own_world_3d_changed(); void set_use_own_world_3d(bool p_use_own_world_3d); bool is_using_own_world_3d() const; - void _propagate_enter_world_3d(Node *p_node); - void _propagate_exit_world_3d(Node *p_node); void set_use_xr(bool p_use_xr); bool is_using_xr(); #endif // _3D_DISABLED - void _propagate_world_2d_changed(Node *p_node); - - void _validate_property(PropertyInfo &p_property) const; Viewport(); ~Viewport(); }; diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp index 090c81aefe..be58f1c1e1 100644 --- a/scene/property_utils.cpp +++ b/scene/property_utils.cpp @@ -73,15 +73,15 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const for (int i = 0; i < states_stack.size(); ++i) { const SceneState::PackState &ia = states_stack[i]; bool found = false; - Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found); - const Vector<String> &deferred_properties = ia.state->get_node_deferred_nodepath_properties(ia.node); + bool node_deferred = false; + Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found, node_deferred); if (found) { if (r_is_valid) { *r_is_valid = true; } // Replace properties stored as NodePaths with actual Nodes. // Otherwise, the property value would be considered as overridden. - if (deferred_properties.has(p_property)) { + if (node_deferred) { if (value_in_ancestor.get_type() == Variant::ARRAY) { Array paths = value_in_ancestor; @@ -103,7 +103,7 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const } // Save script for later bool has_script = false; - Variant script = ia.state->get_property_value(ia.node, SNAME("script"), has_script); + Variant script = ia.state->get_property_value(ia.node, SNAME("script"), has_script, node_deferred); if (has_script) { Ref<Script> scr = script; if (scr.is_valid()) { diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index b55afd2de5..c17241a436 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -299,6 +299,10 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; void _validate_property(PropertyInfo &p_property) const; +#ifdef TOOLS_ENABLED + virtual uint32_t hash_edited_version_for_preview() const override { return 0; } // Not using preview, so disable it for performance. +#endif + private: // --- TileSet data --- // Basic shape and layout. diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 2c92a3edb3..9fb91e9931 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -1310,29 +1310,31 @@ int SceneState::_find_base_scene_node_remap_key(int p_idx) const { return -1; } -Variant SceneState::get_property_value(int p_node, const StringName &p_property, bool &found) const { - found = false; +Variant SceneState::get_property_value(int p_node, const StringName &p_property, bool &r_found, bool &r_node_deferred) const { + r_found = false; + r_node_deferred = false; ERR_FAIL_COND_V(p_node < 0, Variant()); if (p_node < nodes.size()) { - //find in built-in nodes + // Find in built-in nodes. int pc = nodes[p_node].properties.size(); const StringName *namep = names.ptr(); const NodeData::Property *p = nodes[p_node].properties.ptr(); for (int i = 0; i < pc; i++) { if (p_property == namep[p[i].name & FLAG_PROP_NAME_MASK]) { - found = true; + r_found = true; + r_node_deferred = p[i].name & FLAG_PATH_PROPERTY_IS_NODE; return variants[p[i].value]; } } } - //property not found, try on instance - - if (base_scene_node_remap.has(p_node)) { - return get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found); + // Property not found, try on instance. + HashMap<int, int>::ConstIterator I = base_scene_node_remap.find(p_node); + if (I) { + return get_base_scene_state()->get_property_value(I->value, p_property, r_found, r_node_deferred); } return Variant(); @@ -1722,13 +1724,25 @@ StringName SceneState::get_node_property_name(int p_idx, int p_prop) const { Vector<String> SceneState::get_node_deferred_nodepath_properties(int p_idx) const { Vector<String> ret; - ERR_FAIL_INDEX_V(p_idx, nodes.size(), ret); - for (int i = 0; i < nodes[p_idx].properties.size(); i++) { - uint32_t idx = nodes[p_idx].properties[i].name; - if (idx & FLAG_PATH_PROPERTY_IS_NODE) { - ret.push_back(names[idx & FLAG_PROP_NAME_MASK]); + ERR_FAIL_COND_V(p_idx < 0, ret); + + if (p_idx < nodes.size()) { + // Find in built-in nodes. + for (int i = 0; i < nodes[p_idx].properties.size(); i++) { + uint32_t idx = nodes[p_idx].properties[i].name; + if (idx & FLAG_PATH_PROPERTY_IS_NODE) { + ret.push_back(names[idx & FLAG_PROP_NAME_MASK]); + } } + return ret; } + + // Property not found, try on instance. + HashMap<int, int>::ConstIterator I = base_scene_node_remap.find(p_idx); + if (I) { + return get_base_scene_state()->get_node_deferred_nodepath_properties(I->value); + } + return ret; } diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 304e401602..c46a4dd5fe 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -139,7 +139,7 @@ public: static Ref<Resource> get_remap_resource(const Ref<Resource> &p_resource, HashMap<Ref<Resource>, Ref<Resource>> &remap_cache, const Ref<Resource> &p_fallback, Node *p_for_scene); int find_node_by_path(const NodePath &p_node) const; - Variant get_property_value(int p_node, const StringName &p_property, bool &found) const; + Variant get_property_value(int p_node, const StringName &p_property, bool &r_found, bool &r_node_deferred) const; bool is_node_in_group(int p_node, const StringName &p_group) const; bool is_connection(int p_node, const StringName &p_signal, int p_to_node, const StringName &p_to_method) const; diff --git a/scene/theme/default_theme_builders.py b/scene/theme/default_theme_builders.py index b8bb579d1c..02466f52df 100644 --- a/scene/theme/default_theme_builders.py +++ b/scene/theme/default_theme_builders.py @@ -13,7 +13,7 @@ from platform_methods import subprocess_main def make_fonts_header(target, source, env): dst = target[0] - g = open(dst, "w", encoding="utf-8") + g = open(dst, "w", encoding="utf-8", newline="\n") g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") g.write("#ifndef _DEFAULT_FONTS_H\n") diff --git a/scene/theme/icons/default_theme_icons_builders.py b/scene/theme/icons/default_theme_icons_builders.py index c915c52cbd..12347cc58b 100644 --- a/scene/theme/icons/default_theme_icons_builders.py +++ b/scene/theme/icons/default_theme_icons_builders.py @@ -65,7 +65,7 @@ def make_default_theme_icons_action(target, source, env): s.write("#endif\n") - with open(dst, "w") as f: + with open(dst, "w", encoding="utf-8", newline="\n") as f: f.write(s.getvalue()) s.close() diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 4e5539e6a4..aa69cd8539 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -2053,7 +2053,7 @@ void RendererSceneCull::_update_instance_lightmap_captures(Instance *p_instance) Vector3 inner_pos = ((lm_pos - bounds.position) / bounds.size) * 2.0 - Vector3(1.0, 1.0, 1.0); - real_t blend = MAX(inner_pos.x, MAX(inner_pos.y, inner_pos.z)); + real_t blend = MAX(ABS(inner_pos.x), MAX(ABS(inner_pos.y), ABS(inner_pos.z))); //make blend more rounded blend = Math::lerp(inner_pos.length(), blend, blend); blend *= blend; diff --git a/tests/create_test.py b/tests/create_test.py index 867a66e446..4d1f1d656e 100644 --- a/tests/create_test.py +++ b/tests/create_test.py @@ -38,7 +38,7 @@ def main(): if os.path.isfile(file_path): print(f'ERROR: The file "{file_path}" already exists.') sys.exit(1) - with open(file_path, "w") as file: + with open(file_path, "w", encoding="utf-8", newline="\n") as file: file.write( """/**************************************************************************/ /* test_{name_snake_case}.h {padding} */ @@ -108,7 +108,7 @@ TEST_CASE("[{name_pascal_case}] Example test case") {{ if match: new_string = contents[: match.start()] + f'#include "tests/{file_path}"\n' + contents[match.start() :] - with open("test_main.cpp", "w") as file: + with open("test_main.cpp", "w", encoding="utf-8", newline="\n") as file: file.write(new_string) print("Done.") # Use clang format to sort include directives afster insertion. diff --git a/thirdparty/README.md b/thirdparty/README.md index 12333fa06f..59b9203960 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -462,12 +462,12 @@ Files extracted from upstream source: ## libpng - Upstream: http://libpng.org/pub/png/libpng.html -- Version: 1.6.40 (f135775ad4e5d4408d2e12ffcc71bb36e6b48551, 2023) +- Version: 1.6.43 (ed217e3e601d8e462f7fd1e04bed43ac42212429, 2024) - License: libpng/zlib Files extracted from upstream source: -- All `.c` and `.h` files of the main directory, except from `example.c` and +- All `.c` and `.h` files of the main directory, apart from `example.c` and `pngtest.c` - `arm/`, `intel/` and `powerpc/` folders - `scripts/pnglibconf.h.prebuilt` as `pnglibconf.h` diff --git a/thirdparty/libpng/LICENSE b/thirdparty/libpng/LICENSE index 086d1c2fda..25f298f0fc 100644 --- a/thirdparty/libpng/LICENSE +++ b/thirdparty/libpng/LICENSE @@ -4,8 +4,8 @@ COPYRIGHT NOTICE, DISCLAIMER, and LICENSE PNG Reference Library License version 2 --------------------------------------- - * Copyright (c) 1995-2023 The PNG Reference Library Authors. - * Copyright (c) 2018-2023 Cosmin Truta. + * Copyright (c) 1995-2024 The PNG Reference Library Authors. + * Copyright (c) 2018-2024 Cosmin Truta. * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. * Copyright (c) 1996-1997 Andreas Dilger. * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. diff --git a/thirdparty/libpng/png.c b/thirdparty/libpng/png.c index d6471b06cc..9ed3157009 100644 --- a/thirdparty/libpng/png.c +++ b/thirdparty/libpng/png.c @@ -1,7 +1,7 @@ /* png.c - location for general purpose libpng functions * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -14,27 +14,7 @@ #include "pngpriv.h" /* Generate a compiler error if there is an old png.h in the search path. */ -typedef png_libpng_version_1_6_40 Your_png_h_is_not_version_1_6_40; - -#ifdef __GNUC__ -/* The version tests may need to be added to, but the problem warning has - * consistently been fixed in GCC versions which obtain wide-spread release. - * The problem is that many versions of GCC rearrange comparison expressions in - * the optimizer in such a way that the results of the comparison will change - * if signed integer overflow occurs. Such comparisons are not permitted in - * ANSI C90, however GCC isn't clever enough to work out that that do not occur - * below in png_ascii_from_fp and png_muldiv, so it produces a warning with - * -Wextra. Unfortunately this is highly dependent on the optimizer and the - * machine architecture so the warning comes and goes unpredictably and is - * impossible to "fix", even were that a good idea. - */ -#if __GNUC__ == 7 && __GNUC_MINOR__ == 1 -#define GCC_STRICT_OVERFLOW 1 -#endif /* GNU 7.1.x */ -#endif /* GNU */ -#ifndef GCC_STRICT_OVERFLOW -#define GCC_STRICT_OVERFLOW 0 -#endif +typedef png_libpng_version_1_6_43 Your_png_h_is_not_version_1_6_43; /* Tells libpng that we have already handled the first "num_bytes" bytes * of the PNG file signature. If the PNG data is embedded into another @@ -73,21 +53,21 @@ png_set_sig_bytes(png_structrp png_ptr, int num_bytes) int PNGAPI png_sig_cmp(png_const_bytep sig, size_t start, size_t num_to_check) { - png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + static const png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; if (num_to_check > 8) num_to_check = 8; else if (num_to_check < 1) - return (-1); + return -1; if (start > 7) - return (-1); + return -1; if (start + num_to_check > 8) num_to_check = 8 - start; - return ((int)(memcmp(&sig[start], &png_signature[start], num_to_check))); + return memcmp(&sig[start], &png_signature[start], num_to_check); } #endif /* READ */ @@ -447,7 +427,6 @@ png_info_init_3,(png_infopp ptr_ptr, size_t png_info_struct_size), memset(info_ptr, 0, (sizeof *info_ptr)); } -/* The following API is not called internally */ void PNGAPI png_data_freer(png_const_structrp png_ptr, png_inforp info_ptr, int freer, png_uint_32 mask) @@ -686,9 +665,9 @@ png_voidp PNGAPI png_get_io_ptr(png_const_structrp png_ptr) { if (png_ptr == NULL) - return (NULL); + return NULL; - return (png_ptr->io_ptr); + return png_ptr->io_ptr; } #if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) @@ -752,7 +731,7 @@ png_convert_to_rfc1123_buffer(char out[29], png_const_timep ptime) { size_t pos = 0; - char number_buf[5]; /* enough for a four-digit year */ + char number_buf[5] = {0, 0, 0, 0, 0}; /* enough for a four-digit year */ # define APPEND_STRING(string) pos = png_safecat(out, 29, pos, (string)) # define APPEND_NUMBER(format, value)\ @@ -815,8 +794,8 @@ png_get_copyright(png_const_structrp png_ptr) return PNG_STRING_COPYRIGHT #else return PNG_STRING_NEWLINE \ - "libpng version 1.6.40" PNG_STRING_NEWLINE \ - "Copyright (c) 2018-2023 Cosmin Truta" PNG_STRING_NEWLINE \ + "libpng version 1.6.43" PNG_STRING_NEWLINE \ + "Copyright (c) 2018-2024 Cosmin Truta" PNG_STRING_NEWLINE \ "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \ PNG_STRING_NEWLINE \ "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \ @@ -977,7 +956,7 @@ png_reset_zstream(png_structrp png_ptr) return Z_STREAM_ERROR; /* WARNING: this resets the window bits to the maximum! */ - return (inflateReset(&png_ptr->zstream)); + return inflateReset(&png_ptr->zstream); } #endif /* READ */ @@ -986,7 +965,7 @@ png_uint_32 PNGAPI png_access_version_number(void) { /* Version of *.c files used when building libpng */ - return((png_uint_32)PNG_LIBPNG_VER); + return (png_uint_32)PNG_LIBPNG_VER; } #if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) @@ -1842,14 +1821,14 @@ png_icc_profile_error(png_const_structrp png_ptr, png_colorspacerp colorspace, } # ifdef PNG_WARNINGS_SUPPORTED else - { - char number[PNG_NUMBER_BUFFER_SIZE]; /* +24 = 114 */ + { + char number[PNG_NUMBER_BUFFER_SIZE]; /* +24 = 114 */ - pos = png_safecat(message, (sizeof message), pos, - png_format_number(number, number+(sizeof number), - PNG_NUMBER_FORMAT_x, value)); - pos = png_safecat(message, (sizeof message), pos, "h: "); /* +2 = 116 */ - } + pos = png_safecat(message, (sizeof message), pos, + png_format_number(number, number+(sizeof number), + PNG_NUMBER_FORMAT_x, value)); + pos = png_safecat(message, (sizeof message), pos, "h: "); /* +2 = 116 */ + } # endif /* The 'reason' is an arbitrary message, allow +79 maximum 195 */ pos = png_safecat(message, (sizeof message), pos, reason); @@ -2532,17 +2511,6 @@ png_colorspace_set_rgb_coefficients(png_structrp png_ptr) #endif /* COLORSPACE */ -#ifdef __GNUC__ -/* This exists solely to work round a warning from GNU C. */ -static int /* PRIVATE */ -png_gt(size_t a, size_t b) -{ - return a > b; -} -#else -# define png_gt(a,b) ((a) > (b)) -#endif - void /* PRIVATE */ png_check_IHDR(png_const_structrp png_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, @@ -2564,8 +2532,16 @@ png_check_IHDR(png_const_structrp png_ptr, error = 1; } - if (png_gt(((width + 7) & (~7U)), - ((PNG_SIZE_MAX + /* The bit mask on the first line below must be at least as big as a + * png_uint_32. "~7U" is not adequate on 16-bit systems because it will + * be an unsigned 16-bit value. Casting to (png_alloc_size_t) makes the + * type of the result at least as bit (in bits) as the RHS of the > operator + * which also avoids a common warning on 64-bit systems that the comparison + * of (png_uint_32) against the constant value on the RHS will always be + * false. + */ + if (((width + 7) & ~(png_alloc_size_t)7) > + (((PNG_SIZE_MAX - 48 /* big_row_buf hack */ - 1) /* filter byte */ / 8) /* 8-byte RGBA pixels */ @@ -2891,14 +2867,6 @@ png_pow10(int power) /* Function to format a floating point value in ASCII with a given * precision. */ -#if GCC_STRICT_OVERFLOW -#pragma GCC diagnostic push -/* The problem arises below with exp_b10, which can never overflow because it - * comes, originally, from frexp and is therefore limited to a range which is - * typically +/-710 (log2(DBL_MAX)/log2(DBL_MIN)). - */ -#pragma GCC diagnostic warning "-Wstrict-overflow=2" -#endif /* GCC_STRICT_OVERFLOW */ void /* PRIVATE */ png_ascii_from_fp(png_const_structrp png_ptr, png_charp ascii, size_t size, double fp, unsigned int precision) @@ -3220,10 +3188,6 @@ png_ascii_from_fp(png_const_structrp png_ptr, png_charp ascii, size_t size, /* Here on buffer too small. */ png_error(png_ptr, "ASCII conversion buffer too small"); } -#if GCC_STRICT_OVERFLOW -#pragma GCC diagnostic pop -#endif /* GCC_STRICT_OVERFLOW */ - # endif /* FLOATING_POINT */ # ifdef PNG_FIXED_POINT_SUPPORTED @@ -3251,7 +3215,7 @@ png_ascii_from_fixed(png_const_structrp png_ptr, png_charp ascii, if (num <= 0x80000000) /* else overflowed */ { unsigned int ndigits = 0, first = 16 /* flag value */; - char digits[10]; + char digits[10] = {0}; while (num) { @@ -3336,15 +3300,6 @@ png_fixed(png_const_structrp png_ptr, double fp, png_const_charp text) * the nearest .00001). Overflow and divide by zero are signalled in * the result, a boolean - true on success, false on overflow. */ -#if GCC_STRICT_OVERFLOW /* from above */ -/* It is not obvious which comparison below gets optimized in such a way that - * signed overflow would change the result; looking through the code does not - * reveal any tests which have the form GCC complains about, so presumably the - * optimizer is moving an add or subtract into the 'if' somewhere. - */ -#pragma GCC diagnostic push -#pragma GCC diagnostic warning "-Wstrict-overflow=2" -#endif /* GCC_STRICT_OVERFLOW */ int png_muldiv(png_fixed_point_p res, png_fixed_point a, png_int_32 times, png_int_32 divisor) @@ -3459,9 +3414,6 @@ png_muldiv(png_fixed_point_p res, png_fixed_point a, png_int_32 times, return 0; } -#if GCC_STRICT_OVERFLOW -#pragma GCC diagnostic pop -#endif /* GCC_STRICT_OVERFLOW */ #endif /* READ_GAMMA || INCH_CONVERSIONS */ #if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_INCH_CONVERSIONS_SUPPORTED) diff --git a/thirdparty/libpng/png.h b/thirdparty/libpng/png.h index cfc4841099..83d3903126 100644 --- a/thirdparty/libpng/png.h +++ b/thirdparty/libpng/png.h @@ -1,9 +1,9 @@ /* png.h - header file for PNG reference library * - * libpng version 1.6.40 + * libpng version 1.6.43 * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -15,7 +15,7 @@ * libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger * libpng versions 0.97, January 1998, through 1.6.35, July 2018: * Glenn Randers-Pehrson - * libpng versions 1.6.36, December 2018, through 1.6.40, June 2023: + * libpng versions 1.6.36, December 2018, through 1.6.43, February 2024: * Cosmin Truta * See also "Contributing Authors", below. */ @@ -27,8 +27,8 @@ * PNG Reference Library License version 2 * --------------------------------------- * - * * Copyright (c) 1995-2023 The PNG Reference Library Authors. - * * Copyright (c) 2018-2023 Cosmin Truta. + * * Copyright (c) 1995-2024 The PNG Reference Library Authors. + * * Copyright (c) 2018-2024 Cosmin Truta. * * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. * * Copyright (c) 1996-1997 Andreas Dilger. * * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -239,7 +239,7 @@ * ... * 1.5.30 15 10530 15.so.15.30[.0] * ... - * 1.6.40 16 10640 16.so.16.40[.0] + * 1.6.43 16 10643 16.so.16.43[.0] * * Henceforth the source version will match the shared-library major and * minor numbers; the shared-library major version number will be used for @@ -255,9 +255,6 @@ * to the info_ptr or png_ptr members through png.h, and the compiled * application is loaded with a different version of the library. * - * DLLNUM will change each time there are forward or backward changes - * in binary compatibility (e.g., when a new feature is added). - * * See libpng.txt or libpng.3 for more information. The PNG specification * is available as a W3C Recommendation and as an ISO/IEC Standard; see * <https://www.w3.org/TR/2003/REC-PNG-20031110/> @@ -278,19 +275,21 @@ */ /* Version information for png.h - this should match the version in png.c */ -#define PNG_LIBPNG_VER_STRING "1.6.40" -#define PNG_HEADER_VERSION_STRING " libpng version 1.6.40 - June 21, 2023\n" +#define PNG_LIBPNG_VER_STRING "1.6.43" +#define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n" -#define PNG_LIBPNG_VER_SONUM 16 -#define PNG_LIBPNG_VER_DLLNUM 16 +/* The versions of shared library builds should stay in sync, going forward */ +#define PNG_LIBPNG_VER_SHAREDLIB 16 +#define PNG_LIBPNG_VER_SONUM PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */ +#define PNG_LIBPNG_VER_DLLNUM PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */ /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */ #define PNG_LIBPNG_VER_MAJOR 1 #define PNG_LIBPNG_VER_MINOR 6 -#define PNG_LIBPNG_VER_RELEASE 40 +#define PNG_LIBPNG_VER_RELEASE 43 /* This should be zero for a public release, or non-zero for a - * development version. [Deprecated] + * development version. */ #define PNG_LIBPNG_VER_BUILD 0 @@ -318,7 +317,7 @@ * From version 1.0.1 it is: * XXYYZZ, where XX=major, YY=minor, ZZ=release */ -#define PNG_LIBPNG_VER 10640 /* 1.6.40 */ +#define PNG_LIBPNG_VER 10643 /* 1.6.43 */ /* Library configuration: these options cannot be changed after * the library has been built. @@ -428,7 +427,7 @@ extern "C" { /* This triggers a compiler error in png.c, if png.c and png.h * do not agree upon the version number. */ -typedef char* png_libpng_version_1_6_40; +typedef char* png_libpng_version_1_6_43; /* Basic control structions. Read libpng-manual.txt or libpng.3 for more info. * @@ -849,7 +848,7 @@ PNG_FUNCTION(void, (PNGCAPI *png_longjmp_ptr), PNGARG((jmp_buf, int)), typedef); #define PNG_TRANSFORM_GRAY_TO_RGB 0x2000 /* read only */ /* Added to libpng-1.5.4 */ #define PNG_TRANSFORM_EXPAND_16 0x4000 /* read only */ -#if INT_MAX >= 0x8000 /* else this might break */ +#if ~0U > 0xffffU /* or else this might break on a 16-bit machine */ #define PNG_TRANSFORM_SCALE_16 0x8000 /* read only */ #endif @@ -908,15 +907,15 @@ PNG_EXPORT(2, void, png_set_sig_bytes, (png_structrp png_ptr, int num_bytes)); /* Check sig[start] through sig[start + num_to_check - 1] to see if it's a * PNG file. Returns zero if the supplied bytes match the 8-byte PNG * signature, and non-zero otherwise. Having num_to_check == 0 or - * start > 7 will always fail (ie return non-zero). + * start > 7 will always fail (i.e. return non-zero). */ PNG_EXPORT(3, int, png_sig_cmp, (png_const_bytep sig, size_t start, size_t num_to_check)); /* Simple signature checking function. This is the same as calling - * png_check_sig(sig, n) := !png_sig_cmp(sig, 0, n). + * png_check_sig(sig, n) := (png_sig_cmp(sig, 0, n) == 0). */ -#define png_check_sig(sig, n) !png_sig_cmp((sig), 0, (n)) +#define png_check_sig(sig, n) (png_sig_cmp((sig), 0, (n)) == 0) /* DEPRECATED */ /* Allocate and initialize png_ptr struct for reading, and any other memory. */ PNG_EXPORTA(4, png_structp, png_create_read_struct, @@ -1730,12 +1729,9 @@ PNG_EXPORT(97, void, png_free, (png_const_structrp png_ptr, png_voidp ptr)); PNG_EXPORT(98, void, png_free_data, (png_const_structrp png_ptr, png_inforp info_ptr, png_uint_32 free_me, int num)); -/* Reassign responsibility for freeing existing data, whether allocated +/* Reassign the responsibility for freeing existing data, whether allocated * by libpng or by the application; this works on the png_info structure passed - * in, it does not change the state for other png_info structures. - * - * It is unlikely that this function works correctly as of 1.6.0 and using it - * may result either in memory leaks or double free of allocated data. + * in, without changing the state for other png_info structures. */ PNG_EXPORT(99, void, png_data_freer, (png_const_structrp png_ptr, png_inforp info_ptr, int freer, png_uint_32 mask)); @@ -3207,11 +3203,18 @@ PNG_EXPORT(245, int, png_image_write_to_memory, (png_imagep image, void *memory, #ifdef PNG_MIPS_MSA_API_SUPPORTED # define PNG_MIPS_MSA 6 /* HARDWARE: MIPS Msa SIMD instructions supported */ #endif -#define PNG_IGNORE_ADLER32 8 +#ifdef PNG_DISABLE_ADLER32_CHECK_SUPPORTED +# define PNG_IGNORE_ADLER32 8 /* SOFTWARE: disable Adler32 check on IDAT */ +#endif #ifdef PNG_POWERPC_VSX_API_SUPPORTED -# define PNG_POWERPC_VSX 10 /* HARDWARE: PowerPC VSX SIMD instructions supported */ +# define PNG_POWERPC_VSX 10 /* HARDWARE: PowerPC VSX SIMD instructions + * supported */ #endif -#define PNG_OPTION_NEXT 12 /* Next option - numbers must be even */ +#ifdef PNG_MIPS_MMI_API_SUPPORTED +# define PNG_MIPS_MMI 12 /* HARDWARE: MIPS MMI SIMD instructions supported */ +#endif + +#define PNG_OPTION_NEXT 14 /* Next option - numbers must be even */ /* Return values: NOTE: there are four values and 'off' is *not* zero */ #define PNG_OPTION_UNSET 0 /* Unset - defaults to off */ diff --git a/thirdparty/libpng/pngconf.h b/thirdparty/libpng/pngconf.h index 6671e3c335..000d7b1a8a 100644 --- a/thirdparty/libpng/pngconf.h +++ b/thirdparty/libpng/pngconf.h @@ -1,9 +1,9 @@ /* pngconf.h - machine-configurable file for libpng * - * libpng version 1.6.40 + * libpng version 1.6.43 * - * Copyright (c) 2018-2022 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. diff --git a/thirdparty/libpng/pngerror.c b/thirdparty/libpng/pngerror.c index ec3a709b9d..29ebda7943 100644 --- a/thirdparty/libpng/pngerror.c +++ b/thirdparty/libpng/pngerror.c @@ -1,7 +1,7 @@ /* pngerror.c - stub functions for i/o and memory allocation * - * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2017 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -255,7 +255,7 @@ void png_warning_parameter_unsigned(png_warning_parameters p, int number, int format, png_alloc_size_t value) { - char buffer[PNG_NUMBER_BUFFER_SIZE]; + char buffer[PNG_NUMBER_BUFFER_SIZE] = {0}; png_warning_parameter(p, number, PNG_FORMAT_NUMBER(buffer, format, value)); } @@ -265,7 +265,7 @@ png_warning_parameter_signed(png_warning_parameters p, int number, int format, { png_alloc_size_t u; png_charp str; - char buffer[PNG_NUMBER_BUFFER_SIZE]; + char buffer[PNG_NUMBER_BUFFER_SIZE] = {0}; /* Avoid overflow by doing the negate in a png_alloc_size_t: */ u = (png_alloc_size_t)value; @@ -858,7 +858,7 @@ png_get_error_ptr(png_const_structrp png_ptr) if (png_ptr == NULL) return NULL; - return ((png_voidp)png_ptr->error_ptr); + return (png_voidp)png_ptr->error_ptr; } @@ -933,31 +933,25 @@ png_safe_warning(png_structp png_nonconst_ptr, png_const_charp warning_message) #endif int /* PRIVATE */ -png_safe_execute(png_imagep image_in, int (*function)(png_voidp), png_voidp arg) +png_safe_execute(png_imagep image, int (*function)(png_voidp), png_voidp arg) { - volatile png_imagep image = image_in; - volatile int result; - volatile png_voidp saved_error_buf; + png_voidp saved_error_buf = image->opaque->error_buf; jmp_buf safe_jmpbuf; + int result; - /* Safely execute function(arg) with png_error returning to this function. */ - saved_error_buf = image->opaque->error_buf; - result = setjmp(safe_jmpbuf) == 0; - - if (result != 0) + /* Safely execute function(arg), with png_error returning back here. */ + if (setjmp(safe_jmpbuf) == 0) { - image->opaque->error_buf = safe_jmpbuf; result = function(arg); + image->opaque->error_buf = saved_error_buf; + return result; } + /* On png_error, return via longjmp, pop the jmpbuf, and free the image. */ image->opaque->error_buf = saved_error_buf; - - /* And do the cleanup prior to any failure return. */ - if (result == 0) - png_image_free(image); - - return result; + png_image_free(image); + return 0; } #endif /* SIMPLIFIED READ || SIMPLIFIED_WRITE */ #endif /* READ || WRITE */ diff --git a/thirdparty/libpng/pngget.c b/thirdparty/libpng/pngget.c index 1490a032e1..1084b268ff 100644 --- a/thirdparty/libpng/pngget.c +++ b/thirdparty/libpng/pngget.c @@ -1,7 +1,7 @@ /* pngget.c - retrieval of values from info struct * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -28,22 +28,22 @@ png_get_valid(png_const_structrp png_ptr, png_const_inforp info_ptr, * valid tRNS chunk in this case. */ if (flag == PNG_INFO_tRNS && png_ptr->num_trans == 0) - return(0); + return 0; #endif - return(info_ptr->valid & flag); + return info_ptr->valid & flag; } - return(0); + return 0; } size_t PNGAPI png_get_rowbytes(png_const_structrp png_ptr, png_const_inforp info_ptr) { if (png_ptr != NULL && info_ptr != NULL) - return(info_ptr->rowbytes); + return info_ptr->rowbytes; - return(0); + return 0; } #ifdef PNG_INFO_IMAGE_SUPPORTED @@ -51,9 +51,9 @@ png_bytepp PNGAPI png_get_rows(png_const_structrp png_ptr, png_const_inforp info_ptr) { if (png_ptr != NULL && info_ptr != NULL) - return(info_ptr->row_pointers); + return info_ptr->row_pointers; - return(0); + return 0; } #endif @@ -65,7 +65,7 @@ png_get_image_width(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->width; - return (0); + return 0; } png_uint_32 PNGAPI @@ -74,7 +74,7 @@ png_get_image_height(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->height; - return (0); + return 0; } png_byte PNGAPI @@ -83,7 +83,7 @@ png_get_bit_depth(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->bit_depth; - return (0); + return 0; } png_byte PNGAPI @@ -92,7 +92,7 @@ png_get_color_type(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->color_type; - return (0); + return 0; } png_byte PNGAPI @@ -101,7 +101,7 @@ png_get_filter_type(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->filter_type; - return (0); + return 0; } png_byte PNGAPI @@ -110,7 +110,7 @@ png_get_interlace_type(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->interlace_type; - return (0); + return 0; } png_byte PNGAPI @@ -119,7 +119,7 @@ png_get_compression_type(png_const_structrp png_ptr, png_const_inforp info_ptr) if (png_ptr != NULL && info_ptr != NULL) return info_ptr->compression_type; - return (0); + return 0; } png_uint_32 PNGAPI @@ -127,21 +127,20 @@ png_get_x_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_pHYs_SUPPORTED + png_debug(1, "in png_get_x_pixels_per_meter"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) - { - png_debug1(1, "in %s retrieval function", - "png_get_x_pixels_per_meter"); - - if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) - return (info_ptr->x_pixels_per_unit); - } + { + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) + return info_ptr->x_pixels_per_unit; + } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } png_uint_32 PNGAPI @@ -149,42 +148,41 @@ png_get_y_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_pHYs_SUPPORTED + png_debug(1, "in png_get_y_pixels_per_meter"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) { - png_debug1(1, "in %s retrieval function", - "png_get_y_pixels_per_meter"); - if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) - return (info_ptr->y_pixels_per_unit); + return info_ptr->y_pixels_per_unit; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } png_uint_32 PNGAPI png_get_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_pHYs_SUPPORTED + png_debug(1, "in png_get_pixels_per_meter"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_pixels_per_meter"); - if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER && info_ptr->x_pixels_per_unit == info_ptr->y_pixels_per_unit) - return (info_ptr->x_pixels_per_unit); + return info_ptr->x_pixels_per_unit; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } #ifdef PNG_FLOATING_POINT_SUPPORTED @@ -193,21 +191,21 @@ png_get_pixel_aspect_ratio(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_READ_pHYs_SUPPORTED + png_debug(1, "in png_get_pixel_aspect_ratio"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio"); - if (info_ptr->x_pixels_per_unit != 0) - return ((float)((float)info_ptr->y_pixels_per_unit - /(float)info_ptr->x_pixels_per_unit)); + return (float)info_ptr->y_pixels_per_unit + / (float)info_ptr->x_pixels_per_unit; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return ((float)0.0); + return (float)0.0; } #endif @@ -217,6 +215,8 @@ png_get_pixel_aspect_ratio_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_READ_pHYs_SUPPORTED + png_debug(1, "in png_get_pixel_aspect_ratio_fixed"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0 && info_ptr->x_pixels_per_unit > 0 && info_ptr->y_pixels_per_unit > 0 && @@ -225,8 +225,6 @@ png_get_pixel_aspect_ratio_fixed(png_const_structrp png_ptr, { png_fixed_point res; - png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio_fixed"); - /* The following casts work because a PNG 4 byte integer only has a valid * range of 0..2^31-1; otherwise the cast might overflow. */ @@ -247,80 +245,80 @@ png_int_32 PNGAPI png_get_x_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_oFFs_SUPPORTED + png_debug(1, "in png_get_x_offset_microns"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_x_offset_microns"); - if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) - return (info_ptr->x_offset); + return info_ptr->x_offset; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } png_int_32 PNGAPI png_get_y_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_oFFs_SUPPORTED + png_debug(1, "in png_get_y_offset_microns"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_y_offset_microns"); - if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) - return (info_ptr->y_offset); + return info_ptr->y_offset; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } png_int_32 PNGAPI png_get_x_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_oFFs_SUPPORTED + png_debug(1, "in png_get_x_offset_pixels"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_x_offset_pixels"); - if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) - return (info_ptr->x_offset); + return info_ptr->x_offset; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } png_int_32 PNGAPI png_get_y_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) { #ifdef PNG_oFFs_SUPPORTED + png_debug(1, "in png_get_y_offset_pixels"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) != 0) { - png_debug1(1, "in %s retrieval function", "png_get_y_offset_pixels"); - if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) - return (info_ptr->y_offset); + return info_ptr->y_offset; } #else PNG_UNUSED(png_ptr) PNG_UNUSED(info_ptr) #endif - return (0); + return 0; } #ifdef PNG_INCH_CONVERSIONS_SUPPORTED @@ -434,11 +432,11 @@ png_get_pHYs_dpi(png_const_structrp png_ptr, png_const_inforp info_ptr, { png_uint_32 retval = 0; + png_debug1(1, "in %s retrieval function", "pHYs"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) != 0) { - png_debug1(1, "in %s retrieval function", "pHYs"); - if (res_x != NULL) { *res_x = info_ptr->x_pixels_per_unit; @@ -464,7 +462,7 @@ png_get_pHYs_dpi(png_const_structrp png_ptr, png_const_inforp info_ptr, } } - return (retval); + return retval; } #endif /* pHYs */ #endif /* INCH_CONVERSIONS */ @@ -478,9 +476,9 @@ png_byte PNGAPI png_get_channels(png_const_structrp png_ptr, png_const_inforp info_ptr) { if (png_ptr != NULL && info_ptr != NULL) - return(info_ptr->channels); + return info_ptr->channels; - return (0); + return 0; } #ifdef PNG_READ_SUPPORTED @@ -488,9 +486,9 @@ png_const_bytep PNGAPI png_get_signature(png_const_structrp png_ptr, png_const_inforp info_ptr) { if (png_ptr != NULL && info_ptr != NULL) - return(info_ptr->signature); + return info_ptr->signature; - return (NULL); + return NULL; } #endif @@ -499,17 +497,17 @@ png_uint_32 PNGAPI png_get_bKGD(png_const_structrp png_ptr, png_inforp info_ptr, png_color_16p *background) { + png_debug1(1, "in %s retrieval function", "bKGD"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD) != 0 && background != NULL) { - png_debug1(1, "in %s retrieval function", "bKGD"); - *background = &(info_ptr->background); - return (PNG_INFO_bKGD); + return PNG_INFO_bKGD; } - return (0); + return 0; } #endif @@ -524,6 +522,8 @@ png_get_cHRM(png_const_structrp png_ptr, png_const_inforp info_ptr, double *white_x, double *white_y, double *red_x, double *red_y, double *green_x, double *green_y, double *blue_x, double *blue_y) { + png_debug1(1, "in %s retrieval function", "cHRM"); + /* Quiet API change: this code used to only return the end points if a cHRM * chunk was present, but the end points can also come from iCCP or sRGB * chunks, so in 1.6.0 the png_get_ APIs return the end points regardless and @@ -533,8 +533,6 @@ png_get_cHRM(png_const_structrp png_ptr, png_const_inforp info_ptr, if (png_ptr != NULL && info_ptr != NULL && (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) { - png_debug1(1, "in %s retrieval function", "cHRM"); - if (white_x != NULL) *white_x = png_float(png_ptr, info_ptr->colorspace.end_points_xy.whitex, "cHRM white X"); @@ -559,10 +557,10 @@ png_get_cHRM(png_const_structrp png_ptr, png_const_inforp info_ptr, if (blue_y != NULL) *blue_y = png_float(png_ptr, info_ptr->colorspace.end_points_xy.bluey, "cHRM blue Y"); - return (PNG_INFO_cHRM); + return PNG_INFO_cHRM; } - return (0); + return 0; } png_uint_32 PNGAPI @@ -571,11 +569,11 @@ png_get_cHRM_XYZ(png_const_structrp png_ptr, png_const_inforp info_ptr, double *green_Y, double *green_Z, double *blue_X, double *blue_Y, double *blue_Z) { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ(float)"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) { - png_debug1(1, "in %s retrieval function", "cHRM_XYZ(float)"); - if (red_X != NULL) *red_X = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_X, "cHRM red X"); @@ -603,10 +601,10 @@ png_get_cHRM_XYZ(png_const_structrp png_ptr, png_const_inforp info_ptr, if (blue_Z != NULL) *blue_Z = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.blue_Z, "cHRM blue Z"); - return (PNG_INFO_cHRM); + return PNG_INFO_cHRM; } - return (0); + return 0; } # endif @@ -619,11 +617,11 @@ png_get_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, png_fixed_point *int_blue_Z) { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) { - png_debug1(1, "in %s retrieval function", "cHRM_XYZ"); - if (int_red_X != NULL) *int_red_X = info_ptr->colorspace.end_points_XYZ.red_X; if (int_red_Y != NULL) @@ -642,10 +640,10 @@ png_get_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, *int_blue_Y = info_ptr->colorspace.end_points_XYZ.blue_Y; if (int_blue_Z != NULL) *int_blue_Z = info_ptr->colorspace.end_points_XYZ.blue_Z; - return (PNG_INFO_cHRM); + return PNG_INFO_cHRM; } - return (0); + return 0; } png_uint_32 PNGAPI @@ -675,10 +673,10 @@ png_get_cHRM_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, *blue_x = info_ptr->colorspace.end_points_xy.bluex; if (blue_y != NULL) *blue_y = info_ptr->colorspace.end_points_xy.bluey; - return (PNG_INFO_cHRM); + return PNG_INFO_cHRM; } - return (0); + return 0; } # endif #endif @@ -696,10 +694,10 @@ png_get_gAMA_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, file_gamma != NULL) { *file_gamma = info_ptr->colorspace.gamma; - return (PNG_INFO_gAMA); + return PNG_INFO_gAMA; } - return (0); + return 0; } # endif @@ -716,10 +714,10 @@ png_get_gAMA(png_const_structrp png_ptr, png_const_inforp info_ptr, { *file_gamma = png_float(png_ptr, info_ptr->colorspace.gamma, "png_get_gAMA"); - return (PNG_INFO_gAMA); + return PNG_INFO_gAMA; } - return (0); + return 0; } # endif #endif @@ -735,10 +733,10 @@ png_get_sRGB(png_const_structrp png_ptr, png_const_inforp info_ptr, (info_ptr->valid & PNG_INFO_sRGB) != 0 && file_srgb_intent != NULL) { *file_srgb_intent = info_ptr->colorspace.rendering_intent; - return (PNG_INFO_sRGB); + return PNG_INFO_sRGB; } - return (0); + return 0; } #endif @@ -762,10 +760,10 @@ png_get_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, */ if (compression_type != NULL) *compression_type = PNG_COMPRESSION_TYPE_BASE; - return (PNG_INFO_iCCP); + return PNG_INFO_iCCP; } - return (0); + return 0; } #endif @@ -775,13 +773,15 @@ int PNGAPI png_get_sPLT(png_const_structrp png_ptr, png_inforp info_ptr, png_sPLT_tpp spalettes) { + png_debug1(1, "in %s retrieval function", "sPLT"); + if (png_ptr != NULL && info_ptr != NULL && spalettes != NULL) { *spalettes = info_ptr->splt_palettes; return info_ptr->splt_palettes_num; } - return (0); + return 0; } #endif @@ -807,10 +807,10 @@ png_get_eXIf_1(png_const_structrp png_ptr, png_const_inforp info_ptr, { *num_exif = info_ptr->num_exif; *exif = info_ptr->exif; - return (PNG_INFO_eXIf); + return PNG_INFO_eXIf; } - return (0); + return 0; } #endif @@ -825,10 +825,10 @@ png_get_hIST(png_const_structrp png_ptr, png_inforp info_ptr, (info_ptr->valid & PNG_INFO_hIST) != 0 && hist != NULL) { *hist = info_ptr->hist; - return (PNG_INFO_hIST); + return PNG_INFO_hIST; } - return (0); + return 0; } #endif @@ -841,7 +841,7 @@ png_get_IHDR(png_const_structrp png_ptr, png_const_inforp info_ptr, png_debug1(1, "in %s retrieval function", "IHDR"); if (png_ptr == NULL || info_ptr == NULL) - return (0); + return 0; if (width != NULL) *width = info_ptr->width; @@ -873,7 +873,7 @@ png_get_IHDR(png_const_structrp png_ptr, png_const_inforp info_ptr, info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type, info_ptr->compression_type, info_ptr->filter_type); - return (1); + return 1; } #ifdef PNG_oFFs_SUPPORTED @@ -890,10 +890,10 @@ png_get_oFFs(png_const_structrp png_ptr, png_const_inforp info_ptr, *offset_x = info_ptr->x_offset; *offset_y = info_ptr->y_offset; *unit_type = (int)info_ptr->offset_unit_type; - return (PNG_INFO_oFFs); + return PNG_INFO_oFFs; } - return (0); + return 0; } #endif @@ -917,10 +917,10 @@ png_get_pCAL(png_const_structrp png_ptr, png_inforp info_ptr, *nparams = (int)info_ptr->pcal_nparams; *units = info_ptr->pcal_units; *params = info_ptr->pcal_params; - return (PNG_INFO_pCAL); + return PNG_INFO_pCAL; } - return (0); + return 0; } #endif @@ -932,6 +932,8 @@ png_uint_32 PNGAPI png_get_sCAL_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, png_fixed_point *width, png_fixed_point *height) { + png_debug1(1, "in %s retrieval function", "sCAL"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL) != 0) { @@ -943,10 +945,10 @@ png_get_sCAL_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, *width = png_fixed(png_ptr, atof(info_ptr->scal_s_width), "sCAL width"); *height = png_fixed(png_ptr, atof(info_ptr->scal_s_height), "sCAL height"); - return (PNG_INFO_sCAL); + return PNG_INFO_sCAL; } - return(0); + return 0; } # endif /* FLOATING_ARITHMETIC */ # endif /* FIXED_POINT */ @@ -955,32 +957,36 @@ png_uint_32 PNGAPI png_get_sCAL(png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, double *width, double *height) { + png_debug1(1, "in %s retrieval function", "sCAL(float)"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL) != 0) { *unit = info_ptr->scal_unit; *width = atof(info_ptr->scal_s_width); *height = atof(info_ptr->scal_s_height); - return (PNG_INFO_sCAL); + return PNG_INFO_sCAL; } - return(0); + return 0; } # endif /* FLOATING POINT */ png_uint_32 PNGAPI png_get_sCAL_s(png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, png_charpp width, png_charpp height) { + png_debug1(1, "in %s retrieval function", "sCAL(str)"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL) != 0) { *unit = info_ptr->scal_unit; *width = info_ptr->scal_s_width; *height = info_ptr->scal_s_height; - return (PNG_INFO_sCAL); + return PNG_INFO_sCAL; } - return(0); + return 0; } #endif /* sCAL */ @@ -1015,7 +1021,7 @@ png_get_pHYs(png_const_structrp png_ptr, png_const_inforp info_ptr, } } - return (retval); + return retval; } #endif /* pHYs */ @@ -1031,10 +1037,10 @@ png_get_PLTE(png_const_structrp png_ptr, png_inforp info_ptr, *palette = info_ptr->palette; *num_palette = info_ptr->num_palette; png_debug1(3, "num_palette = %d", *num_palette); - return (PNG_INFO_PLTE); + return PNG_INFO_PLTE; } - return (0); + return 0; } #ifdef PNG_sBIT_SUPPORTED @@ -1048,10 +1054,10 @@ png_get_sBIT(png_const_structrp png_ptr, png_inforp info_ptr, (info_ptr->valid & PNG_INFO_sBIT) != 0 && sig_bit != NULL) { *sig_bit = &(info_ptr->sig_bit); - return (PNG_INFO_sBIT); + return PNG_INFO_sBIT; } - return (0); + return 0; } #endif @@ -1062,7 +1068,7 @@ png_get_text(png_const_structrp png_ptr, png_inforp info_ptr, { if (png_ptr != NULL && info_ptr != NULL && info_ptr->num_text > 0) { - png_debug1(1, "in 0x%lx retrieval function", + png_debug1(1, "in text retrieval function, chunk typeid = 0x%lx", (unsigned long)png_ptr->chunk_name); if (text_ptr != NULL) @@ -1077,7 +1083,7 @@ png_get_text(png_const_structrp png_ptr, png_inforp info_ptr, if (num_text != NULL) *num_text = 0; - return(0); + return 0; } #endif @@ -1092,10 +1098,10 @@ png_get_tIME(png_const_structrp png_ptr, png_inforp info_ptr, (info_ptr->valid & PNG_INFO_tIME) != 0 && mod_time != NULL) { *mod_time = &(info_ptr->mod_time); - return (PNG_INFO_tIME); + return PNG_INFO_tIME; } - return (0); + return 0; } #endif @@ -1105,11 +1111,12 @@ png_get_tRNS(png_const_structrp png_ptr, png_inforp info_ptr, png_bytep *trans_alpha, int *num_trans, png_color_16p *trans_color) { png_uint_32 retval = 0; + + png_debug1(1, "in %s retrieval function", "tRNS"); + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS) != 0) { - png_debug1(1, "in %s retrieval function", "tRNS"); - if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) { if (trans_alpha != NULL) @@ -1141,7 +1148,7 @@ png_get_tRNS(png_const_structrp png_ptr, png_inforp info_ptr, } } - return (retval); + return retval; } #endif @@ -1156,7 +1163,7 @@ png_get_unknown_chunks(png_const_structrp png_ptr, png_inforp info_ptr, return info_ptr->unknown_chunks_num; } - return (0); + return 0; } #endif @@ -1252,7 +1259,7 @@ png_get_palette_max(png_const_structp png_ptr, png_const_infop info_ptr) if (png_ptr != NULL && info_ptr != NULL) return png_ptr->num_palette_max; - return (-1); + return -1; } # endif #endif diff --git a/thirdparty/libpng/pnglibconf.h b/thirdparty/libpng/pnglibconf.h index c7033ae176..83f09fbe77 100644 --- a/thirdparty/libpng/pnglibconf.h +++ b/thirdparty/libpng/pnglibconf.h @@ -1,8 +1,8 @@ /* pnglibconf.h - library build configuration */ -/* libpng version 1.6.40 */ +/* libpng version 1.6.43 */ -/* Copyright (c) 2018-2023 Cosmin Truta */ +/* Copyright (c) 2018-2024 Cosmin Truta */ /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */ /* This code is released under the libpng license. */ @@ -27,6 +27,7 @@ #define PNG_COLORSPACE_SUPPORTED #define PNG_CONSOLE_IO_SUPPORTED #define PNG_CONVERT_tIME_SUPPORTED +/*#undef PNG_DISABLE_ADLER32_CHECK_SUPPORTED*/ #define PNG_EASY_ACCESS_SUPPORTED /*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ #define PNG_ERROR_TEXT_SUPPORTED @@ -41,6 +42,10 @@ #define PNG_INCH_CONVERSIONS_SUPPORTED #define PNG_INFO_IMAGE_SUPPORTED #define PNG_IO_STATE_SUPPORTED +/*#undef PNG_MIPS_MMI_API_SUPPORTED*/ +/*#undef PNG_MIPS_MMI_CHECK_SUPPORTED*/ +/*#undef PNG_MIPS_MSA_API_SUPPORTED*/ +/*#undef PNG_MIPS_MSA_CHECK_SUPPORTED*/ #define PNG_MNG_FEATURES_SUPPORTED #define PNG_POINTER_INDEXING_SUPPORTED /*#undef PNG_POWERPC_VSX_API_SUPPORTED*/ diff --git a/thirdparty/libpng/pngpread.c b/thirdparty/libpng/pngpread.c index e283627b77..ffab19c08c 100644 --- a/thirdparty/libpng/pngpread.c +++ b/thirdparty/libpng/pngpread.c @@ -1,7 +1,7 @@ /* pngpread.c - read a png file in push mode * - * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -145,10 +145,10 @@ png_push_read_sig(png_structrp png_ptr, png_inforp info_ptr) num_to_check); png_ptr->sig_bytes = (png_byte)(png_ptr->sig_bytes + num_to_check); - if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check)) + if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check) != 0) { if (num_checked < 4 && - png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4) != 0) png_error(png_ptr, "Not a PNG file"); else @@ -295,6 +295,14 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) } #endif +#ifdef PNG_READ_eXIf_SUPPORTED + else if (png_ptr->chunk_name == png_eXIf) + { + PNG_PUSH_SAVE_BUFFER_IF_FULL + png_handle_eXIf(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif #ifdef PNG_READ_sRGB_SUPPORTED else if (chunk_name == png_sRGB) { @@ -1089,7 +1097,7 @@ png_voidp PNGAPI png_get_progressive_ptr(png_const_structrp png_ptr) { if (png_ptr == NULL) - return (NULL); + return NULL; return png_ptr->io_ptr; } diff --git a/thirdparty/libpng/pngpriv.h b/thirdparty/libpng/pngpriv.h index 7c19373f0b..9bfdb71342 100644 --- a/thirdparty/libpng/pngpriv.h +++ b/thirdparty/libpng/pngpriv.h @@ -1,7 +1,7 @@ /* pngpriv.h - private declarations for use inside libpng * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -36,7 +36,7 @@ * still required (as of 2011-05-02.) */ #ifndef _POSIX_SOURCE -# define _POSIX_SOURCE 1 /* Just the POSIX 1003.1 and C89 APIs */ +# define _POSIX_SOURCE 1 /* Just the POSIX 1003.1 and C89 APIs */ #endif #ifndef PNG_VERSION_INFO_ONLY @@ -190,13 +190,27 @@ #endif /* PNG_ARM_NEON_OPT > 0 */ #ifndef PNG_MIPS_MSA_OPT -# if defined(__mips_msa) && (__mips_isa_rev >= 5) && defined(PNG_ALIGNED_MEMORY_SUPPORTED) +# if defined(__mips_msa) && (__mips_isa_rev >= 5) && \ + defined(PNG_ALIGNED_MEMORY_SUPPORTED) # define PNG_MIPS_MSA_OPT 2 # else # define PNG_MIPS_MSA_OPT 0 # endif #endif +#ifndef PNG_MIPS_MMI_OPT +# ifdef PNG_MIPS_MMI +# if defined(__mips_loongson_mmi) && (_MIPS_SIM == _ABI64) && \ + defined(PNG_ALIGNED_MEMORY_SUPPORTED) +# define PNG_MIPS_MMI_OPT 1 +# else +# define PNG_MIPS_MMI_OPT 0 +# endif +# else +# define PNG_MIPS_MMI_OPT 0 +# endif +#endif + #ifndef PNG_POWERPC_VSX_OPT # if defined(__PPC64__) && defined(__ALTIVEC__) && defined(__VSX__) # define PNG_POWERPC_VSX_OPT 2 @@ -205,13 +219,21 @@ # endif #endif +#ifndef PNG_LOONGARCH_LSX_OPT +# if defined(__loongarch_sx) +# define PNG_LOONGARCH_LSX_OPT 1 +# else +# define PNG_LOONGARCH_LSX_OPT 0 +# endif +#endif + #ifndef PNG_INTEL_SSE_OPT # ifdef PNG_INTEL_SSE /* Only check for SSE if the build configuration has been modified to * enable SSE optimizations. This means that these optimizations will * be off by default. See contrib/intel for more details. */ -# if defined(__SSE4_1__) || defined(__AVX__) || defined(__SSSE3__) || \ +# if defined(__SSE4_1__) || defined(__AVX__) || defined(__SSSE3__) || \ defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || \ (defined(_M_IX86_FP) && _M_IX86_FP >= 2) # define PNG_INTEL_SSE_OPT 1 @@ -248,7 +270,6 @@ #endif #if PNG_MIPS_MSA_OPT > 0 -# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_msa # ifndef PNG_MIPS_MSA_IMPLEMENTATION # if defined(__mips_msa) # if defined(__clang__) @@ -264,11 +285,28 @@ # ifndef PNG_MIPS_MSA_IMPLEMENTATION # define PNG_MIPS_MSA_IMPLEMENTATION 1 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_mips # endif #else # define PNG_MIPS_MSA_IMPLEMENTATION 0 #endif /* PNG_MIPS_MSA_OPT > 0 */ +#if PNG_MIPS_MMI_OPT > 0 +# ifndef PNG_MIPS_MMI_IMPLEMENTATION +# if defined(__mips_loongson_mmi) && (_MIPS_SIM == _ABI64) +# define PNG_MIPS_MMI_IMPLEMENTATION 2 +# else /* !defined __mips_loongson_mmi || _MIPS_SIM != _ABI64 */ +# define PNG_MIPS_MMI_IMPLEMENTATION 0 +# endif /* __mips_loongson_mmi && _MIPS_SIM == _ABI64 */ +# endif /* !PNG_MIPS_MMI_IMPLEMENTATION */ + +# if PNG_MIPS_MMI_IMPLEMENTATION > 0 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_mips +# endif +#else +# define PNG_MIPS_MMI_IMPLEMENTATION 0 +#endif /* PNG_MIPS_MMI_OPT > 0 */ + #if PNG_POWERPC_VSX_OPT > 0 # define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_vsx # define PNG_POWERPC_VSX_IMPLEMENTATION 1 @@ -276,6 +314,12 @@ # define PNG_POWERPC_VSX_IMPLEMENTATION 0 #endif +#if PNG_LOONGARCH_LSX_OPT > 0 +# define PNG_FILTER_OPTIMIZATIONS png_init_filter_functions_lsx +# define PNG_LOONGARCH_LSX_IMPLEMENTATION 1 +#else +# define PNG_LOONGARCH_LSX_IMPLEMENTATION 0 +#endif /* Is this a build of a DLL where compilation of the object modules requires * different preprocessor settings to those required for a simple library? If @@ -514,18 +558,8 @@ */ # include <float.h> -# if (defined(__MWERKS__) && defined(macintosh)) || defined(applec) || \ - defined(THINK_C) || defined(__SC__) || defined(TARGET_OS_MAC) - /* We need to check that <math.h> hasn't already been included earlier - * as it seems it doesn't agree with <fp.h>, yet we should really use - * <fp.h> if possible. - */ -# if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__) -# include <fp.h> -# endif -# else -# include <math.h> -# endif +# include <math.h> + # if defined(_AMIGA) && defined(__SASC) && defined(_M68881) /* Amiga SAS/C: We must include builtin FPU functions when compiling using * MATH=68881 @@ -1306,7 +1340,7 @@ PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_neon,(png_row_infop row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); #endif -#if PNG_MIPS_MSA_OPT > 0 +#if PNG_MIPS_MSA_IMPLEMENTATION == 1 PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_msa,(png_row_infop row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_msa,(png_row_infop @@ -1323,6 +1357,23 @@ PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_msa,(png_row_infop row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); #endif +#if PNG_MIPS_MMI_IMPLEMENTATION > 0 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_mmi,(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_mmi,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + #if PNG_POWERPC_VSX_OPT > 0 PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_vsx,(png_row_infop row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); @@ -1355,6 +1406,23 @@ PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_sse2,(png_row_infop row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); #endif +#if PNG_LOONGARCH_LSX_IMPLEMENTATION == 1 +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_lsx,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +#endif + /* Choose the best filter to use and filter the row data */ PNG_INTERNAL_FUNCTION(void,png_write_find_filter,(png_structrp png_ptr, png_row_infop row_info),PNG_EMPTY); @@ -2094,17 +2162,27 @@ PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_neon, (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); #endif -#if PNG_MIPS_MSA_OPT > 0 -PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_msa, +#if PNG_MIPS_MSA_IMPLEMENTATION == 1 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_mips, (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); #endif +# if PNG_MIPS_MMI_IMPLEMENTATION > 0 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_mips, + (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); +# endif + # if PNG_INTEL_SSE_IMPLEMENTATION > 0 PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_sse2, (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); # endif #endif +#if PNG_LOONGARCH_LSX_OPT > 0 +PNG_INTERNAL_FUNCTION(void, png_init_filter_functions_lsx, + (png_structp png_ptr, unsigned int bpp), PNG_EMPTY); +#endif + PNG_INTERNAL_FUNCTION(png_uint_32, png_check_keyword, (png_structrp png_ptr, png_const_charp key, png_bytep new_key), PNG_EMPTY); diff --git a/thirdparty/libpng/pngread.c b/thirdparty/libpng/pngread.c index 96996ced5b..07a39df6e2 100644 --- a/thirdparty/libpng/pngread.c +++ b/thirdparty/libpng/pngread.c @@ -1,7 +1,7 @@ /* pngread.c - read a PNG file * - * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -568,7 +568,11 @@ png_read_row(png_structrp png_ptr, png_bytep row, png_bytep dsp_row) #endif #ifdef PNG_READ_TRANSFORMS_SUPPORTED - if (png_ptr->transformations) + if (png_ptr->transformations +# ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + || png_ptr->num_palette_max >= 0 +# endif + ) png_do_read_transformations(png_ptr, &row_info); #endif @@ -785,7 +789,7 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED /* Report invalid palette index; added at libng-1.5.10 */ if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && - png_ptr->num_palette_max > png_ptr->num_palette) + png_ptr->num_palette_max >= png_ptr->num_palette) png_benign_error(png_ptr, "Read palette index exceeding num_palette"); #endif @@ -1049,6 +1053,8 @@ void PNGAPI png_read_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, voidp params) { + png_debug(1, "in png_read_png"); + if (png_ptr == NULL || info_ptr == NULL) return; diff --git a/thirdparty/libpng/pngrtran.c b/thirdparty/libpng/pngrtran.c index 238f5afe7e..1526123e02 100644 --- a/thirdparty/libpng/pngrtran.c +++ b/thirdparty/libpng/pngrtran.c @@ -1,7 +1,7 @@ /* pngrtran.c - transforms the data in a row for PNG readers * - * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -290,21 +290,20 @@ png_set_alpha_mode_fixed(png_structrp png_ptr, int mode, int compose = 0; png_fixed_point file_gamma; - png_debug(1, "in png_set_alpha_mode"); + png_debug(1, "in png_set_alpha_mode_fixed"); if (png_rtran_ok(png_ptr, 0) == 0) return; output_gamma = translate_gamma_flags(png_ptr, output_gamma, 1/*screen*/); - /* Validate the value to ensure it is in a reasonable range. The value + /* Validate the value to ensure it is in a reasonable range. The value * is expected to be 1 or greater, but this range test allows for some - * viewing correction values. The intent is to weed out users of this API - * who use the inverse of the gamma value accidentally! Since some of these - * values are reasonable this may have to be changed: + * viewing correction values. The intent is to weed out the API users + * who might use the inverse of the gamma value accidentally! * - * 1.6.x: changed from 0.07..3 to 0.01..100 (to accommodate the optimal 16-bit - * gamma of 36, and its reciprocal.) + * In libpng 1.6.0, we changed from 0.07..3 to 0.01..100, to accommodate + * the optimal 16-bit gamma of 36 and its reciprocal. */ if (output_gamma < 1000 || output_gamma > 10000000) png_error(png_ptr, "output gamma out of expected range"); @@ -441,7 +440,7 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette, int i; png_ptr->quantize_index = (png_bytep)png_malloc(png_ptr, - (png_alloc_size_t)((png_uint_32)num_palette * (sizeof (png_byte)))); + (png_alloc_size_t)num_palette); for (i = 0; i < num_palette; i++) png_ptr->quantize_index[i] = (png_byte)i; } @@ -458,7 +457,7 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette, /* Initialize an array to sort colors */ png_ptr->quantize_sort = (png_bytep)png_malloc(png_ptr, - (png_alloc_size_t)((png_uint_32)num_palette * (sizeof (png_byte)))); + (png_alloc_size_t)num_palette); /* Initialize the quantize_sort array */ for (i = 0; i < num_palette; i++) @@ -592,11 +591,9 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette, /* Initialize palette index arrays */ png_ptr->index_to_palette = (png_bytep)png_malloc(png_ptr, - (png_alloc_size_t)((png_uint_32)num_palette * - (sizeof (png_byte)))); + (png_alloc_size_t)num_palette); png_ptr->palette_to_index = (png_bytep)png_malloc(png_ptr, - (png_alloc_size_t)((png_uint_32)num_palette * - (sizeof (png_byte)))); + (png_alloc_size_t)num_palette); /* Initialize the sort array */ for (i = 0; i < num_palette; i++) @@ -761,12 +758,11 @@ png_set_quantize(png_structrp png_ptr, png_colorp palette, size_t num_entries = ((size_t)1 << total_bits); png_ptr->palette_lookup = (png_bytep)png_calloc(png_ptr, - (png_alloc_size_t)(num_entries * (sizeof (png_byte)))); + (png_alloc_size_t)(num_entries)); - distance = (png_bytep)png_malloc(png_ptr, (png_alloc_size_t)(num_entries * - (sizeof (png_byte)))); + distance = (png_bytep)png_malloc(png_ptr, (png_alloc_size_t)num_entries); - memset(distance, 0xff, num_entries * (sizeof (png_byte))); + memset(distance, 0xff, num_entries); for (i = 0; i < num_palette; i++) { @@ -970,7 +966,7 @@ void PNGFAPI png_set_rgb_to_gray_fixed(png_structrp png_ptr, int error_action, png_fixed_point red, png_fixed_point green) { - png_debug(1, "in png_set_rgb_to_gray"); + png_debug(1, "in png_set_rgb_to_gray_fixed"); /* Need the IHDR here because of the check on color_type below. */ /* TODO: fix this */ diff --git a/thirdparty/libpng/pngrutil.c b/thirdparty/libpng/pngrutil.c index 068ab193a3..d31dc21dae 100644 --- a/thirdparty/libpng/pngrutil.c +++ b/thirdparty/libpng/pngrutil.c @@ -1,7 +1,7 @@ /* pngrutil.c - utilities to read a PNG file * - * Copyright (c) 2018-2022 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -26,7 +26,7 @@ png_get_uint_31(png_const_structrp png_ptr, png_const_bytep buf) if (uval > PNG_UINT_31_MAX) png_error(png_ptr, "PNG unsigned integer out of range"); - return (uval); + return uval; } #if defined(PNG_READ_gAMA_SUPPORTED) || defined(PNG_READ_cHRM_SUPPORTED) @@ -140,7 +140,7 @@ png_read_sig(png_structrp png_ptr, png_inforp info_ptr) if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check) != 0) { if (num_checked < 4 && - png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4) != 0) png_error(png_ptr, "Not a PNG file"); else png_error(png_ptr, "PNG file corrupted by ASCII conversion"); @@ -171,7 +171,7 @@ png_read_chunk_header(png_structrp png_ptr) /* Put the chunk name into png_ptr->chunk_name. */ png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(buf+4); - png_debug2(0, "Reading %lx chunk, length = %lu", + png_debug2(0, "Reading chunk typeid = 0x%lx, length = %lu", (unsigned long)png_ptr->chunk_name, (unsigned long)length); /* Reset the crc and run it over the chunk name. */ @@ -238,10 +238,10 @@ png_crc_finish(png_structrp png_ptr, png_uint_32 skip) else png_chunk_error(png_ptr, "CRC error"); - return (1); + return 1; } - return (0); + return 0; } /* Compare the CRC stored in the PNG file with that calculated by libpng from @@ -277,11 +277,11 @@ png_crc_error(png_structrp png_ptr) if (need_crc != 0) { crc = png_get_uint_32(crc_bytes); - return ((int)(crc != png_ptr->crc)); + return crc != png_ptr->crc; } else - return (0); + return 0; } #if defined(PNG_READ_iCCP_SUPPORTED) || defined(PNG_READ_iTXt_SUPPORTED) ||\ @@ -421,8 +421,7 @@ png_inflate_claim(png_structrp png_ptr, png_uint_32 owner) png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED; } -#if ZLIB_VERNUM >= 0x1290 && \ - defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_IGNORE_ADLER32) +#ifdef PNG_DISABLE_ADLER32_CHECK_SUPPORTED if (((png_ptr->options >> PNG_IGNORE_ADLER32) & 3) == PNG_OPTION_ON) /* Turn off validation of the ADLER32 checksum in IDAT chunks */ ret = inflateValidate(&png_ptr->zstream, 0); diff --git a/thirdparty/libpng/pngset.c b/thirdparty/libpng/pngset.c index 3fc31feb0c..eb1c8c7a35 100644 --- a/thirdparty/libpng/pngset.c +++ b/thirdparty/libpng/pngset.c @@ -1,7 +1,7 @@ /* pngset.c - storage of image information into info struct * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -763,11 +763,11 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr, { int i; - png_debug1(1, "in %lx storage function", png_ptr == NULL ? 0xabadca11U : - (unsigned long)png_ptr->chunk_name); + png_debug1(1, "in text storage function, chunk typeid = 0x%lx", + png_ptr == NULL ? 0xabadca11UL : (unsigned long)png_ptr->chunk_name); if (png_ptr == NULL || info_ptr == NULL || num_text <= 0 || text_ptr == NULL) - return(0); + return 0; /* Make sure we have enough space in the "text" array in info_struct * to hold all of the incoming text_ptr objects. This compare can't overflow @@ -947,7 +947,7 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr, png_debug1(3, "transferred text chunk %d", info_ptr->num_text); } - return(0); + return 0; } #endif @@ -1063,6 +1063,8 @@ png_set_sPLT(png_const_structrp png_ptr, { png_sPLT_tp np; + png_debug1(1, "in %s storage function", "sPLT"); + if (png_ptr == NULL || info_ptr == NULL || nentries <= 0 || entries == NULL) return; @@ -1537,7 +1539,7 @@ void PNGAPI png_set_rows(png_const_structrp png_ptr, png_inforp info_ptr, png_bytepp row_pointers) { - png_debug1(1, "in %s storage function", "rows"); + png_debug(1, "in png_set_rows"); if (png_ptr == NULL || info_ptr == NULL) return; @@ -1556,6 +1558,8 @@ png_set_rows(png_const_structrp png_ptr, png_inforp info_ptr, void PNGAPI png_set_compression_buffer_size(png_structrp png_ptr, size_t size) { + png_debug(1, "in png_set_compression_buffer_size"); + if (png_ptr == NULL) return; @@ -1627,6 +1631,8 @@ void PNGAPI png_set_user_limits(png_structrp png_ptr, png_uint_32 user_width_max, png_uint_32 user_height_max) { + png_debug(1, "in png_set_user_limits"); + /* Images with dimensions larger than these limits will be * rejected by png_set_IHDR(). To accept any PNG datastream * regardless of dimensions, set both limits to 0x7fffffff. @@ -1642,6 +1648,8 @@ png_set_user_limits(png_structrp png_ptr, png_uint_32 user_width_max, void PNGAPI png_set_chunk_cache_max(png_structrp png_ptr, png_uint_32 user_chunk_cache_max) { + png_debug(1, "in png_set_chunk_cache_max"); + if (png_ptr != NULL) png_ptr->user_chunk_cache_max = user_chunk_cache_max; } @@ -1651,6 +1659,8 @@ void PNGAPI png_set_chunk_malloc_max(png_structrp png_ptr, png_alloc_size_t user_chunk_malloc_max) { + png_debug(1, "in png_set_chunk_malloc_max"); + if (png_ptr != NULL) png_ptr->user_chunk_malloc_max = user_chunk_malloc_max; } diff --git a/thirdparty/libpng/pngtrans.c b/thirdparty/libpng/pngtrans.c index 1100f46ebe..62cb21edf1 100644 --- a/thirdparty/libpng/pngtrans.c +++ b/thirdparty/libpng/pngtrans.c @@ -1,7 +1,7 @@ /* pngtrans.c - transforms the data in a row (used by both readers and writers) * - * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -103,10 +103,10 @@ png_set_interlace_handling(png_structrp png_ptr) if (png_ptr != 0 && png_ptr->interlaced != 0) { png_ptr->transformations |= PNG_INTERLACE; - return (7); + return 7; } - return (1); + return 1; } #endif @@ -498,6 +498,8 @@ png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) png_bytep dp = row; /* destination pointer */ png_bytep ep = row + row_info->rowbytes; /* One beyond end of row */ + png_debug(1, "in png_do_strip_channel"); + /* At the start sp will point to the first byte to copy and dp to where * it is copied to. ep always points just beyond the end of the row, so * the loop simply copies (channels-1) channels until sp reaches ep. @@ -698,6 +700,8 @@ png_do_bgr(png_row_infop row_info, png_bytep row) void /* PRIVATE */ png_do_check_palette_indexes(png_structrp png_ptr, png_row_infop row_info) { + png_debug(1, "in png_do_check_palette_indexes"); + if (png_ptr->num_palette < (1 << row_info->bit_depth) && png_ptr->num_palette > 0) /* num_palette can be 0 in MNG files */ { @@ -708,7 +712,7 @@ png_do_check_palette_indexes(png_structrp png_ptr, png_row_infop row_info) * forms produced on either GCC or MSVC. */ int padding = PNG_PADBITS(row_info->pixel_depth, row_info->width); - png_bytep rp = png_ptr->row_buf + row_info->rowbytes - 1; + png_bytep rp = png_ptr->row_buf + row_info->rowbytes; switch (row_info->bit_depth) { @@ -833,7 +837,7 @@ png_voidp PNGAPI png_get_user_transform_ptr(png_const_structrp png_ptr) { if (png_ptr == NULL) - return (NULL); + return NULL; return png_ptr->user_transform_ptr; } diff --git a/thirdparty/libpng/pngwrite.c b/thirdparty/libpng/pngwrite.c index 32f4bfbe7d..77e412f43d 100644 --- a/thirdparty/libpng/pngwrite.c +++ b/thirdparty/libpng/pngwrite.c @@ -1,7 +1,7 @@ /* pngwrite.c - general routines to write a PNG file * - * Copyright (c) 2018-2023 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -369,7 +369,8 @@ png_write_end(png_structrp png_ptr, png_inforp info_ptr) png_error(png_ptr, "No IDATs written into file"); #ifdef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED - if (png_ptr->num_palette_max > png_ptr->num_palette) + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max >= png_ptr->num_palette) png_benign_error(png_ptr, "Wrote palette index exceeding num_palette"); #endif @@ -714,12 +715,12 @@ png_write_row(png_structrp png_ptr, png_const_bytep row) /* 1.5.6: moved from png_struct to be a local structure: */ png_row_info row_info; - if (png_ptr == NULL) - return; - png_debug2(1, "in png_write_row (row %u, pass %d)", png_ptr->row_number, png_ptr->pass); + if (png_ptr == NULL) + return; + /* Initialize transformations and other stuff if first time */ if (png_ptr->row_number == 0 && png_ptr->pass == 0) { @@ -1210,6 +1211,8 @@ png_set_compression_strategy(png_structrp png_ptr, int strategy) void PNGAPI png_set_compression_window_bits(png_structrp png_ptr, int window_bits) { + png_debug(1, "in png_set_compression_window_bits"); + if (png_ptr == NULL) return; @@ -1293,6 +1296,8 @@ png_set_text_compression_strategy(png_structrp png_ptr, int strategy) void PNGAPI png_set_text_compression_window_bits(png_structrp png_ptr, int window_bits) { + png_debug(1, "in png_set_text_compression_window_bits"); + if (png_ptr == NULL) return; @@ -1330,6 +1335,8 @@ png_set_text_compression_method(png_structrp png_ptr, int method) void PNGAPI png_set_write_status_fn(png_structrp png_ptr, png_write_status_ptr write_row_fn) { + png_debug(1, "in png_set_write_status_fn"); + if (png_ptr == NULL) return; @@ -1357,6 +1364,8 @@ void PNGAPI png_write_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, voidp params) { + png_debug(1, "in png_write_png"); + if (png_ptr == NULL || info_ptr == NULL) return; diff --git a/thirdparty/libpng/pngwutil.c b/thirdparty/libpng/pngwutil.c index 01f0607c70..14cc4ce367 100644 --- a/thirdparty/libpng/pngwutil.c +++ b/thirdparty/libpng/pngwutil.c @@ -1,7 +1,7 @@ /* pngwutil.c - utilities to write a PNG file * - * Copyright (c) 2018-2022 Cosmin Truta + * Copyright (c) 2018-2024 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -2311,7 +2311,7 @@ png_setup_sub_row(png_structrp png_ptr, png_uint_32 bpp, break; } - return (sum); + return sum; } static void /* PRIVATE */ @@ -2361,7 +2361,7 @@ png_setup_up_row(png_structrp png_ptr, size_t row_bytes, size_t lmins) break; } - return (sum); + return sum; } static void /* PRIVATE */ png_setup_up_row_only(png_structrp png_ptr, size_t row_bytes) @@ -2417,7 +2417,7 @@ png_setup_avg_row(png_structrp png_ptr, png_uint_32 bpp, break; } - return (sum); + return sum; } static void /* PRIVATE */ png_setup_avg_row_only(png_structrp png_ptr, png_uint_32 bpp, @@ -2500,7 +2500,7 @@ png_setup_paeth_row(png_structrp png_ptr, png_uint_32 bpp, break; } - return (sum); + return sum; } static void /* PRIVATE */ png_setup_paeth_row_only(png_structrp png_ptr, png_uint_32 bpp, |