diff options
107 files changed, 1134 insertions, 730 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a0e919c47..dd51d7b419 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,12 +17,12 @@ repos: platform/android/java/lib/src/com/.* ) - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.2.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 hooks: - - id: black - files: (\.py$|SConstruct|SCsub) - types_or: [text] + - id: ruff + args: [--fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.971 @@ -146,6 +146,7 @@ repos: exclude: | (?x)^( core/math/bvh_.*\.inc$| + platform/(?!android|ios|linuxbsd|macos|web|windows)\w+/.*| platform/android/java/lib/src/com/.*| platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView\.java$| platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper\.java$| @@ -186,3 +187,15 @@ repos: language: python entry: python3 misc/scripts/dotnet_format.py types_or: [c#] + +# End of upstream Godot pre-commit hooks. +# +# Keep this separation to let downstream forks add their own hooks to this file, +# without running into merge conflicts when rebasing on latest upstream. +# +# Start of downstream pre-commit hooks. +# +# This is still the "repo: local" scope, so new local hooks can be defined directly at this indentation: +# - id: new-local-hook +# To add external repo hooks, bring the indentation back to: +# - repo: my-remote-hook diff --git a/SConstruct b/SConstruct index afa44fef4a..7e51ef4fc4 100644 --- a/SConstruct +++ b/SConstruct @@ -10,11 +10,11 @@ import os import pickle import sys import time -from types import ModuleType from collections import OrderedDict -from importlib.util import spec_from_file_location, module_from_spec -from SCons import __version__ as scons_raw_version +from importlib.util import module_from_spec, spec_from_file_location +from types import ModuleType +from SCons import __version__ as scons_raw_version # Explicitly resolve the helper modules, this is done to avoid clash with # modules of the same name that might be randomly added (e.g. someone adding @@ -53,12 +53,12 @@ _helper_module("core.core_builders", "core/core_builders.py") _helper_module("main.main_builders", "main/main_builders.py") # Local -import methods -import glsl_builders import gles3_builders +import glsl_builders +import methods import scu_builders -from methods import print_warning, print_error -from platform_methods import architectures, architecture_aliases +from methods import print_error, print_warning +from platform_methods import architecture_aliases, architectures if ARGUMENTS.get("target", "editor") == "editor": _helper_module("editor.editor_builders", "editor/editor_builders.py") @@ -68,7 +68,7 @@ if ARGUMENTS.get("target", "editor") == "editor": # <https://github.com/python/cpython/issues/73245> if sys.stdout.isatty() and sys.platform == "win32": try: - from ctypes import windll, byref, WinError # type: ignore + from ctypes import WinError, byref, windll # type: ignore from ctypes.wintypes import DWORD # type: ignore stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) @@ -562,7 +562,7 @@ if env["build_profile"] != "": dbo = ft["disabled_build_options"] for c in dbo: env[c] = dbo[c] - except: + except json.JSONDecodeError: print_error('Failed to open feature build profile: "{}"'.format(env["build_profile"])) Exit(255) @@ -570,7 +570,7 @@ if env["build_profile"] != "": # These can sometimes override default options. flag_list = platform_flags[env["platform"]] for f in flag_list: - if not (f[0] in ARGUMENTS) or ARGUMENTS[f[0]] == "auto": # Allow command line to override platform flags + if f[0] not in ARGUMENTS or ARGUMENTS[f[0]] == "auto": # Allow command line to override platform flags env[f[0]] = f[1] # 'dev_mode' and 'production' are aliases to set default options if they haven't been @@ -591,7 +591,7 @@ if env["production"]: # Run SCU file generation script if in a SCU build. if env["scu_build"]: max_includes_per_scu = 8 - if env.dev_build == True: + if env.dev_build: max_includes_per_scu = 1024 read_scu_limit = int(env["scu_limit"]) @@ -984,7 +984,7 @@ GLSL_BUILDERS = { env.Append(BUILDERS=GLSL_BUILDERS) scons_cache_path = os.environ.get("SCONS_CACHE") -if scons_cache_path != None: +if scons_cache_path is not None: CacheDir(scons_cache_path) print("Scons cache enabled... (path: '" + scons_cache_path + "')") diff --git a/core/SCsub b/core/SCsub index a61c0b5fc2..52f3506416 100644 --- a/core/SCsub +++ b/core/SCsub @@ -2,9 +2,11 @@ Import("env") +import os + import core_builders + import methods -import os env.core_sources = [] @@ -188,9 +190,7 @@ def version_info_builder(target, source, env): #define VERSION_WEBSITE "{website}" #define VERSION_DOCS_BRANCH "{docs_branch}" #define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH -""".format( - **env.version_info - ) +""".format(**env.version_info) ) @@ -206,9 +206,7 @@ def version_hash_builder(target, source, env): const char *const VERSION_HASH = "{git_hash}"; const uint64_t VERSION_TIMESTAMP = {git_timestamp}; -""".format( - **env.version_info - ) +""".format(**env.version_info) ) diff --git a/core/core_builders.py b/core/core_builders.py index a401f03693..a3dc935b79 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -180,7 +180,7 @@ def make_license_header(target, source, env): return line def next_tag(self): - if not ":" in self.current: + if ":" not in self.current: return ("", []) tag, line = self.current.split(":", 1) lines = [line.strip()] @@ -206,7 +206,7 @@ def make_license_header(target, source, env): if not tag or not reader.current: # end of a paragraph start a new part - if "License" in part and not "Files" in part: + if "License" in part and "Files" not in part: # no Files tag in this one, so assume standalone license license_list.append(part["License"]) part = {} @@ -298,13 +298,13 @@ def make_license_header(target, source, env): f.write("const int LICENSE_COUNT = " + str(len(license_list)) + ";\n") f.write("const char *const LICENSE_NAMES[] = {\n") - for l in license_list: - f.write('\t"' + escape_string(l[0]) + '",\n') + for license in license_list: + f.write('\t"' + escape_string(license[0]) + '",\n') f.write("};\n\n") f.write("const char *const LICENSE_BODIES[] = {\n\n") - for l in license_list: - for line in l[1:]: + for license in license_list: + for line in license[1:]: if line == ".": f.write('\t"\\n"\n') else: diff --git a/core/extension/SCsub b/core/extension/SCsub index 901ceec1e8..6ab2d2b0a6 100644 --- a/core/extension/SCsub +++ b/core/extension/SCsub @@ -2,8 +2,8 @@ Import("env") -import make_wrappers import make_interface_dumper +import make_wrappers env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run)) env.CommandNoCache( diff --git a/core/extension/make_wrappers.py b/core/extension/make_wrappers.py index 655b90d2b1..54f4fd5579 100644 --- a/core/extension/make_wrappers.py +++ b/core/extension/make_wrappers.py @@ -10,7 +10,6 @@ _FORCE_INLINE_ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\ def generate_mod_version(argcount, const=False, returns=False): s = proto_mod sproto = str(argcount) - method_info = "" if returns: sproto += "R" s = s.replace("$RETTYPE", "m_ret, ") @@ -68,7 +67,6 @@ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\ def generate_ex_version(argcount, const=False, returns=False): s = proto_ex sproto = str(argcount) - method_info = "" if returns: sproto += "R" s = s.replace("$RETTYPE", "m_ret, ") diff --git a/core/input/SCsub b/core/input/SCsub index da29637135..d8e6f33156 100644 --- a/core/input/SCsub +++ b/core/input/SCsub @@ -4,7 +4,6 @@ Import("env") import input_builders - # Order matters here. Higher index controller database files write on top of lower index database files. controller_databases = [ "gamecontrollerdb.txt", diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index 1502b2807c..83ebdc5a84 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -350,6 +350,8 @@ public: return triangles; } + // Assumes cartesian coordinate system with +x to the right, +y up. + // If using screen coordinates (+x to the right, +y down) the result will need to be flipped. static bool is_polygon_clockwise(const Vector<Vector2> &p_polygon) { int c = p_polygon.size(); if (c < 3) { diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index e25703b93f..3d93ce8e73 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -522,7 +522,7 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require #ifdef TOOLS_ENABLED if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { - ERR_PRINT_ONCE(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); + ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); } else { ObjectGDExtension *extension = get_placeholder_extension(ti->name); return (Object *)extension->create_instance(extension->class_userdata); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 4b32acaaa0..bcab80ea94 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1571,9 +1571,6 @@ <member name="Geometry3D" type="Geometry3D" setter="" getter=""> The [Geometry3D] singleton. </member> - <member name="GodotSharp" type="GodotSharp" setter="" getter=""> - The [GodotSharp] singleton. - </member> <member name="IP" type="IP" setter="" getter=""> The [IP] singleton. </member> diff --git a/doc/classes/Geometry2D.xml b/doc/classes/Geometry2D.xml index dfcf299fe6..f21696d02c 100644 --- a/doc/classes/Geometry2D.xml +++ b/doc/classes/Geometry2D.xml @@ -116,6 +116,7 @@ <param index="0" name="polygon" type="PackedVector2Array" /> <description> Returns [code]true[/code] if [param polygon]'s vertices are ordered in clockwise order, otherwise returns [code]false[/code]. + [b]Note:[/b] Assumes a Cartesian coordinate system where [code]+x[/code] is right and [code]+y[/code] is up. If using screen coordinates ([code]+y[/code] is down), the result will need to be flipped (i.e. a [code]true[/code] result will indicate counter-clockwise). </description> </method> <method name="line_intersects_line"> diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py index a23c11b585..57c03bfdee 100755 --- a/doc/tools/doc_status.py +++ b/doc/tools/doc_status.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import fnmatch -import os -import sys -import re import math +import os import platform +import re +import sys import xml.etree.ElementTree as ET from typing import Dict, List, Set @@ -286,7 +286,7 @@ class ClassStatus: status.progresses[tag.tag].increment(is_deprecated or is_experimental or has_descr) elif tag.tag in ["constants", "members", "theme_items"]: for sub_tag in list(tag): - if not sub_tag.text is None: + if sub_tag.text is not None: is_deprecated = "deprecated" in sub_tag.attrib is_experimental = "experimental" in sub_tag.attrib has_descr = len(sub_tag.text.strip()) > 0 diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 43e1afe720..bc379f8553 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -4,17 +4,16 @@ import argparse import os -import platform import re import sys import xml.etree.ElementTree as ET from collections import OrderedDict -from typing import List, Dict, TextIO, Tuple, Optional, Any, Union +from typing import Any, Dict, List, Optional, TextIO, Tuple, Union # Import hardcoded version information from version.py root_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") sys.path.append(root_directory) # Include the root directory -import version +import version # noqa: E402 # $DOCS_URL/path/to/page.html(#fragment-tag) GODOT_DOCS_PATTERN = re.compile(r"^\$DOCS_URL/(.*)\.html(#.*)?$") @@ -706,7 +705,7 @@ def main() -> None: # <https://github.com/python/cpython/issues/73245> if should_color and sys.stdout.isatty() and sys.platform == "win32": try: - from ctypes import windll, byref, WinError # type: ignore + from ctypes import WinError, byref, windll # type: ignore from ctypes.wintypes import DWORD # type: ignore stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) @@ -1413,7 +1412,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: operator_anchor = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}" for parameter in m.parameters: operator_anchor += f"_{parameter.type_name.type_name}" - operator_anchor += f":\n\n" + operator_anchor += ":\n\n" f.write(operator_anchor) f.write(".. rst-class:: classref-operator\n\n") @@ -1553,7 +1552,7 @@ def make_method_signature( out += f":ref:`{op_name}<class_{class_def.name}_{ref_type}_{sanitize_operator_name(definition.name, state)}" for parameter in definition.parameters: out += f"_{parameter.type_name.type_name}" - out += f">`" + out += ">`" elif ref_type == "method": ref_type_qualifier = "" if definition.name.startswith("_"): diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index c656ee3cc7..5cde4ee9de 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -70,21 +70,28 @@ enum SceneUniformLocation { SCENE_GLOBALS_UNIFORM_LOCATION, SCENE_DATA_UNIFORM_LOCATION, SCENE_MATERIAL_UNIFORM_LOCATION, - SCENE_EMPTY, // Unused, put here to avoid conflicts with SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. + SCENE_EMPTY1, // Unused, put here to avoid conflicts with SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. SCENE_OMNILIGHT_UNIFORM_LOCATION, SCENE_SPOTLIGHT_UNIFORM_LOCATION, SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, SCENE_MULTIVIEW_UNIFORM_LOCATION, SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION, SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION, + SCENE_EMPTY2, // Unused, put here to avoid conflicts with SKY_MULTIVIEW_UNIFORM_LOCATION. }; enum SkyUniformLocation { SKY_TONEMAP_UNIFORM_LOCATION, SKY_GLOBALS_UNIFORM_LOCATION, - SKY_EMPTY, // Unused, put here to avoid conflicts with SCENE_DATA_UNIFORM_LOCATION. + SKY_EMPTY1, // Unused, put here to avoid conflicts with SCENE_DATA_UNIFORM_LOCATION. SKY_MATERIAL_UNIFORM_LOCATION, SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, + SKY_EMPTY2, // Unused, put here to avoid conflicts with SCENE_OMNILIGHT_UNIFORM_LOCATION. + SKY_EMPTY3, // Unused, put here to avoid conflicts with SCENE_SPOTLIGHT_UNIFORM_LOCATION. + SKY_EMPTY4, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. + SKY_EMPTY5, // Unused, put here to avoid conflicts with SCENE_MULTIVIEW_UNIFORM_LOCATION. + SKY_EMPTY6, // Unused, put here to avoid conflicts with SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION. + SKY_EMPTY7, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION. SKY_MULTIVIEW_UNIFORM_LOCATION, }; diff --git a/drivers/gles3/shaders/sky.glsl b/drivers/gles3/shaders/sky.glsl index 26549901a6..9de65ba960 100644 --- a/drivers/gles3/shaders/sky.glsl +++ b/drivers/gles3/shaders/sky.glsl @@ -116,7 +116,7 @@ uniform float z_far; uniform uint directional_light_count; #ifdef USE_MULTIVIEW -layout(std140) uniform MultiviewData { // ubo:5 +layout(std140) uniform MultiviewData { // ubo:11 highp mat4 projection_matrix_view[MAX_VIEWS]; highp mat4 inv_projection_matrix_view[MAX_VIEWS]; highp vec4 eye_offset[MAX_VIEWS]; diff --git a/editor/SCsub b/editor/SCsub index e613a71238..029048969a 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -4,11 +4,12 @@ Import("env") env.editor_sources = [] -import os import glob +import os + import editor_builders -import methods +import methods if env.editor_build: # Generate doc data paths diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index ebb63dd57c..045774080c 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -7312,17 +7312,19 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_child(zoom); timeline->set_zoom(zoom); + ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F); + auto_fit = memnew(Button); auto_fit->set_flat(true); auto_fit->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit)); - auto_fit->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + auto_fit->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); bottom_hb->add_child(auto_fit); auto_fit_bezier = memnew(Button); auto_fit_bezier->set_flat(true); auto_fit_bezier->set_visible(false); auto_fit_bezier->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier)); - auto_fit_bezier->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + auto_fit_bezier->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); bottom_hb->add_child(auto_fit_bezier); edit = memnew(MenuButton); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index b58e82b7e7..331dacf6ad 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -344,25 +344,6 @@ void DocTools::merge_from(const DocTools &p_data) { merge_theme_properties(c.theme_properties, cf.theme_properties); merge_operators(c.operators, cf.operators); - -#ifndef MODULE_MONO_ENABLED - // The Mono module defines some properties that we want to keep when - // re-generating docs with a non-Mono build, to prevent pointless diffs - // (and loss of descriptions) depending on the config of the doc writer. - // We use a horrible hack to force keeping the relevant properties, - // hardcoded below. At least it's an ad hoc hack... ¯\_(ツ)_/¯ - // Don't show this to your kids. - if (c.name == "@GlobalScope") { - // Retrieve GodotSharp singleton. - for (int j = 0; j < cf.properties.size(); j++) { - if (cf.properties[j].name == "GodotSharp") { - c.properties.push_back(cf.properties[j]); - c.properties.sort(); - break; - } - } - } -#endif } } diff --git a/editor/editor_builders.py b/editor/editor_builders.py index cfe6e56b49..625d570666 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -7,6 +7,7 @@ import subprocess import tempfile import uuid import zlib + from methods import print_warning diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 68fcc613ee..581294d677 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6297,6 +6297,11 @@ EditorNode::EditorNode() { EditorSettings::create(); } + ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L); + ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L); + ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G); + ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G); + FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename")); _update_vsync_mode(); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index a06dd310ea..fa0dad41dc 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -1368,22 +1368,35 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d } bool scene_drop = true; + bool audio_drop = true; for (int i = 0; i < files.size(); i++) { String ftype = EditorFileSystem::get_singleton()->get_file_type(files[i]); if (ftype != "PackedScene") { scene_drop = false; - break; + } + if (audio_drop && !ClassDB::is_parent_class(ftype, "AudioStream")) { + audio_drop = false; } } if (scene_drop) { tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM); - } else { + return true; + } + + if (audio_drop) { if (files.size() > 1) { - return false; + tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN); + } else { + tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM); } - tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); + return true; + } + + if (files.size() > 1) { + return false; } + tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); return true; } diff --git a/editor/icons/SCsub b/editor/icons/SCsub index d2d752cff4..0d9ac43c46 100644 --- a/editor/icons/SCsub +++ b/editor/icons/SCsub @@ -3,8 +3,8 @@ Import("env") import os -import editor_icons_builders +import editor_icons_builders env["BUILDERS"]["MakeEditorIconsBuilder"] = Builder( action=env.Run(editor_icons_builders.make_editor_icons_action), diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 070c44419a..23c22a8049 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2329,6 +2329,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import } script_ext_hint += "*." + E; } + bool trimming_defaults_on = p_path.get_extension().to_lower() == "fbx"; r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); @@ -2342,7 +2343,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 30)); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/trimming"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/trimming"), trimming_defaults_on)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/remove_immutable_tracks"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import_rest_as_RESET"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), "")); diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 8cf104725a..487b8fc175 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -269,9 +269,9 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { Vector<uint8_t> data; if (use_webp) { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image); } else { - data = Image::png_packer(p_image->get_image_from_mipmap(i)); + data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image); } int data_len = data.size(); f->store_32(data_len); @@ -289,7 +289,7 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I f->store_32(p_image->get_format()); for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { - Vector<uint8_t> data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + Vector<uint8_t> data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality); int data_len = data.size(); f->store_32(data_len); diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index fbe76e1d5c..2d87e6592f 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -188,10 +188,20 @@ void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_ params->checked.clear(); params->base_options_path = p_path; + HashMap<StringName, Variant> import_options; + List<String> section_keys; + p_config->get_section_keys("params", §ion_keys); + for (const String §ion_key : section_keys) { + import_options[section_key] = p_config->get_value("params", section_key); + } + if (params->importer.is_valid()) { + params->importer->handle_compatibility_options(import_options); + } + for (const ResourceImporter::ImportOption &E : options) { params->properties.push_back(E.option); - if (p_config.is_valid() && p_config->has_section_key("params", E.option.name)) { - params->values[E.option.name] = p_config->get_value("params", E.option.name); + if (p_config.is_valid() && import_options.has(E.option.name)) { + params->values[E.option.name] = import_options[E.option.name]; } else { params->values[E.option.name] = E.default_value; } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index a44430ca7f..ee96de8f23 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -46,10 +46,12 @@ #include "editor/scene_tree_dock.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" +#include "scene/2d/audio_stream_player_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite_2d.h" #include "scene/2d/touch_screen_button.h" +#include "scene/gui/base_button.h" #include "scene/gui/flow_container.h" #include "scene/gui/grid_container.h" #include "scene/gui/separator.h" @@ -966,7 +968,7 @@ void CanvasItemEditor::_add_node_pressed(int p_result) { } } -void CanvasItemEditor::_node_created(Node *p_node) { +void CanvasItemEditor::_adjust_new_node_position(Node *p_node) { if (node_create_position == Point2()) { return; } @@ -5161,7 +5163,7 @@ CanvasItemEditor::CanvasItemEditor() { editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed)); - SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); + SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position)); SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); // Add some margin to the sides for better esthetics. @@ -5417,7 +5419,7 @@ CanvasItemEditor::CanvasItemEditor() { lock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED)); lock_button->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); + lock_button->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes")); unlock_button = memnew(Button); unlock_button->set_theme_type_variation("FlatButton"); @@ -5425,7 +5427,7 @@ CanvasItemEditor::CanvasItemEditor() { unlock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED)); unlock_button->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); + unlock_button->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes")); group_button = memnew(Button); group_button->set_theme_type_variation("FlatButton"); @@ -5433,7 +5435,7 @@ CanvasItemEditor::CanvasItemEditor() { group_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED)); group_button->set_tooltip_text(TTR("Groups the selected node with its children. This causes the parent to be selected when any child node is clicked in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); + group_button->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes")); ungroup_button = memnew(Button); ungroup_button->set_theme_type_variation("FlatButton"); @@ -5441,7 +5443,7 @@ CanvasItemEditor::CanvasItemEditor() { ungroup_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED)); ungroup_button->set_tooltip_text(TTR("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); + ungroup_button->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes")); main_menu_hbox->add_child(memnew(VSeparator)); @@ -5697,15 +5699,15 @@ CanvasItemEditorPlugin::~CanvasItemEditorPlugin() { } void CanvasItemEditorViewport::_on_mouse_exit() { - if (!selector->is_visible()) { + if (!texture_node_type_selector->is_visible()) { _remove_preview(); } } -void CanvasItemEditorViewport::_on_select_type(Object *selected) { +void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) { CheckBox *check = Object::cast_to<CheckBox>(selected); String type = check->get_text(); - selector->set_title(vformat(TTR("Add %s"), type)); + texture_node_type_selector->set_title(vformat(TTR("Add %s"), type)); label->set_text(vformat(TTR("Adding %s..."), type)); } @@ -5717,7 +5719,7 @@ void CanvasItemEditorViewport::_on_change_type_confirmed() { CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button()); default_texture_node_type = check->get_text(); _perform_drop_data(); - selector->hide(); + texture_node_type_selector->hide(); } void CanvasItemEditorViewport::_on_change_type_closed() { @@ -5728,35 +5730,35 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons bool add_preview = false; for (int i = 0; i < files.size(); i++) { Ref<Resource> res = ResourceLoader::load(files[i]); - ERR_FAIL_COND(res.is_null()); - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - if (texture != nullptr || scene != nullptr) { - String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") + - "\n" + TTR("Hold Shift when dropping to add as child of selected node.") + - "\n" + TTR("Hold Alt when dropping to add as child of root node."); - - if (texture != nullptr) { - Sprite2D *sprite = memnew(Sprite2D); - sprite->set_texture(texture); - sprite->set_modulate(Color(1, 1, 1, 0.7f)); - preview_node->add_child(sprite); - label->show(); - label_desc->show(); - desc += "\n" + TTR("Hold Alt + Shift when dropping to add as a different node type."); - label_desc->set_text(desc); - } else { - if (scene.is_valid()) { - Node *instance = scene->instantiate(); - if (instance) { - preview_node->add_child(instance); - label_desc->show(); - label_desc->set_text(desc); - } - } + ERR_CONTINUE(res.is_null()); + + Ref<Texture2D> texture = res; + if (texture.is_valid()) { + Sprite2D *sprite = memnew(Sprite2D); + sprite->set_texture(texture); + sprite->set_modulate(Color(1, 1, 1, 0.7f)); + preview_node->add_child(sprite); + add_preview = true; + } + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + Node *instance = scene->instantiate(); + if (instance) { + preview_node->add_child(instance); } add_preview = true; } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + Sprite2D *sprite = memnew(Sprite2D); + sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D"))); + sprite->set_modulate(Color(1, 1, 1, 0.7f)); + sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE); + preview_node->add_child(sprite); + add_preview = true; + } } if (add_preview) { @@ -5797,44 +5799,46 @@ bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_targe return false; } -void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) { +void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) { // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. - String name = path.get_file().get_basename(); - child->set_name(Node::adjust_name_casing(name)); + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + p_child->set_name(node_name); + } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - Ref<Texture2D> texture = ResourceCache::get_ref(path); + Ref<Texture2D> texture = ResourceCache::get_ref(p_path); - if (parent) { - undo_redo->add_do_method(parent, "add_child", child, true); - undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); - undo_redo->add_do_reference(child); - undo_redo->add_undo_method(parent, "remove_child", child); + if (p_parent) { + undo_redo->add_do_method(p_parent, "add_child", p_child, true); + undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(p_child); + undo_redo->add_undo_method(p_parent, "remove_child", p_child); } else { // If no parent is selected, set as root node of the scene. - undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); - undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); - undo_redo->add_do_reference(child); + undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child); + undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(p_child); undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); } - if (parent) { - String new_name = parent->validate_child_name(child); + if (p_parent) { + String new_name = p_parent->validate_child_name(p_child); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_child->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); } - if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) { - undo_redo->add_do_property(child, "texture_normal", texture); + if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) { + undo_redo->add_do_property(p_child, "texture_normal", texture); } else { - undo_redo->add_do_property(child, "texture", texture); + undo_redo->add_do_property(p_child, "texture", texture); } // make visible for certain node type - if (Object::cast_to<Control>(child)) { + if (Object::cast_to<Control>(p_child)) { Size2 texture_size = texture->get_size(); - undo_redo->add_do_property(child, "size", texture_size); - } else if (Object::cast_to<Polygon2D>(child)) { + undo_redo->add_do_property(p_child, "size", texture_size); + } else if (Object::cast_to<Polygon2D>(p_child)) { Size2 texture_size = texture->get_size(); Vector<Vector2> list = { Vector2(0, 0), @@ -5842,7 +5846,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Vector2(texture_size.width, texture_size.height), Vector2(0, texture_size.height) }; - undo_redo->add_do_property(child, "polygon", list); + undo_redo->add_do_property(p_child, "polygon", list); } // Compute the global position @@ -5850,21 +5854,68 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Point2 target_position = xform.affine_inverse().xform(p_point); // Adjust position for Control and TouchScreenButton - if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) { + if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) { target_position -= texture->get_size() / 2; } - // there's nothing to be used as source position so snapping will work as absolute if enabled + // There's nothing to be used as source position, so snapping will work as absolute if enabled. + target_position = canvas_item_editor->snap_point(target_position); + + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); + Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position; + + undo_redo->add_do_method(p_child, "set_position", local_target_pos); +} + +void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) { + AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D); + child->set_stream(ResourceCache::get_ref(p_path)); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + child->set_name(node_name); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + if (p_parent) { + undo_redo->add_do_method(p_parent, "add_child", child, true); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(p_parent, "remove_child", child); + } else { // If no parent is selected, set as root node of the scene. + undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); + } + + if (p_parent) { + String new_name = p_parent->validate_child_name(child); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), child->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); + } + + // Compute the global position + Transform2D xform = canvas_item_editor->get_canvas_transform(); + Point2 target_position = xform.affine_inverse().xform(p_point); + + // There's nothing to be used as source position, so snapping will work as absolute if enabled. target_position = canvas_item_editor->snap_point(target_position); - CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position; undo_redo->add_do_method(child, "set_position", local_target_pos); + + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + undo_redo->add_do_method(editor_selection, "add_node", child); } -bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { - Ref<PackedScene> sdata = ResourceLoader::load(path); +bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<PackedScene> sdata = ResourceLoader::load(p_path); if (!sdata.is_valid()) { // invalid scene return false; } @@ -5883,27 +5934,27 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons } } - instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path)); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name)); CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); if (instance_ci) { Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point); target_pos = canvas_item_editor->snap_point(target_pos); - CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent); if (parent_ci) { target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); } @@ -5922,12 +5973,8 @@ void CanvasItemEditorViewport::_perform_drop_data() { _remove_preview(); if (!target_node) { - // Without root dropping multiple files is not allowed - if (selected_files.size() > 1) { - accept->set_text(TTR("Cannot instantiate multiple nodes without root.")); - accept->popup_centered(); - return; - } + // Should already be handled by `can_drop_data`. + ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root."); const String &path = selected_files[0]; Ref<Resource> res = ResourceLoader::load(path); @@ -5973,9 +6020,14 @@ void CanvasItemEditorViewport::_perform_drop_data() { Ref<Texture2D> texture = res; if (texture.is_valid()) { Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type)); - _create_nodes(target_node, child, path, drop_pos); + _create_texture_node(target_node, child, path, drop_pos); undo_redo->add_do_method(editor_selection, "add_node", child); } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + _create_audio_node(target_node, path, drop_pos); + } } undo_redo->commit_action(); @@ -5994,71 +6046,108 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian } Vector<String> files = d["files"]; - bool can_instantiate = false; - bool is_cyclical_dep = false; - String error_file; - // Check if at least one of the dragged files is a texture or scene. - for (int i = 0; i < files.size(); i++) { - bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene"); - bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture2D"); + const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (!edited_scene && files.size() > 1) { + canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root."); + canvas_item_editor->update_viewport(); + return false; + } + + enum { + SCENE = 1 << 0, + TEXTURE = 1 << 1, + AUDIO = 1 << 2, + }; + int instantiate_type = 0; - if (is_scene || is_texture) { - Ref<Resource> res = ResourceLoader::load(files[i]); - if (res.is_null()) { + for (const String &path : files) { + const String &res_type = ResourceLoader::get_resource_type(path); + String error_message; + + if (ClassDB::is_parent_class(res_type, "PackedScene")) { + Ref<PackedScene> scn = ResourceLoader::load(path); + ERR_CONTINUE(scn.is_null()); + + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { continue; } - Ref<PackedScene> scn = res; - if (scn.is_valid()) { - Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instantiated_scene) { - continue; - } - - Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); - if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { - memdelete(instantiated_scene); - can_instantiate = false; - is_cyclical_dep = true; - error_file = files[i].get_file(); - break; - } - memdelete(instantiated_scene); + if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { + error_message = vformat(TTR("Circular dependency found at %s."), path.get_file()); } - can_instantiate = true; + memdelete(instantiated_scene); + instantiate_type |= SCENE; + } + if (ClassDB::is_parent_class(res_type, "Texture2D")) { + instantiate_type |= TEXTURE; + } + if (ClassDB::is_parent_class(res_type, "AudioStream")) { + instantiate_type |= AUDIO; + } + + if (!error_message.is_empty()) { + // TRANSLATORS: The placeholder is the error message. + canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message); + canvas_item_editor->update_viewport(); + return false; } } - if (is_cyclical_dep) { - canvas_item_editor->message = vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)); - canvas_item_editor->update_viewport(); + if (instantiate_type == 0) { return false; } - if (can_instantiate) { - if (!preview_node->get_parent()) { // create preview only once - _create_preview(files); - } - ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false); - Transform2D trans = canvas_item_editor->get_canvas_transform(); - preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x); + if (!preview_node->get_parent()) { // create preview only once + _create_preview(files); + } + ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false); + + const Transform2D trans = canvas_item_editor->get_canvas_transform(); + preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x); + + if (!edited_scene && instantiate_type & SCENE) { String scene_file_path = preview_node->get_child(0)->get_scene_file_path(); - if (scene_file_path.is_empty() || preview_node->get_tree()->get_edited_scene_root()) { - double snap = EDITOR_GET("interface/inspector/default_float_step"); - int snap_step_decimals = Math::range_step_decimals(snap); + // TRANSLATORS: The placeholder is the file path of the scene being instantiated. + canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path); + } else { + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); #define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals))) - Vector2 preview_node_pos = preview_node->get_global_position(); - canvas_item_editor->message = TTR("Instantiating:") + " (" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px"; - label->set_text(vformat(TTR("Adding %s..."), default_texture_node_type)); - } else { - canvas_item_editor->message = TTR("Creating inherited scene from: ") + scene_file_path; + Vector2 preview_node_pos = preview_node->get_global_position(); + canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px"; + } + canvas_item_editor->update_viewport(); + + if (instantiate_type & TEXTURE && instantiate_type & AUDIO) { + // TRANSLATORS: The placeholders are the types of nodes being instantiated. + label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D")); + } else { + String node_type; + if (instantiate_type & TEXTURE) { + node_type = default_texture_node_type; + } else if (instantiate_type & AUDIO) { + node_type = "AudioStreamPlayer2D"; + } + if (!node_type.is_empty()) { + // TRANSLATORS: The placeholder is the type of node being instantiated. + label->set_text(vformat(TTR("Adding %s..."), node_type)); } + } + label->set_visible(instantiate_type & ~SCENE); - canvas_item_editor->update_viewport(); + String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") + + "\n" + TTR("Hold Shift when dropping to add as child of selected node.") + + "\n" + TTR("Hold Alt when dropping to add as child of root node."); + if (instantiate_type & TEXTURE) { + desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type."); } - return can_instantiate; + label_desc->set_text(desc); + label_desc->show(); + + return true; } -void CanvasItemEditorViewport::_show_resource_type_selector() { +void CanvasItemEditorViewport::_show_texture_node_type_selector() { _remove_preview(); List<BaseButton *> btn_list; button_group->get_buttons(&btn_list); @@ -6067,18 +6156,17 @@ void CanvasItemEditorViewport::_show_resource_type_selector() { CheckBox *check = Object::cast_to<CheckBox>(btn); check->set_pressed(check->get_text() == default_texture_node_type); } - selector->set_title(vformat(TTR("Add %s"), default_texture_node_type)); - selector->popup_centered(); + texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type)); + texture_node_type_selector->popup_centered(); } -bool CanvasItemEditorViewport::_only_packed_scenes_selected() const { +bool CanvasItemEditorViewport::_is_any_texture_selected() const { for (int i = 0; i < selected_files.size(); ++i) { - if (ResourceLoader::load(selected_files[i])->get_class() != "PackedScene") { - return false; + if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) { + return true; } } - - return true; + return false; } void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) { @@ -6115,8 +6203,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p drop_pos = p_point; - if (is_alt && is_shift && !_only_packed_scenes_selected()) { - _show_resource_type_selector(); + if (is_alt && is_shift && _is_any_texture_selected()) { + _show_texture_node_type_selector(); } else { _perform_drop_data(); } @@ -6176,19 +6264,19 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it accept = memnew(AcceptDialog); EditorNode::get_singleton()->get_gui_base()->add_child(accept); - selector = memnew(AcceptDialog); - EditorNode::get_singleton()->get_gui_base()->add_child(selector); - selector->set_title(TTR("Change Default Type")); - selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); - selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); + texture_node_type_selector = memnew(AcceptDialog); + EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector); + texture_node_type_selector->set_title(TTR("Change Default Type")); + texture_node_type_selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); + texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); VBoxContainer *vbc = memnew(VBoxContainer); - selector->add_child(vbc); + texture_node_type_selector->add_child(vbc); vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE); - btn_group = memnew(VBoxContainer); + VBoxContainer *btn_group = memnew(VBoxContainer); vbc->add_child(btn_group); btn_group->set_h_size_flags(SIZE_EXPAND_FILL); @@ -6197,7 +6285,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it CheckBox *check = memnew(CheckBox); btn_group->add_child(check); check->set_text(texture_node_types[i]); - check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check)); + check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check)); check->set_button_group(button_group); } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 6d951a77ec..a9de5e9a0b 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -32,10 +32,11 @@ #define CANVAS_ITEM_EDITOR_PLUGIN_H #include "editor/plugins/editor_plugin.h" -#include "scene/gui/base_button.h" #include "scene/gui/box_container.h" class AcceptDialog; +class Button; +class ButtonGroup; class CanvasItemEditorViewport; class ConfirmationDialog; class EditorData; @@ -409,7 +410,7 @@ private: void _selection_result_pressed(int); void _selection_menu_hide(); void _add_node_pressed(int p_result); - void _node_created(Node *p_node); + void _adjust_new_node_position(Node *p_node); void _reset_create_position(); void _update_editor_settings(); bool _is_grid_visible() const; @@ -634,15 +635,13 @@ class CanvasItemEditorViewport : public Control { CanvasItemEditor *canvas_item_editor = nullptr; Control *preview_node = nullptr; AcceptDialog *accept = nullptr; - AcceptDialog *selector = nullptr; - Label *selector_label = nullptr; + AcceptDialog *texture_node_type_selector = nullptr; Label *label = nullptr; Label *label_desc = nullptr; - VBoxContainer *btn_group = nullptr; Ref<ButtonGroup> button_group; void _on_mouse_exit(); - void _on_select_type(Object *selected); + void _on_select_texture_node_type(Object *selected); void _on_change_type_confirmed(); void _on_change_type_closed(); @@ -650,11 +649,12 @@ class CanvasItemEditorViewport : public Control { void _remove_preview(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const; - bool _only_packed_scenes_selected() const; - void _create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point); - bool _create_instance(Node *parent, String &path, const Point2 &p_point); + bool _is_any_texture_selected() const; + void _create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point); + void _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point); + bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point); void _perform_drop_data(); - void _show_resource_type_selector(); + void _show_texture_node_type_selector(); void _update_theme(); protected: diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ca7ea821e8..e9e5b2294f 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -78,12 +78,14 @@ #include "editor/plugins/gizmos/voxel_gi_gizmo_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "editor/scene_tree_dock.h" +#include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/decal.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics/collision_shape_3d.h" #include "scene/3d/physics/physics_body_3d.h" +#include "scene/3d/sprite_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/3d/world_environment.h" #include "scene/gui/center_container.h" @@ -4192,27 +4194,37 @@ Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const { void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const { bool add_preview = false; - for (int i = 0; i < files.size(); i++) { - Ref<Resource> res = ResourceLoader::load(files[i]); + for (const String &path : files) { + Ref<Resource> res = ResourceLoader::load(path); ERR_CONTINUE(res.is_null()); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); - if (mesh != nullptr || scene != nullptr) { - if (mesh != nullptr) { - MeshInstance3D *mesh_instance = memnew(MeshInstance3D); - mesh_instance->set_mesh(mesh); - preview_node->add_child(mesh_instance); - } else { - if (scene.is_valid()) { - Node *instance = scene->instantiate(); - if (instance) { - instance = _sanitize_preview_node(instance); - preview_node->add_child(instance); - } - } + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + Node *instance = scene->instantiate(); + if (instance) { + instance = _sanitize_preview_node(instance); + preview_node->add_child(instance); } add_preview = true; } + + Ref<Mesh> mesh = res; + if (mesh.is_valid()) { + MeshInstance3D *mesh_instance = memnew(MeshInstance3D); + mesh_instance->set_mesh(mesh); + preview_node->add_child(mesh_instance); + add_preview = true; + } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + Sprite3D *sprite = memnew(Sprite3D); + sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer"))); + sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); + sprite->set_pixel_size(0.005); + preview_node->add_child(sprite); + add_preview = true; + } } if (add_preview) { EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); @@ -4346,12 +4358,12 @@ bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_sc return false; } -bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { - Ref<Resource> res = ResourceLoader::load(path); +bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<Resource> res = ResourceLoader::load(p_path); ERR_FAIL_COND_V(res.is_null(), false); - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); + Ref<PackedScene> scene = res; + Ref<Mesh> mesh = res; Node *instantiated_scene = nullptr; @@ -4361,8 +4373,10 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po mesh_instance->set_mesh(mesh); // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. - String name = path.get_file().get_basename(); - mesh_instance->set_name(Node::adjust_name_casing(name)); + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + mesh_instance->set_name(node_name); + } instantiated_scene = mesh_instance; } else { @@ -4386,25 +4400,25 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po } if (scene != nullptr) { - instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path)); } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene()); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), path, new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene); if (node3d) { Transform3D parent_tf; - Node3D *parent_node3d = Object::cast_to<Node3D>(parent); + Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent); if (parent_node3d) { parent_tf = parent_node3d->get_global_gizmo_transform(); } @@ -4419,6 +4433,46 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return true; } +bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) { + Ref<AudioStream> audio = ResourceLoader::load(p_path); + ERR_FAIL_COND_V(audio.is_null(), false); + + AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D); + audio_player->set_stream(audio); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename()); + if (!node_name.is_empty()) { + audio_player->set_name(node_name); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(p_parent, "add_child", audio_player, true); + undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(audio_player); + undo_redo->add_undo_method(p_parent, "remove_child", audio_player); + undo_redo->add_do_method(editor_selection, "add_node", audio_player); + + const String new_name = p_parent->validate_child_name(audio_player); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name)); + + Transform3D parent_tf; + Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent); + if (parent_node3d) { + parent_tf = parent_node3d->get_global_gizmo_transform(); + } + + Transform3D new_tf = audio_player->get_transform(); + new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position()); + new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis; + + undo_redo->add_do_method(audio_player, "set_transform", new_tf); + + return true; +} + void Node3DEditorViewport::_perform_drop_data() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (spatial_editor->get_preview_material_target().is_valid()) { @@ -4453,11 +4507,18 @@ void Node3DEditorViewport::_perform_drop_data() { if (res.is_null()) { continue; } - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); - if (mesh != nullptr || scene != nullptr) { - bool success = _create_instance(target_node, path, drop_pos); - if (!success) { + + Ref<PackedScene> scene = res; + Ref<Mesh> mesh = res; + if (mesh.is_valid() || scene.is_valid()) { + if (!_create_instance(target_node, path, drop_pos)) { + error_files.push_back(path.get_file()); + } + } + + Ref<AudioStream> audio = res; + if (audio.is_valid()) { + if (!_create_audio_node(target_node, path, drop_pos)) { error_files.push_back(path.get_file()); } } @@ -4488,12 +4549,14 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant bool is_other_valid = false; // Check if at least one of the dragged files is a mesh, material, texture or scene. for (int i = 0; i < files.size(); i++) { - bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene"); - bool is_mesh = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Mesh"); - bool is_material = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Material"); - bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture"); - - if (is_mesh || is_scene || is_material || is_texture) { + const String &res_type = ResourceLoader::get_resource_type(files[i]); + bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene"); + bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh"); + bool is_material = ClassDB::is_parent_class(res_type, "Material"); + bool is_texture = ClassDB::is_parent_class(res_type, "Texture"); + bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream"); + + if (is_mesh || is_scene || is_material || is_texture || is_audio) { Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; @@ -4502,6 +4565,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant Ref<Mesh> mesh = res; Ref<Material> mat = res; Ref<Texture2D> tex = res; + Ref<AudioStream> audio = res; if (scn.is_valid()) { Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { @@ -4537,6 +4601,8 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant spatial_editor->set_preview_material(new_mat); is_other_valid = true; continue; + } else if (!is_other_valid && audio.is_valid()) { + is_other_valid = true; } else { continue; } @@ -8410,7 +8476,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED)); tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); + tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes")); tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]); @@ -8418,7 +8484,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED)); tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); + tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes")); tool_button[TOOL_GROUP_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]); @@ -8426,7 +8492,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED)); tool_button[TOOL_GROUP_SELECTED]->set_tooltip_text(TTR("Groups the selected node with its children. This selects the parent when any child node is clicked in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); + tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes")); tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button); main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]); @@ -8434,7 +8500,7 @@ Node3DEditor::Node3DEditor() { tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED)); tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip_text(TTR("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); + tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes")); main_menu_hbox->add_child(memnew(VSeparator)); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index a3e1224cb8..4990b11a47 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -443,7 +443,8 @@ private: void _reset_preview_material() const; void _remove_preview_material(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const; - bool _create_instance(Node *parent, String &path, const Point2 &p_point); + bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point); + bool _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point); void _perform_drop_data(); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 63eecd357d..22dbb6e9f2 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -765,7 +765,7 @@ void ScriptEditor::_update_recent_scripts() { } recent_scripts->add_separator(); - recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files"))); + recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent")); recent_scripts->set_item_disabled(recent_scripts->get_item_id(recent_scripts->get_item_count() - 1), rc.is_empty()); recent_scripts->reset_size(); @@ -3591,13 +3591,13 @@ void ScriptEditor::_update_selected_editor_menu() { script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTR("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT); script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS); script_search_menu->get_popup()->add_separator(); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/find_in_files"), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES); script_search_menu->show(); } else { if (tab_container->get_tab_count() == 0) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/find_in_files"), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/replace_in_files"), REPLACE_IN_FILES); script_search_menu->show(); } else { script_search_menu->hide(); @@ -4101,7 +4101,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_NEW); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_NEW_TEXTFILE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTR("Open...")), FILE_OPEN); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T), FILE_REOPEN_CLOSED); + file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_REOPEN_CLOSED); recent_scripts = memnew(PopupMenu); file_menu->get_popup()->add_submenu_node_item(TTR("Open Recent"), recent_scripts, FILE_OPEN_RECENT); @@ -4502,6 +4502,15 @@ void ScriptEditorPlugin::edited_scene_changed() { } ScriptEditorPlugin::ScriptEditorPlugin() { + ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T); + ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); + ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F); + ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R); + + ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4); + ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5); + ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6); + window_wrapper = memnew(WindowWrapper); window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Script Editor"))); window_wrapper->set_margins_enabled(true); @@ -4530,9 +4539,6 @@ ScriptEditorPlugin::ScriptEditorPlugin() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_path", PROPERTY_HINT_GLOBAL_FILE)); EDITOR_DEF("text_editor/external/exec_flags", "{file}"); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); - - ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T); - ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); } ScriptEditorPlugin::~ScriptEditorPlugin() { diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 191d58228b..a5d89ff54c 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2133,10 +2133,11 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { void ScriptTextEditor::_color_changed(const Color &p_color) { String new_args; + const int decimals = 3; if (p_color.a == 1.0f) { - new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ")"); + new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ")"); } else { - new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ", " + rtos(p_color.a) + ")"); + new_args = String("(" + String::num(p_color.r, decimals) + ", " + String::num(p_color.g, decimals) + ", " + String::num(p_color.b, decimals) + ", " + String::num(p_color.a, decimals) + ")"); } String line = code_editor->get_text_editor()->get_line(color_position.x); @@ -2311,9 +2312,9 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_separator(); { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); - sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_submenu_node_item(TTR("Convert Case"), sub_menu); } diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 2aa04d7874..5bc3cafe19 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -647,9 +647,9 @@ TextEditor::TextEditor() { edit_menu->get_popup()->add_separator(); PopupMenu *convert_case = memnew(PopupMenu); edit_menu->get_popup()->add_submenu_node_item(TTR("Convert Case"), convert_case); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase")), EDIT_TO_UPPERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase")), EDIT_TO_LOWERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize")), EDIT_CAPITALIZE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE); convert_case->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); highlighter_menu = memnew(PopupMenu); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 2ee0dc0316..2b86268414 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -250,7 +250,7 @@ void TileAtlasView::_draw_base_tiles() { Vector2 offset_pos = Rect2(base_frame_rect).get_center() + Vector2(tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin()); // Draw the tile. - TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame); + TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame); } } @@ -411,7 +411,7 @@ void TileAtlasView::_draw_alternatives() { } // Draw the tile. - TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id); + TileMapLayer::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id); // Increment the x position. current_pos.x += transposed ? texture_region_size.y : texture_region_size.x; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 2d0e5214db..a94080e253 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -144,7 +144,8 @@ void GenericTilePolygonEditor::_base_control_draw() { } // Draw tile-related things. - Size2 tile_size = tile_set->get_tile_size(); + const Size2 base_tile_size = tile_set->get_tile_size(); + const Size2 tile_size = background_region.size; Transform2D xform; xform.set_origin(base_control->get_size() / 2 + panning); @@ -170,12 +171,16 @@ void GenericTilePolygonEditor::_base_control_draw() { // Draw grid. if (current_snap_option == SNAP_GRID) { - Vector2 spacing = tile_size / snap_subdivision->get_value(); + Vector2 spacing = base_tile_size / snap_subdivision->get_value(); Vector2 offset = -tile_size / 2; + int w = snap_subdivision->get_value() * (tile_size / base_tile_size).x; + int h = snap_subdivision->get_value() * (tile_size / base_tile_size).y; - for (int i = 1; i < snap_subdivision->get_value(); i++) { - base_control->draw_line(Vector2(spacing.x * i, 0) + offset, Vector2(spacing.x * i, tile_size.y) + offset, Color(1, 1, 1, 0.33)); - base_control->draw_line(Vector2(0, spacing.y * i) + offset, Vector2(tile_size.x, spacing.y * i) + offset, Color(1, 1, 1, 0.33)); + for (int y = 1; y < h; y++) { + for (int x = 1; x < w; x++) { + base_control->draw_line(Vector2(spacing.x * x, 0) + offset, Vector2(spacing.x * x, tile_size.y) + offset, Color(1, 1, 1, 0.33)); + base_control->draw_line(Vector2(0, spacing.y * y) + offset, Vector2(tile_size.x, spacing.y * y) + offset, Color(1, 1, 1, 0.33)); + } } } @@ -1320,7 +1325,7 @@ TileDataDefaultEditor::TileDataDefaultEditor() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); toolbar->add_child(picker_button); } @@ -2807,7 +2812,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); toolbar->add_child(picker_button); // Setup diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 98c05e9b15..5fd4fb3602 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -2183,13 +2183,6 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { ->get_viewport_control() ->connect(SceneStringName(mouse_exited), callable_mp(this, &TileMapLayerEditorTilesPlugin::_mouse_exited_viewport)); - // --- Shortcuts --- - ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); - ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); - ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); - ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); - ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); - // --- Initialize references --- tile_map_clipboard.instantiate(); selection_pattern.instantiate(); @@ -2217,7 +2210,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { paint_tool_button->set_theme_type_variation("FlatButton"); paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); + paint_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/paint_tool")); paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Draw rectangle.")); paint_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -2228,7 +2221,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); // TRANSLATORS: This refers to the line tool in the tilemap editor. - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L)); + line_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/line_tool")); line_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); viewport_shortcut_buttons.push_back(line_tool_button); @@ -2237,7 +2230,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { rect_tool_button->set_theme_type_variation("FlatButton"); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); + rect_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/rect_tool")); rect_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); viewport_shortcut_buttons.push_back(rect_tool_button); @@ -2246,7 +2239,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { bucket_tool_button->set_theme_type_variation("FlatButton"); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); + bucket_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/bucket_tool")); bucket_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); toolbar->add_child(tilemap_tiles_tools_buttons); @@ -2263,7 +2256,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL; picker_button->set_tooltip_text(vformat(TTR("Alternatively hold %s with other tools to pick tile."), find_keycode_name(key))); picker_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); @@ -2274,7 +2267,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { erase_button = memnew(Button); erase_button->set_theme_type_variation("FlatButton"); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); erase_button->set_tooltip_text(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -3552,7 +3545,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_pressed(true); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); + paint_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/paint_tool")); paint_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); viewport_shortcut_buttons.push_back(paint_tool_button); @@ -3561,7 +3554,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { line_tool_button->set_theme_type_variation("FlatButton"); line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line"), Key::L)); + line_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/line_tool")); line_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); viewport_shortcut_buttons.push_back(line_tool_button); @@ -3570,7 +3563,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { rect_tool_button->set_theme_type_variation("FlatButton"); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); + rect_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/rect_tool")); rect_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); viewport_shortcut_buttons.push_back(rect_tool_button); @@ -3579,7 +3572,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { bucket_tool_button->set_theme_type_variation("FlatButton"); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); + bucket_tool_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/bucket_tool")); bucket_tool_button->connect(SceneStringName(pressed), callable_mp(this, &TileMapLayerEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); viewport_shortcut_buttons.push_back(bucket_tool_button); @@ -3597,7 +3590,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { picker_button = memnew(Button); picker_button->set_theme_type_variation("FlatButton"); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/picker")); picker_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); viewport_shortcut_buttons.push_back(picker_button); @@ -3606,7 +3599,7 @@ TileMapLayerEditorTerrainsPlugin::TileMapLayerEditorTerrainsPlugin() { erase_button = memnew(Button); erase_button->set_theme_type_variation("FlatButton"); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); erase_button->connect(SceneStringName(pressed), callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); viewport_shortcut_buttons.push_back(erase_button); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index e4fd32a637..1daba1f665 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2627,7 +2627,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tools_settings_erase_button = memnew(Button); tools_settings_erase_button->set_theme_type_variation("FlatButton"); tools_settings_erase_button->set_toggle_mode(true); - tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + tools_settings_erase_button->set_shortcut(ED_GET_SHORTCUT("tiles_editor/eraser")); tools_settings_erase_button->set_shortcut_context(this); tool_settings->add_child(tools_settings_erase_button); @@ -2687,7 +2687,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_create_help->set_grow_direction_preset(Control::PRESET_BOTTOM_LEFT); base_tile_popup_menu = memnew(PopupMenu); - base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE); + base_tile_popup_menu->add_shortcut(ED_GET_SHORTCUT("tiles_editor/delete"), TILE_DELETE); base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE); base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tile_atlas_view->add_child(base_tile_popup_menu); diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 6833a36138..f1cc69ce18 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -303,6 +303,19 @@ TilesEditorUtils::TilesEditorUtils() { singleton = this; // Pattern preview generation thread. pattern_preview_thread.start(_thread_func, this); + + ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); + ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); + ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); + ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); + ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); + + ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D); + ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L); + ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R); + ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B); + ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E); + ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P); } TilesEditorUtils::~TilesEditorUtils() { diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 32f088ec60..f474464f6c 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" +#include "core/os/time.h" +#include "core/version.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -130,12 +132,29 @@ void ProjectListItemControl::set_project_icon(const Ref<Texture2D> &p_icon) { project_icon->set_texture(p_icon); } +void ProjectListItemControl::set_last_edited_info(const String &p_info) { + last_edited_info->set_text(p_info); +} + +void ProjectListItemControl::set_project_version(const String &p_info) { + project_version->set_text(p_info); +} + void ProjectListItemControl::set_unsupported_features(PackedStringArray p_features) { if (p_features.size() > 0) { String tooltip_text = ""; for (int i = 0; i < p_features.size(); i++) { if (ProjectList::project_feature_looks_like_version(p_features[i])) { - tooltip_text += TTR("This project was last edited in a different Godot version: ") + p_features[i] + "\n"; + PackedStringArray project_version_split = p_features[i].split("."); + int project_version_major = 0, project_version_minor = 0; + if (project_version_split.size() >= 2) { + project_version_major = project_version_split[0].to_int(); + project_version_minor = project_version_split[1].to_int(); + } + if (VERSION_MAJOR != project_version_major || VERSION_MINOR <= project_version_minor) { + // Don't show a warning if the project was last edited in a previous minor version. + tooltip_text += TTR("This project was last edited in a different Godot version: ") + p_features[i] + "\n"; + } p_features.remove_at(i); i--; } @@ -144,6 +163,10 @@ void ProjectListItemControl::set_unsupported_features(PackedStringArray p_featur String unsupported_features_str = String(", ").join(p_features); tooltip_text += TTR("This project uses features unsupported by the current build:") + "\n" + unsupported_features_str; } + if (tooltip_text.is_empty()) { + return; + } + project_version->set_tooltip_text(tooltip_text); project_unsupported_features->set_tooltip_text(tooltip_text); project_unsupported_features->show(); } else { @@ -272,12 +295,24 @@ ProjectListItemControl::ProjectListItemControl() { project_path->set_modulate(Color(1, 1, 1, 0.5)); path_hb->add_child(project_path); + last_edited_info = memnew(Label); + last_edited_info->set_name("LastEditedInfo"); + last_edited_info->set_mouse_filter(Control::MOUSE_FILTER_PASS); + last_edited_info->set_tooltip_text("Last edited timestamp"); + last_edited_info->set_modulate(Color(1, 1, 1, 0.5)); + path_hb->add_child(last_edited_info); + project_unsupported_features = memnew(TextureRect); project_unsupported_features->set_name("ProjectUnsupportedFeatures"); project_unsupported_features->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); path_hb->add_child(project_unsupported_features); project_unsupported_features->hide(); + project_version = memnew(Label); + project_version->set_name("ProjectVersion"); + project_version->set_mouse_filter(Control::MOUSE_FILTER_PASS); + path_hb->add_child(project_version); + Control *spacer = memnew(Control); spacer->set_custom_minimum_size(Size2(10, 10)); path_hb->add_child(spacer); @@ -409,6 +444,24 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa PackedStringArray project_features = cf->get_value("application", "config/features", PackedStringArray()); PackedStringArray unsupported_features = ProjectSettings::get_unsupported_features(project_features); + String project_version = "?"; + for (int i = 0; i < project_features.size(); i++) { + if (ProjectList::project_feature_looks_like_version(project_features[i])) { + project_version = project_features[i]; + break; + } + } + + if (config_version < ProjectSettings::CONFIG_VERSION) { + // Previous versions may not have unsupported features. + if (config_version == 4) { + unsupported_features.push_back("3.x"); + project_version = "3.x"; + } else { + unsupported_features.push_back("Unknown version"); + } + } + uint64_t last_edited = 0; if (cf_err == OK) { // The modification date marks the date the project was last edited. @@ -433,7 +486,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa ProjectManager::get_singleton()->add_new_tag(tag); } - return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); + return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version); } void ProjectList::_update_icons_async() { @@ -706,6 +759,8 @@ void ProjectList::_create_project_item_control(int p_index) { hb->set_tooltip_text(item.description); hb->set_tags(item.tags, this); hb->set_unsupported_features(item.unsupported_features.duplicate()); + hb->set_project_version(item.project_version); + hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true)); hb->set_is_favorite(item.favorite); hb->set_is_missing(item.missing); diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 981df0f3a0..6e0f5830ac 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -51,6 +51,8 @@ class ProjectListItemControl : public HBoxContainer { TextureRect *project_icon = nullptr; Label *project_title = nullptr; Label *project_path = nullptr; + Label *last_edited_info = nullptr; + Label *project_version = nullptr; TextureRect *project_unsupported_features = nullptr; HBoxContainer *tag_container = nullptr; @@ -71,6 +73,8 @@ public: void set_project_path(const String &p_path); void set_tags(const PackedStringArray &p_tags, ProjectList *p_parent_list); void set_project_icon(const Ref<Texture2D> &p_icon); + void set_last_edited_info(const String &p_info); + void set_project_version(const String &p_version); void set_unsupported_features(PackedStringArray p_features); bool should_load_project_icon() const; @@ -100,6 +104,7 @@ public: struct Item { String project_name; String description; + String project_version; PackedStringArray tags; String tag_sort_string; String path; @@ -118,6 +123,7 @@ public: Item(const String &p_name, const String &p_description, + const String &p_project_version, const PackedStringArray &p_tags, const String &p_path, const String &p_icon, @@ -130,6 +136,7 @@ public: int p_version) { project_name = p_name; description = p_description; + project_version = p_project_version; tags = p_tags; path = p_path; icon = p_icon; diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index cff95cadc8..dc9d1df4e8 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -54,7 +54,7 @@ void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) { if (saved_size != Rect2()) { popup(saved_size); } else { - popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8); + popup_centered_clamped(Size2(1200, 700) * EDSCALE, 0.8); } _add_feature_overrides(); diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 51f9d319a6..edefaa61a8 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -289,6 +289,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) { vbc->add_child(lbl_preview_title); lbl_preview = memnew(Label); + lbl_preview->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); lbl_preview->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); vbc->add_child(lbl_preview); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 53145c3450..ec0380f964 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -56,6 +56,7 @@ #include "editor/shader_create_dialog.h" #include "editor/themes/editor_scale.h" #include "scene/animation/animation_tree.h" +#include "scene/audio/audio_stream_player.h" #include "scene/gui/check_box.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" @@ -254,8 +255,8 @@ void SceneTreeDock::instantiate_scenes(const Vector<String> &p_files, Node *p_pa _perform_instantiate_scenes(p_files, parent, -1); } -void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos) { - ERR_FAIL_NULL(parent); +void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos) { + ERR_FAIL_NULL(p_parent); Vector<Node *> instances; @@ -302,25 +303,25 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Instantiate Scene(s)")); + undo_redo->create_action_for_history(TTRN("Instantiate Scene", "Instantiate Scenes", instances.size()), editor_data->get_current_edited_scene_history_id()); + undo_redo->add_do_method(editor_selection, "clear"); for (int i = 0; i < instances.size(); i++) { Node *instantiated_scene = instances[i]; - undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true); if (p_pos >= 0) { - undo_redo->add_do_method(parent, "move_child", instantiated_scene, p_pos + i); + undo_redo->add_do_method(p_parent, "move_child", instantiated_scene, p_pos + i); } undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); - undo_redo->add_do_method(editor_selection, "clear"); undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene); undo_redo->add_do_reference(instantiated_scene); - undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); + undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene); - String new_name = parent->validate_child_name(instantiated_scene); + String new_name = p_parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), p_files[i], new_name); - undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).path_join(new_name))); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_files[i], new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name))); } undo_redo->commit_action(); @@ -330,6 +331,75 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } } +void SceneTreeDock::_perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos) { + ERR_FAIL_NULL(p_parent); + + StringName node_type = "AudioStreamPlayer"; + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + if (Object::cast_to<Node2D>(p_parent)) { + node_type = "AudioStreamPlayer2D"; + } else if (Object::cast_to<Node3D>(p_parent)) { + node_type = "AudioStreamPlayer3D"; + } + } + + Vector<Node *> nodes; + bool error = false; + + for (const String &path : p_files) { + Ref<AudioStream> stream = ResourceLoader::load(path); + if (stream.is_null()) { + current_option = -1; + accept->set_text(vformat(TTR("Error loading audio stream from %s"), path)); + accept->popup_centered(); + error = true; + break; + } + + Node *player = Object::cast_to<Node>(ClassDB::instantiate(node_type)); + player->set("stream", stream); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + const String &node_name = Node::adjust_name_casing(path.get_file().get_basename()); + if (!node_name.is_empty()) { + player->set_name(node_name); + } + + nodes.push_back(player); + } + + if (error) { + for (Node *node : nodes) { + memdelete(node); + } + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action_for_history(TTRN("Create AudioStreamPlayer", "Create AudioStreamPlayers", nodes.size()), editor_data->get_current_edited_scene_history_id()); + undo_redo->add_do_method(editor_selection, "clear"); + + for (int i = 0; i < nodes.size(); i++) { + Node *node = nodes[i]; + + undo_redo->add_do_method(p_parent, "add_child", node, true); + if (p_pos >= 0) { + undo_redo->add_do_method(p_parent, "move_child", node, p_pos + i); + } + undo_redo->add_do_method(node, "set_owner", edited_scene); + undo_redo->add_do_method(editor_selection, "add_node", node); + undo_redo->add_do_reference(node); + undo_redo->add_undo_method(p_parent, "remove_child", node); + + String new_name = p_parent->validate_child_name(node); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + undo_redo->add_do_method(ed, "live_debug_create_node", edited_scene->get_path_to(p_parent), node->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name))); + } + + undo_redo->commit_action(); +} + void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) { // `move_child` + `get_index` doesn't really work for internal nodes. ERR_FAIL_COND_MSG(base->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to replace internal node, this is not supported."); @@ -3196,15 +3266,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) { void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) { Node *node = get_node(p_to); ERR_FAIL_NULL(node); + ERR_FAIL_COND(p_files.is_empty()); - if (scene_tree->get_scene_tree()->get_drop_mode_flags() & Tree::DROP_MODE_INBETWEEN) { - // Dropped PackedScene, instance it. - int to_pos = -1; - _normalize_drop(node, to_pos, p_type); - _perform_instantiate_scenes(p_files, node, to_pos); - } else { - const String &res_path = p_files[0]; - StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); + const String &res_path = p_files[0]; + const StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); + + // Dropping as property when possible. + if (p_type == 0 && p_files.size() == 1) { List<String> valid_properties; List<PropertyInfo> pinfo; @@ -3238,10 +3306,22 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, menu_properties->reset_size(); menu_properties->set_position(get_screen_position() + get_local_mouse_position()); menu_properties->popup(); - } else if (!valid_properties.is_empty()) { + return; + } + if (!valid_properties.is_empty()) { _perform_property_drop(node, valid_properties.front()->get(), ResourceLoader::load(res_path)); + return; } } + + // Either instantiate scenes or create AudioStreamPlayers. + int to_pos = -1; + _normalize_drop(node, to_pos, p_type); + if (res_type == "PackedScene") { + _perform_instantiate_scenes(p_files, node, to_pos); + } else { + _perform_create_audio_stream_players(p_files, node, to_pos); + } } void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) { @@ -3571,7 +3651,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { if (profile_allow_editing) { menu->add_separator(); - menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_SHORTCUT("scene_tree/delete", TTR("Delete Node(s)"), Key::KEY_DELETE), TOOL_ERASE); + menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("scene_tree/delete"), TOOL_ERASE); } menu->reset_size(); menu->set_position(p_menu_pos); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 21e1b00f93..abef990995 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -272,7 +272,8 @@ class SceneTreeDock : public VBoxContainer { void _filter_option_selected(int option); void _append_filter_options_to(PopupMenu *p_menu, bool p_include_separator = true); - void _perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos); + void _perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos); + void _perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos); void _replace_with_branch_scene(const String &p_file, Node *base); void _remote_tree_selected(); diff --git a/editor/themes/SCsub b/editor/themes/SCsub index 65cfb6a8be..e8f96e4299 100644 --- a/editor/themes/SCsub +++ b/editor/themes/SCsub @@ -3,8 +3,8 @@ Import("env") import glob -import editor_theme_builders +import editor_theme_builders # Fonts flist = glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.ttf") diff --git a/gles3_builders.py b/gles3_builders.py index 1491927feb..a4928c81c5 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -1,9 +1,10 @@ """Functions used to generate source files during build time""" import os.path -from methods import print_error from typing import Optional +from methods import print_error + class GLES3HeaderStruct: def __init__(self): @@ -91,11 +92,11 @@ def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, includeline = line.replace("#include ", "").strip()[1:-1] included_file = os.path.relpath(os.path.dirname(filename) + "/" + includeline) - if not included_file in header_data.vertex_included_files and header_data.reading == "vertex": + if included_file not in header_data.vertex_included_files and header_data.reading == "vertex": header_data.vertex_included_files += [included_file] if include_file_in_gles3_header(included_file, header_data, depth + 1) is None: print_error(f'In file "{filename}": #include "{includeline}" could not be found!"') - elif not included_file in header_data.fragment_included_files and header_data.reading == "fragment": + elif included_file not in header_data.fragment_included_files and header_data.reading == "fragment": header_data.fragment_included_files += [included_file] if include_file_in_gles3_header(included_file, header_data, depth + 1) is None: print_error(f'In file "{filename}": #include "{includeline}" could not be found!"') @@ -121,7 +122,7 @@ def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, # unfiorm array x = x[: x.find("[")] - if not x in header_data.texunit_names: + if x not in header_data.texunit_names: header_data.texunits += [(x, texunit)] header_data.texunit_names += [x] @@ -142,7 +143,7 @@ def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, # unfiorm array x = x[: x.find("[")] - if not x in header_data.ubo_names: + if x not in header_data.ubo_names: header_data.ubos += [(x, ubo)] header_data.ubo_names += [x] @@ -157,7 +158,7 @@ def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, # unfiorm array x = x[: x.find("[")] - if not x in header_data.uniforms: + if x not in header_data.uniforms: header_data.uniforms += [x] if (line.strip().find("out ") == 0 or line.strip().find("flat ") == 0) and line.find("tfb:") != -1: diff --git a/glsl_builders.py b/glsl_builders.py index 22f4de74b1..05aab3acbb 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -1,8 +1,9 @@ """Functions used to generate source files during build time""" import os.path +from typing import Iterable, Optional + from methods import print_error -from typing import Optional, Iterable def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True): @@ -77,15 +78,15 @@ def include_file_in_rd_header(filename: str, header_data: RDHeaderStruct, depth: else: included_file = os.path.relpath(os.path.dirname(filename) + "/" + includeline) - if not included_file in header_data.vertex_included_files and header_data.reading == "vertex": + if included_file not in header_data.vertex_included_files and header_data.reading == "vertex": header_data.vertex_included_files += [included_file] if include_file_in_rd_header(included_file, header_data, depth + 1) is None: print_error(f'In file "{filename}": #include "{includeline}" could not be found!"') - elif not included_file in header_data.fragment_included_files and header_data.reading == "fragment": + elif included_file not in header_data.fragment_included_files and header_data.reading == "fragment": header_data.fragment_included_files += [included_file] if include_file_in_rd_header(included_file, header_data, depth + 1) is None: print_error(f'In file "{filename}": #include "{includeline}" could not be found!"') - elif not included_file in header_data.compute_included_files and header_data.reading == "compute": + elif included_file not in header_data.compute_included_files and header_data.reading == "compute": header_data.compute_included_files += [included_file] if include_file_in_rd_header(included_file, header_data, depth + 1) is None: print_error(f'In file "{filename}": #include "{includeline}" could not be found!"') diff --git a/methods.py b/methods.py index da221cc0ea..7600e6d445 100644 --- a/methods.py +++ b/methods.py @@ -1,17 +1,14 @@ +import contextlib +import glob import os -import sys import re -import glob import subprocess -import contextlib +import sys from collections import OrderedDict -from collections.abc import Mapping from enum import Enum -from typing import Generator, Optional -from io import TextIOWrapper, StringIO +from io import StringIO, TextIOWrapper from pathlib import Path -from os.path import normpath, basename - +from typing import Generator, Optional # Get the "Godot" folder name ahead of time base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" @@ -199,7 +196,7 @@ def add_module_version_string(self, s): def get_version_info(module_version_string="", silent=False): build_name = "custom_build" - if os.getenv("BUILD_NAME") != None: + if os.getenv("BUILD_NAME") is not None: build_name = str(os.getenv("BUILD_NAME")) if not silent: print(f"Using custom build name: '{build_name}'.") @@ -221,7 +218,7 @@ def get_version_info(module_version_string="", silent=False): # For dev snapshots (alpha, beta, RC, etc.) we do not commit status change to Git, # so this define provides a way to override it without having to modify the source. - if os.getenv("GODOT_VERSION_STATUS") != None: + if os.getenv("GODOT_VERSION_STATUS") is not None: version_info["status"] = str(os.getenv("GODOT_VERSION_STATUS")) if not silent: print(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.") @@ -435,7 +432,7 @@ def module_check_dependencies(self, module): required_deps = self.module_dependencies[module][0] if module in self.module_dependencies else [] for dep in required_deps: opt = "module_{}_enabled".format(dep) - if not opt in self or not self[opt]: + if opt not in self or not self[opt]: missing_deps.append(dep) if missing_deps != []: @@ -450,7 +447,6 @@ def module_check_dependencies(self, module): def sort_module_list(env): - out = OrderedDict() deps = {k: v[0] + list(filter(lambda x: x in env.module_list, v[1])) for k, v in env.module_dependencies.items()} frontier = list(env.module_list.keys()) @@ -650,7 +646,7 @@ def detect_visual_c_compiler_version(tools_env): def find_visual_c_batch_file(env): - from SCons.Tool.MSCommon.vc import get_default_version, get_host_target, find_batch_file, find_vc_pdir + from SCons.Tool.MSCommon.vc import find_batch_file, find_vc_pdir, get_default_version, get_host_target msvc_version = get_default_version(env) @@ -696,10 +692,7 @@ def glob_recursive(pattern, node="."): def add_to_vs_project(env, sources): for x in sources: - if type(x) == type(""): - fname = env.File(x).path - else: - fname = env.File(x)[0].path + fname = env.File(x).path if isinstance(x, str) else env.File(x)[0].path pieces = fname.split(".") if len(pieces) > 0: basename = pieces[0] @@ -884,7 +877,8 @@ def show_progress(env): return import sys - from SCons.Script import Progress, Command, AlwaysBuild + + from SCons.Script import AlwaysBuild, Command, Progress screen = sys.stdout # Progress reporting is not available in non-TTY environments since it @@ -895,7 +889,8 @@ def show_progress(env): node_count_interval = 1 node_count_fname = str(env.Dir("#")) + "/.scons_node_count" - import time, math + import math + import time class cache_progress: # The default is 1 GB cache and 12 hours half life @@ -903,7 +898,7 @@ def show_progress(env): self.path = path self.limit = limit self.exponent_scale = math.log(2) / half_life - if env["verbose"] and path != None: + if env["verbose"] and path is not None: screen.write( "Current cache limit is {} (used: {})\n".format( self.convert_size(limit), self.convert_size(self.get_size(path)) @@ -1049,11 +1044,11 @@ def generate_vs_project(env, original_args, project_name="godot"): if type(f) is Node.FS.Dir: results += glob_recursive_2(pattern, dirs, f) r = Glob(str(node) + "/" + pattern, source=True) - if len(r) > 0 and not str(node) in dirs: + if len(r) > 0 and str(node) not in dirs: d = "" for part in str(node).split("\\"): d += part - if not d in dirs: + if d not in dirs: dirs.append(d) d += "\\" results += r @@ -1066,7 +1061,7 @@ def generate_vs_project(env, original_args, project_name="godot"): if val is not None: try: return _text2bool(val) - except: + except (ValueError, AttributeError): return default else: return default @@ -1239,13 +1234,13 @@ def generate_vs_project(env, original_args, project_name="godot"): others_active = [] for x in envsources: fname = "" - if type(x) == type(""): + if isinstance(x, str): fname = env.File(x).path else: # Some object files might get added directly as a File object and not a list. try: fname = env.File(x)[0].path - except: + except Exception: fname = x.path pass @@ -1324,7 +1319,7 @@ def generate_vs_project(env, original_args, project_name="godot"): itemlist = {} for item in activeItems: key = os.path.dirname(item).replace("\\", "_") - if not key in itemlist: + if key not in itemlist: itemlist[key] = [item] else: itemlist[key] += [item] @@ -1465,14 +1460,14 @@ def generate_vs_project(env, original_args, project_name="godot"): if godot_platform != "windows": configurations += [ f'<ProjectConfiguration Include="editor|{proj_plat}">', - f" <Configuration>editor</Configuration>", + " <Configuration>editor</Configuration>", f" <Platform>{proj_plat}</Platform>", "</ProjectConfiguration>", ] properties += [ f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">", - f" <GodotConfiguration>editor</GodotConfiguration>", + " <GodotConfiguration>editor</GodotConfiguration>", f" <GodotPlatform>{proj_plat}</GodotPlatform>", "</PropertyGroup>", ] diff --git a/misc/scripts/install_d3d12_sdk_windows.py b/misc/scripts/install_d3d12_sdk_windows.py index aed80d81a4..d7574e6222 100755 --- a/misc/scripts/install_d3d12_sdk_windows.py +++ b/misc/scripts/install_d3d12_sdk_windows.py @@ -1,15 +1,15 @@ #!/usr/bin/env python import os -import urllib.request import shutil import subprocess import sys +import urllib.request # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://github.com/python/cpython/issues/73245> if sys.platform == "win32": - from ctypes import windll, c_int, byref + from ctypes import byref, c_int, windll stdout_handle = windll.kernel32.GetStdHandle(c_int(-11)) mode = c_int(0) diff --git a/modules/SCsub b/modules/SCsub index 739c5de0b5..db0e563dc4 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,8 +1,9 @@ #!/usr/bin/env python -import methods import os +import methods + Import("env") env_modules = env.Clone() diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index 3cc919fae2..e0f60fe998 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -111,7 +111,7 @@ void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path, void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { if (!p_import_params.has("fbx/importer")) { - p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX; + p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF; } } diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index f9709362eb..efbf298f99 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -5,7 +5,7 @@ import os.path import shlex import subprocess from dataclasses import dataclass -from typing import Optional, List +from typing import List, Optional def find_dotnet_cli(): @@ -304,9 +304,7 @@ def generate_sdk_package_versions(): <GodotVersionConstants>{1}</GodotVersionConstants> </PropertyGroup> </Project> -""".format( - version_str, ";".join(version_defines) - ) +""".format(version_str, ";".join(version_defines)) # We write in ../SdkPackageVersions.props. with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8", newline="\n") as f: @@ -323,9 +321,7 @@ def generate_sdk_package_versions(): public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}"; }} }} -""".format( - **version_info - ) +""".format(**version_info) generators_dir = os.path.join( dirname(script_path), diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index e5469c4980..98c50dcc22 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,7 +1,3 @@ -import os -import os.path - - def is_desktop(platform): return platform in ["windows", "macos", "linuxbsd"] diff --git a/modules/mono/config.py b/modules/mono/config.py index 3d087c9e27..5ebdb83b36 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -12,7 +12,7 @@ def configure(env): # Check if the platform has marked mono as supported. supported = env.get("supported", []) - if not "mono" in supported: + if "mono" not in supported: raise RuntimeError("This module does not currently support building for this platform") env.add_module_version_string("mono") diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index b559ca20b2..7e14259d5a 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -5,7 +5,6 @@ </brief_description> <description> This class represents a C# script. It is the C# equivalent of the [GDScript] class and is only available in Mono-enabled Godot builds. - See also [GodotSharp]. </description> <tutorials> <link title="C# documentation index">$DOCS_URL/tutorials/scripting/c_sharp/index.html</link> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml deleted file mode 100644 index 969ca14350..0000000000 --- a/modules/mono/doc_classes/GodotSharp.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="GodotSharp" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> - <brief_description> - Bridge between Godot and the Mono runtime (Mono-enabled builds only). - </brief_description> - <description> - This class is a bridge between Godot and the Mono runtime. It exposes several low-level operations and is only available in Mono-enabled Godot builds. - See also [CSharpScript]. - </description> - <tutorials> - </tutorials> - <methods> - <method name="is_runtime_initialized"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise. - </description> - </method> - </methods> -</class> diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 05dacd28fb..03d8b4eab6 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -143,7 +143,7 @@ bool godot_icall_Internal_IsAssembliesReloadingNeeded() { void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD - mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); + callable_mp(mono_bind::GodotSharp::get_singleton(), &mono_bind::GodotSharp::reload_assemblies).call_deferred(p_soft_reload); #endif } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index ab7f8ede44..33f0850a8d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -318,9 +318,9 @@ namespace Godot Vector3 ofs = _position + halfExtents; return ofs + new Vector3( - dir.X > 0f ? -halfExtents.X : halfExtents.X, - dir.Y > 0f ? -halfExtents.Y : halfExtents.Y, - dir.Z > 0f ? -halfExtents.Z : halfExtents.Z); + dir.X > 0f ? halfExtents.X : -halfExtents.X, + dir.Y > 0f ? halfExtents.Y : -halfExtents.Y, + dir.Z > 0f ? halfExtents.Z : -halfExtents.Z); } /// <summary> diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 0e34616951..48caae8523 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -564,11 +564,7 @@ namespace mono_bind { GodotSharp *GodotSharp::singleton = nullptr; -bool GodotSharp::_is_runtime_initialized() { - return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized(); -} - -void GodotSharp::_reload_assemblies(bool p_soft_reload) { +void GodotSharp::reload_assemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD CRASH_COND(CSharpLanguage::get_singleton() == nullptr); // This method may be called more than once with `call_deferred`, so we need to check @@ -579,11 +575,6 @@ void GodotSharp::_reload_assemblies(bool p_soft_reload) { #endif } -void GodotSharp::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::_is_runtime_initialized); - ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies); -} - GodotSharp::GodotSharp() { singleton = this; } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 0cb087db57..614bfc63fb 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -167,18 +167,14 @@ namespace mono_bind { class GodotSharp : public Object { GDCLASS(GodotSharp, Object); - friend class GDMono; - - void _reload_assemblies(bool p_soft_reload); - bool _is_runtime_initialized(); - protected: static GodotSharp *singleton; - static void _bind_methods(); public: static GodotSharp *get_singleton() { return singleton; } + void reload_assemblies(bool p_soft_reload); + GodotSharp(); ~GodotSharp(); }; diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp index beaa50ecb2..4d5426d96f 100644 --- a/modules/mono/register_types.cpp +++ b/modules/mono/register_types.cpp @@ -49,9 +49,6 @@ void initialize_mono_module(ModuleInitializationLevel p_level) { _godotsharp = memnew(mono_bind::GodotSharp); - GDREGISTER_CLASS(mono_bind::GodotSharp); - Engine::get_singleton()->add_singleton(Engine::Singleton("GodotSharp", mono_bind::GodotSharp::get_singleton())); - script_language_cs = memnew(CSharpLanguage); script_language_cs->set_language_index(ScriptServer::get_language_count()); ScriptServer::register_language(script_language_cs); diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index cc3bbdbf01..df0bdc9537 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -895,9 +895,22 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation Vector<Vector3> nav_vertices; + HashMap<Vector3, int> recast_vertex_to_native_index; + LocalVector<int> recast_index_to_native_index; + recast_index_to_native_index.resize(detail_mesh->nverts); + for (int i = 0; i < detail_mesh->nverts; i++) { const float *v = &detail_mesh->verts[i * 3]; - nav_vertices.push_back(Vector3(v[0], v[1], v[2])); + const Vector3 vertex = Vector3(v[0], v[1], v[2]); + int *existing_index_ptr = recast_vertex_to_native_index.getptr(vertex); + if (!existing_index_ptr) { + int new_index = recast_vertex_to_native_index.size(); + recast_index_to_native_index[i] = new_index; + recast_vertex_to_native_index[vertex] = new_index; + nav_vertices.push_back(vertex); + } else { + recast_index_to_native_index[i] = *existing_index_ptr; + } } p_navigation_mesh->set_vertices(nav_vertices); p_navigation_mesh->clear_polygons(); @@ -912,9 +925,14 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation Vector<int> nav_indices; nav_indices.resize(3); // Polygon order in recast is opposite than godot's - nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0])); - nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2])); - nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1])); + int index1 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0])); + int index2 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2])); + int index3 = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1])); + + nav_indices.write[0] = recast_index_to_native_index[index1]; + nav_indices.write[1] = recast_index_to_native_index[index2]; + nav_indices.write[2] = recast_index_to_native_index[index3]; + p_navigation_mesh->add_polygon(nav_indices); } } diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml index 79aa547c52..338d632524 100644 --- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml +++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml @@ -84,6 +84,12 @@ Called right before the OpenXR instance is destroyed. </description> </method> + <method name="_on_main_swapchains_created" qualifiers="virtual"> + <return type="void" /> + <description> + Called right after the main swapchains are (re)created. + </description> + </method> <method name="_on_pre_render" qualifiers="virtual"> <return type="void" /> <description> diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index ce03df0b30..8d05657afc 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -84,6 +84,7 @@ public: // This is when controller data is queried and made available to game logic. virtual void on_process() {} virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewports. + virtual void on_main_swapchains_created() {} // `on_main_swapchains_created` is called right after our main swapchains are (re)created. virtual void on_pre_draw_viewport(RID p_render_target) {} // `on_pre_draw_viewport` is called right before we start rendering this viewport virtual void on_post_draw_viewport(RID p_render_target) {} // `on_port_draw_viewport` is called right after we start rendering this viewport (note that on Vulkan draw commands may only be queued) diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp index 3b31e1b1f6..e09ca484d5 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp @@ -50,6 +50,7 @@ void OpenXRExtensionWrapperExtension::_bind_methods() { GDVIRTUAL_BIND(_on_session_created, "session"); GDVIRTUAL_BIND(_on_process); GDVIRTUAL_BIND(_on_pre_render); + GDVIRTUAL_BIND(_on_main_swapchains_created); GDVIRTUAL_BIND(_on_session_destroyed); GDVIRTUAL_BIND(_on_state_idle); GDVIRTUAL_BIND(_on_state_ready); @@ -198,6 +199,10 @@ void OpenXRExtensionWrapperExtension::on_pre_render() { GDVIRTUAL_CALL(_on_pre_render); } +void OpenXRExtensionWrapperExtension::on_main_swapchains_created() { + GDVIRTUAL_CALL(_on_main_swapchains_created); +} + void OpenXRExtensionWrapperExtension::on_session_destroyed() { GDVIRTUAL_CALL(_on_session_destroyed); } diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h index 71d2a57ff8..e37853903b 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h @@ -86,6 +86,7 @@ public: virtual void on_session_created(const XrSession p_session) override; virtual void on_process() override; virtual void on_pre_render() override; + virtual void on_main_swapchains_created() override; virtual void on_session_destroyed() override; GDVIRTUAL0(_on_register_metadata); @@ -95,6 +96,7 @@ public: GDVIRTUAL1(_on_session_created, uint64_t); GDVIRTUAL0(_on_process); GDVIRTUAL0(_on_pre_render); + GDVIRTUAL0(_on_main_swapchains_created); GDVIRTUAL0(_on_session_destroyed); virtual void on_state_idle() override; diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp index bbdd2e3c8a..8ce808dd3c 100644 --- a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp @@ -101,7 +101,7 @@ void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer } } -void OpenXRFBFoveationExtension::on_state_ready() { +void OpenXRFBFoveationExtension::on_main_swapchains_created() { update_profile(); } @@ -127,26 +127,41 @@ void OpenXRFBFoveationExtension::set_foveation_dynamic(XrFoveationDynamicFB p_fo update_profile(); } -void OpenXRFBFoveationExtension::update_profile() { - if (!is_enabled()) { +void OpenXRFBFoveationExtension::_update_profile() { + // Must be called from rendering thread! + ERR_NOT_ON_RENDER_THREAD; + + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + ERR_FAIL_NULL(fov_ext); + + if (!fov_ext->is_enabled()) { + return; + } + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + XrSwapchain main_color_swapchain = openxr_api->get_color_swapchain(); + if (main_color_swapchain == XR_NULL_HANDLE) { + // Our swapchain hasn't been created yet, we'll call this again once it has. return; } XrFoveationLevelProfileCreateInfoFB level_profile_create_info; level_profile_create_info.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB; level_profile_create_info.next = nullptr; - level_profile_create_info.level = foveation_level; + level_profile_create_info.level = fov_ext->foveation_level; level_profile_create_info.verticalOffset = 0.0f; - level_profile_create_info.dynamic = foveation_dynamic; + level_profile_create_info.dynamic = fov_ext->foveation_dynamic; XrFoveationProfileCreateInfoFB profile_create_info; profile_create_info.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB; profile_create_info.next = &level_profile_create_info; XrFoveationProfileFB foveation_profile; - XrResult result = xrCreateFoveationProfileFB(OpenXRAPI::get_singleton()->get_session(), &profile_create_info, &foveation_profile); + XrResult result = fov_ext->xrCreateFoveationProfileFB(openxr_api->get_session(), &profile_create_info, &foveation_profile); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to create the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to create the foveation profile [", openxr_api->get_error_string(result), "]"); return; } @@ -154,15 +169,15 @@ void OpenXRFBFoveationExtension::update_profile() { foveation_update_state.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB; foveation_update_state.profile = foveation_profile; - result = swapchain_update_state_ext->xrUpdateSwapchainFB(OpenXRAPI::get_singleton()->get_color_swapchain(), (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); + result = fov_ext->swapchain_update_state_ext->xrUpdateSwapchainFB(main_color_swapchain, (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to update the swapchain [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to update the swapchain [", openxr_api->get_error_string(result), "]"); // We still want to destroy our profile so keep going... } - result = xrDestroyFoveationProfileFB(foveation_profile); + result = fov_ext->xrDestroyFoveationProfileFB(foveation_profile); if (XR_FAILED(result)) { - print_line("OpenXR: Unable to destroy the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + print_line("OpenXR: Unable to destroy the foveation profile [", openxr_api->get_error_string(result), "]"); } } diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.h b/modules/openxr/extensions/openxr_fb_foveation_extension.h index 1c5e722731..84bd7011b5 100644 --- a/modules/openxr/extensions/openxr_fb_foveation_extension.h +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.h @@ -60,7 +60,7 @@ public: virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override; - virtual void on_state_ready() override; + virtual void on_main_swapchains_created() override; bool is_enabled() const; @@ -82,7 +82,15 @@ private: XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB; XrFoveationDynamicFB foveation_dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB; - void update_profile(); + static void _update_profile(); + + void update_profile() { + // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready... + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + rendering_server->call_on_render_thread(callable_mp_static(&OpenXRFBFoveationExtension::_update_profile)); + } // Enable foveation on this swapchain XrSwapchainCreateInfoFoveationFB swapchain_create_info_foveation_fb; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 32512070d6..541e369925 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1220,6 +1220,10 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { } }; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_main_swapchains_created(); + } + return true; }; diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index 50cc7b9e7b..b02f3082ab 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -165,7 +165,9 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) { return; } - ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); + if (p_viewport != nullptr) { + ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); + } layer_viewport = p_viewport; diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 58e1868e31..2f7a175d91 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -1,14 +1,15 @@ #!/usr/bin/env python import atexit import sys -import methods import time +import methods + # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://github.com/python/cpython/issues/73245> if sys.stdout.isatty() and sys.platform == "win32": try: - from ctypes import windll, byref, WinError # type: ignore + from ctypes import WinError, byref, windll # type: ignore from ctypes.wintypes import DWORD # type: ignore stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) @@ -720,7 +721,7 @@ if env["static_icu_data"]: env.Append(CXXFLAGS=["-DICU_STATIC_DATA"]) env.Append(CPPPATH=["../../../thirdparty/icu4c/"]) else: - thirdparty_sources += ["../icu_data/icudata_stub.cpp"] + thirdparty_icu_sources += ["../icu_data/icudata_stub.cpp"] env_icu.Append(CPPPATH=["../../../thirdparty/icu4c/common/", "../../../thirdparty/icu4c/i18n/"]) env_icu.Append( diff --git a/modules/text_server_adv/gdextension_build/methods.py b/modules/text_server_adv/gdextension_build/methods.py index 3453c3e8f0..43bf56abfe 100644 --- a/modules/text_server_adv/gdextension_build/methods.py +++ b/modules/text_server_adv/gdextension_build/methods.py @@ -81,9 +81,9 @@ def disable_warnings(self): self.Append(CCFLAGS=["/w"]) self.Append(CFLAGS=["/w"]) self.Append(CXXFLAGS=["/w"]) - self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not x in warn_flags] - self["CFLAGS"] = [x for x in self["CFLAGS"] if not x in warn_flags] - self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not x in warn_flags] + self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in warn_flags] + self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in warn_flags] + self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in warn_flags] else: self.Append(CCFLAGS=["-w"]) self.Append(CFLAGS=["-w"]) @@ -117,31 +117,31 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) with open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") as f: - 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' - ) - f.write(f'<plist version="1.0">\n') - f.write(f"<dict>\n") - f.write(f"\t<key>CFBundleExecutable</key>\n") - f.write(f"\t<string>{binary_name}</string>\n") - f.write(f"\t<key>CFBundleIdentifier</key>\n") - f.write(f"\t<string>{identifier}</string>\n") - f.write(f"\t<key>CFBundleInfoDictionaryVersion</key>\n") - f.write(f"\t<string>6.0</string>\n") - f.write(f"\t<key>CFBundleName</key>\n") - f.write(f"\t<string>{name}</string>\n") - f.write(f"\t<key>CFBundlePackageType</key>\n") - f.write(f"\t<string>FMWK</string>\n") - f.write(f"\t<key>CFBundleShortVersionString</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>CFBundleSupportedPlatforms</key>\n") - f.write(f"\t<array>\n") - f.write(f"\t\t<string>MacOSX</string>\n") - f.write(f"\t</array>\n") - f.write(f"\t<key>CFBundleVersion</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>LSMinimumSystemVersion</key>\n") - f.write(f"\t<string>10.14</string>\n") - f.write(f"</dict>\n") - f.write(f"</plist>\n") + f.write(f"""\ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>{binary_name}</string> + <key>CFBundleIdentifier</key> + <string>{identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>{name}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>MacOSX</string> + </array> + <key>CFBundleVersion</key> + <string>1.0.0</string> + <key>LSMinimumSystemVersion</key> + <string>10.14</string> +</dict> +</plist> +""") diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 9326a53026..f6ae7149be 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -1,14 +1,15 @@ #!/usr/bin/env python import atexit import sys -import methods import time +import methods + # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://github.com/python/cpython/issues/73245> if sys.stdout.isatty() and sys.platform == "win32": try: - from ctypes import windll, byref, WinError # type: ignore + from ctypes import WinError, byref, windll # type: ignore from ctypes.wintypes import DWORD # type: ignore stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) diff --git a/modules/text_server_fb/gdextension_build/methods.py b/modules/text_server_fb/gdextension_build/methods.py index 3453c3e8f0..43bf56abfe 100644 --- a/modules/text_server_fb/gdextension_build/methods.py +++ b/modules/text_server_fb/gdextension_build/methods.py @@ -81,9 +81,9 @@ def disable_warnings(self): self.Append(CCFLAGS=["/w"]) self.Append(CFLAGS=["/w"]) self.Append(CXXFLAGS=["/w"]) - self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not x in warn_flags] - self["CFLAGS"] = [x for x in self["CFLAGS"] if not x in warn_flags] - self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not x in warn_flags] + self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in warn_flags] + self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in warn_flags] + self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in warn_flags] else: self.Append(CCFLAGS=["-w"]) self.Append(CFLAGS=["-w"]) @@ -117,31 +117,31 @@ def make_icu_data(target, source, env): def write_macos_plist(target, binary_name, identifier, name): os.makedirs(f"{target}/Resource/", exist_ok=True) with open(f"{target}/Resource/Info.plist", "w", encoding="utf-8", newline="\n") as f: - 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' - ) - f.write(f'<plist version="1.0">\n') - f.write(f"<dict>\n") - f.write(f"\t<key>CFBundleExecutable</key>\n") - f.write(f"\t<string>{binary_name}</string>\n") - f.write(f"\t<key>CFBundleIdentifier</key>\n") - f.write(f"\t<string>{identifier}</string>\n") - f.write(f"\t<key>CFBundleInfoDictionaryVersion</key>\n") - f.write(f"\t<string>6.0</string>\n") - f.write(f"\t<key>CFBundleName</key>\n") - f.write(f"\t<string>{name}</string>\n") - f.write(f"\t<key>CFBundlePackageType</key>\n") - f.write(f"\t<string>FMWK</string>\n") - f.write(f"\t<key>CFBundleShortVersionString</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>CFBundleSupportedPlatforms</key>\n") - f.write(f"\t<array>\n") - f.write(f"\t\t<string>MacOSX</string>\n") - f.write(f"\t</array>\n") - f.write(f"\t<key>CFBundleVersion</key>\n") - f.write(f"\t<string>1.0.0</string>\n") - f.write(f"\t<key>LSMinimumSystemVersion</key>\n") - f.write(f"\t<string>10.14</string>\n") - f.write(f"</dict>\n") - f.write(f"</plist>\n") + f.write(f"""\ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>{binary_name}</string> + <key>CFBundleIdentifier</key> + <string>{identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>{name}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0</string> + <key>CFBundleSupportedPlatforms</key> + <array> + <string>MacOSX</string> + </array> + <key>CFBundleVersion</key> + <string>1.0.0</string> + <key>LSMinimumSystemVersion</key> + <string>10.14</string> +</dict> +</plist> +""") diff --git a/platform/SCsub b/platform/SCsub index b24c189848..b07023efed 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -1,9 +1,10 @@ #!/usr/bin/env python -import methods from glob import glob from pathlib import Path +import methods + Import("env") env.platform_sources = [] diff --git a/platform/android/SCsub b/platform/android/SCsub index 4d76ffb180..bc1b5e9200 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -1,7 +1,8 @@ #!/usr/bin/env python -import sys import subprocess +import sys + from methods import print_warning Import("env") diff --git a/platform/android/detect.py b/platform/android/detect.py index 6a8c4ed86d..485f31dee2 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -1,10 +1,11 @@ import os -import sys import platform import subprocess -from methods import print_warning, print_error +import sys from typing import TYPE_CHECKING +from methods import print_error, print_warning + if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index d27e75b07a..c404af34d8 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -330,8 +330,7 @@ ext.getReleaseKeyAlias = { -> } ext.isAndroidStudio = { -> - def sysProps = System.getProperties() - return sysProps != null && sysProps['idea.platform.prefix'] != null + return project.hasProperty('android.injected.invoked.from.ide') } ext.shouldZipAlign = { -> diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index c609b33ef4..b91b023ce6 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -232,11 +232,6 @@ def generateBuildTasks(String flavor = "template") { return tasks } -def isAndroidStudio() { - def sysProps = System.getProperties() - return sysProps != null && sysProps['idea.platform.prefix'] != null -} - task copyEditorReleaseApkToBin(type: Copy) { dependsOn ':editor:assembleRelease' from('editor/build/outputs/apk/release') diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt index 89fbb9f580..cfbbcf7d0e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt @@ -61,6 +61,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi private var contextClickInProgress = false private var pointerCaptureInProgress = false + private var lastDragX: Float = 0.0f + private var lastDragY: Float = 0.0f + override fun onDown(event: MotionEvent): Boolean { GodotInputHandler.handleMotionEvent(event.source, MotionEvent.ACTION_DOWN, event.buttonState, event.x, event.y, nextDownIsDoubleTap) nextDownIsDoubleTap = false @@ -165,6 +168,8 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi pointerCaptureInProgress = false dragInProgress = false contextClickInProgress = false + lastDragX = 0.0f + lastDragY = 0.0f return true } @@ -189,6 +194,17 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi sourceMouseRelative ) return true + } else if (!scaleInProgress) { + // The 'onScroll' event is triggered with a long delay. + // Force the 'InputEventScreenDrag' event earlier here. + // We don't toggle 'dragInProgress' here so that the scaling logic can override the drag operation if needed. + // Once the 'onScroll' event kicks-in, 'dragInProgress' will be properly set. + if (lastDragX != event.getX(0) || lastDragY != event.getY(0)) { + lastDragX = event.getX(0) + lastDragY = event.getY(0) + GodotInputHandler.handleMotionEvent(event) + return true + } } return false } @@ -216,7 +232,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi distanceY: Float ): Boolean { if (scaleInProgress) { - if (dragInProgress) { + if (dragInProgress || lastDragX != 0.0f || lastDragY != 0.0f) { if (originEvent != null) { // Cancel the drag GodotInputHandler.handleMotionEvent( @@ -228,6 +244,8 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi ) } dragInProgress = false + lastDragX = 0.0f + lastDragY = 0.0f } } @@ -235,8 +253,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi val y = terminusEvent.y if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) { GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f) - } else if (!scaleInProgress){ + } else if (!scaleInProgress) { dragInProgress = true + lastDragX = terminusEvent.getX(0) + lastDragY = terminusEvent.getY(0) GodotInputHandler.handleMotionEvent(terminusEvent) } return true diff --git a/platform/ios/SCsub b/platform/ios/SCsub index f38914434b..cff7dcc1fd 100644 --- a/platform/ios/SCsub +++ b/platform/ios/SCsub @@ -2,11 +2,11 @@ Import("env") -import os, json -from platform_methods import architectures, lipo, get_build_version, detect_mvk -import subprocess +import os import shutil +from platform_methods import detect_mvk, lipo + def generate_bundle(target, source, env): bin_dir = Dir("#bin").abspath diff --git a/platform/ios/detect.py b/platform/ios/detect.py index e3bac4ec5c..eb233a5d54 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -1,9 +1,9 @@ import os import sys -from methods import print_error, detect_darwin_sdk_path - from typing import TYPE_CHECKING +from methods import detect_darwin_sdk_path, print_error + if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 47f3bcadc9..28825038ca 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -1,11 +1,11 @@ import os import platform import sys -from methods import print_warning, print_error, get_compiler_version, using_gcc -from platform_methods import detect_arch - from typing import TYPE_CHECKING +from methods import get_compiler_version, print_error, print_warning, using_gcc +from platform_methods import detect_arch + if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 59ef4ee85c..eb8524826f 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -2,11 +2,13 @@ Import("env") -import os, json -from platform_methods import architectures, lipo, get_build_version -import platform_macos_builders -import subprocess +import os import shutil +import subprocess + +import platform_macos_builders + +from platform_methods import get_build_version, lipo def generate_bundle(target, source, env): diff --git a/platform/macos/detect.py b/platform/macos/detect.py index a5ef29e34f..f02f693dd9 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -1,10 +1,10 @@ import os import sys -from methods import print_error, detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang -from platform_methods import detect_arch, detect_mvk - from typing import TYPE_CHECKING +from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error +from platform_methods import detect_arch, detect_mvk + if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment @@ -107,7 +107,7 @@ def configure(env: "SConsEnvironment"): env.Append(CCFLAGS=["-fobjc-arc"]) - if not "osxcross" in env: # regular native build + if "osxcross" not in env: # regular native build if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") mpclangver = env["macports_clang"] diff --git a/platform/web/SCsub b/platform/web/SCsub index bc5893ab3a..fea2fa7df9 100644 --- a/platform/web/SCsub +++ b/platform/web/SCsub @@ -6,9 +6,10 @@ Import("env") # The HTTP server "targets". Run with "scons p=web serve", or "scons p=web run" if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS: - from serve import serve import os + from serve import serve + port = os.environ.get("GODOT_WEB_TEST_PORT", 8060) try: port = int(port) diff --git a/platform/web/detect.py b/platform/web/detect.py index ccd884b225..524ff44f4d 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -1,18 +1,19 @@ import os import sys +from typing import TYPE_CHECKING from emscripten_helpers import ( - run_closure_compiler, - create_engine_file, + add_js_externs, add_js_libraries, add_js_pre, - add_js_externs, + create_engine_file, create_template_zip, get_template_zip_path, + run_closure_compiler, ) -from methods import print_warning, print_error, get_compiler_version from SCons.Util import WhereIs -from typing import TYPE_CHECKING + +from methods import get_compiler_version, print_error, print_warning if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py index 3ba133c9a1..745b2457fa 100644 --- a/platform/web/emscripten_helpers.py +++ b/platform/web/emscripten_helpers.py @@ -1,4 +1,5 @@ -import os, json +import json +import os from SCons.Util import WhereIs @@ -24,13 +25,13 @@ def get_build_version(): import version name = "custom_build" - if os.getenv("BUILD_NAME") != None: + if os.getenv("BUILD_NAME") is not None: name = os.getenv("BUILD_NAME") v = "%d.%d" % (version.major, version.minor) if version.patch > 0: v += ".%d" % version.patch status = version.status - if os.getenv("GODOT_VERSION_STATUS") != None: + if os.getenv("GODOT_VERSION_STATUS") is not None: status = str(os.getenv("GODOT_VERSION_STATUS")) v += ".%s.%s" % (status, name) return v diff --git a/platform/web/serve.py b/platform/web/serve.py index 89dff63ca3..f0b0ec9622 100755 --- a/platform/web/serve.py +++ b/platform/web/serve.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore -from pathlib import Path -import os -import sys import argparse import contextlib +import os import socket import subprocess +import sys +from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore +from pathlib import Path # See cpython GH-17851 and GH-17864. diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 435c501956..1c2bfb9b75 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -4,6 +4,7 @@ Import("env") import os from pathlib import Path + import platform_windows_builders sources = [] diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 93eb34001e..b66cdadc41 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -1,12 +1,12 @@ -import methods import os import subprocess import sys -from methods import print_warning, print_error -from platform_methods import detect_arch - from typing import TYPE_CHECKING +import methods +from methods import print_error, print_warning +from platform_methods import detect_arch + if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment @@ -178,7 +178,7 @@ def get_opts(): caller_frame = inspect.stack()[1] caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1])) d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") - except: # Give up. + except Exception: # Give up. d3d12_deps_folder = "" return [ @@ -523,7 +523,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): env.Append(CXXFLAGS=["/bigobj"]) # PIX - if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): + if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): env["use_pix"] = False if env["use_pix"]: @@ -750,7 +750,7 @@ def configure_mingw(env: "SConsEnvironment"): env.Append(LIBS=["dxgi", "dxguid"]) # PIX - if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): + if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): env["use_pix"] = False if env["use_pix"]: diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index 729d55cea6..3fd9e1a581 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -1,8 +1,8 @@ """Functions used to generate source files during build time""" import os -from detect import get_mingw_bin_prefix -from detect import try_cmd + +from detect import get_mingw_bin_prefix, try_cmd def make_debug_mingw(target, source, env): diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 6fc33afe99..6c54faa13a 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -42,20 +42,30 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er return; } - const unsigned int BUFFER_SIZE = 16384; - char buf[BUFFER_SIZE + 1]; // +1 for the terminating character - int len = vsnprintf(buf, BUFFER_SIZE, p_format, p_list); - if (len <= 0) { - return; + const int static_buffer_size = 1024; + char static_buf[static_buffer_size]; + char *buf = static_buf; + va_list list_copy; + va_copy(list_copy, p_list); + int len = vsnprintf(buf, static_buffer_size, p_format, p_list); + if (len >= static_buffer_size) { + buf = (char *)memalloc(len + 1); + len = vsnprintf(buf, len + 1, p_format, list_copy); + } + va_end(list_copy); + + String str_buf = String::utf8(buf, len).replace("\r\n", "\n").replace("\n", "\r\n"); + if (len >= static_buffer_size) { + memfree(buf); } - if ((unsigned int)len >= BUFFER_SIZE) { - len = BUFFER_SIZE; // Output is too big, will be truncated + CharString cstr_buf = str_buf.utf8(); + if (cstr_buf.length() == 0) { + return; } - buf[len] = 0; DWORD written = 0; HANDLE h = p_err ? GetStdHandle(STD_ERROR_HANDLE) : GetStdHandle(STD_OUTPUT_HANDLE); - WriteFile(h, &buf[0], len, &written, nullptr); + WriteFile(h, cstr_buf.ptr(), cstr_buf.length(), &written, nullptr); #ifdef DEBUG_ENABLED FlushFileBuffers(h); diff --git a/platform_methods.py b/platform_methods.py index 5326e36077..2b157da22b 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -1,10 +1,7 @@ import os -import sys -import json import platform -import uuid -import functools import subprocess + import methods # NOTE: The multiprocessing module is not compatible with SCons due to conflict on cPickle @@ -47,14 +44,14 @@ def get_build_version(short): import version name = "custom_build" - if os.getenv("BUILD_NAME") != None: + if os.getenv("BUILD_NAME") is not None: name = os.getenv("BUILD_NAME") v = "%d.%d" % (version.major, version.minor) if version.patch > 0: v += ".%d" % version.patch status = version.status if not short: - if os.getenv("GODOT_VERSION_STATUS") != None: + if os.getenv("GODOT_VERSION_STATUS") is not None: status = str(os.getenv("GODOT_VERSION_STATUS")) v += ".%s.%s" % (status, name) return v @@ -86,7 +83,7 @@ def get_mvk_sdk_path(osname): def int_or_zero(i): try: return int(i) - except: + except (TypeError, ValueError): return 0 def ver_parse(a): @@ -140,9 +137,8 @@ def detect_mvk(env, osname): ) for mvk_path in mvk_list: - if mvk_path and os.path.isfile(os.path.join(mvk_path, osname + "/libMoltenVK.a")): - mvk_found = True - print("MoltenVK found at: " + mvk_path) + if mvk_path and os.path.isfile(os.path.join(mvk_path, f"{osname}/libMoltenVK.a")): + print(f"MoltenVK found at: {mvk_path}") return mvk_path return "" diff --git a/pyproject.toml b/pyproject.toml index f1ea10fbae..34ae075f2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,19 @@ namespace_packages = true explicit_package_bases = true exclude = ["thirdparty/"] -[tool.black] +[tool.ruff] +extend-exclude = ["thirdparty"] +extend-include = ["SConstruct", "SCsub"] line-length = 120 -extend-exclude = ".*thirdparty/.*" +target-version = "py37" + +[tool.ruff.lint] +extend-select = [ + "I", # isort +] + +[tool.ruff.lint.per-file-ignores] +"{SConstruct,SCsub}" = [ + "E402", # Module level import not at top of file + "F821", # Undefined name +] diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 523d81e874..f7d672620d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -225,91 +225,6 @@ int TileMap::get_rendering_quadrant_size() const { return rendering_quadrant_size; } -void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) { - ERR_FAIL_COND(!p_tile_set.is_valid()); - ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Check for the frame. - if (p_frame >= 0) { - ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); - } - - // Get the texture. - Ref<Texture2D> tex = atlas_source->get_runtime_texture(); - if (!tex.is_valid()) { - return; - } - - // Check if we are in the texture, return otherwise. - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { - return; - } - - // Get tile data. - const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); - - // Get the tile modulation. - Color modulate = tile_data->get_modulate() * p_modulation; - - // Compute the offset. - Vector2 tile_offset = tile_data->get_texture_origin(); - - // Get destination rect. - Rect2 dest_rect; - dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; - dest_rect.size.x += FP_ADJUST; - dest_rect.size.y += FP_ADJUST; - - bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - if (transpose) { - dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); - } else { - dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); - } - - if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { - dest_rect.size.x = -dest_rect.size.x; - } - - if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { - dest_rect.size.y = -dest_rect.size.y; - } - - // Draw the tile. - if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else { - real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); - real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; - real_t animation_offset = p_normalized_animation_offset * animation_duration; - // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors. - // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`. - real_t time_unscaled = 0.0; - for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { - real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame); - real_t slice_start = time_unscaled / speed; - real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed; - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset); - - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - - time_unscaled += frame_duration_unscaled; - } - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); - } - } -} - void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { if (p_tileset == tile_set) { return; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 45604bfb8a..690102f730 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -63,8 +63,6 @@ private: // A compatibility enum to specify how is the data if formatted. mutable TileMapDataFormat format = TileMapDataFormat::TILE_MAP_DATA_FORMAT_3; - static constexpr float FP_ADJUST = 0.00001; - // Properties. Ref<TileSet> tile_set; int rendering_quadrant_size = 16; @@ -123,8 +121,6 @@ public: void set_rendering_quadrant_size(int p_size); int get_rendering_quadrant_size() const; - static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0); - // Accessors. void set_tileset(const Ref<TileSet> &p_tileset); Ref<TileSet> get_tileset() const; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 03fd7364c0..0ac236eaa7 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -340,7 +340,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { } // Drawing the tile in the canvas item. - TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset); + draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset); } // Reset physics interpolation for any recreated canvas items. @@ -603,6 +603,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); + rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index)); } else { // Clear occluder. if (occluder.is_valid()) { @@ -1695,18 +1696,18 @@ void TileMapLayer::_notification(int p_what) { _internal_update(true); } break; - case TileMap::NOTIFICATION_ENTER_CANVAS: { + case NOTIFICATION_ENTER_CANVAS: { dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; _queue_internal_update(); } break; - case TileMap::NOTIFICATION_EXIT_CANVAS: { + case NOTIFICATION_EXIT_CANVAS: { dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; // Update immediately on exiting, and force cleanup. _internal_update(true); } break; - case TileMap::NOTIFICATION_VISIBILITY_CHANGED: { + case NOTIFICATION_VISIBILITY_CHANGED: { dirty.flags[DIRTY_FLAGS_LAYER_VISIBILITY] = true; _queue_internal_update(); } break; @@ -2161,6 +2162,91 @@ TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords) const { } } +void TileMapLayer::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) { + ERR_FAIL_COND(p_tile_set.is_null()); + ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } + + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); + if (tex.is_null()) { + return; + } + + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + + // Get tile data. + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); + + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; + + // Compute the offset. + Vector2 tile_offset = tile_data->get_texture_origin(); + + // Get destination rect. + Rect2 dest_rect; + dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; + dest_rect.size.x += FP_ADJUST; + dest_rect.size.y += FP_ADJUST; + + bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Draw the tile. + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t animation_offset = p_normalized_animation_offset * animation_duration; + // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors. + // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`. + real_t time_unscaled = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame); + real_t slice_start = time_unscaled / speed; + real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset); + + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + + time_unscaled += frame_duration_unscaled; + } + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + } + } +} + void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile) { // Set the current cell tile (using integer position). Vector2i pk(p_coords); @@ -2211,7 +2297,7 @@ void TileMapLayer::erase_cell(const Vector2i &p_coords) { } void TileMapLayer::fix_invalid_tiles() { - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot call fix_invalid_tiles() on a TileMapLayer without a valid TileSet."); RBSet<Vector2i> coords; for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 5861433c8a..57c83d7c4c 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -269,6 +269,8 @@ public: }; private: + static constexpr float FP_ADJUST = 0.00001; + // Properties. HashMap<Vector2i, CellData> tile_map_layer_data; @@ -407,6 +409,8 @@ public: // Not exposed to users. TileMapCell get_cell(const Vector2i &p_coords) const; + static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0); + ////////////// Exposed functions ////////////// // --- Cells manipulation --- diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 498e101b3c..a4804e928a 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -847,12 +847,13 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { ERR_FAIL_INDEX(p_bone_idx, bone_size); Bone *bonesptr = bones.ptrw(); - List<int> bones_to_process = List<int>(); + thread_local LocalVector<int> bones_to_process; + bones_to_process.clear(); bones_to_process.push_back(p_bone_idx); - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process.front()->get(); - bones_to_process.erase(current_bone_idx); + uint32_t index = 0; + while (index < bones_to_process.size()) { + int current_bone_idx = bones_to_process[index]; Bone &b = bonesptr[current_bone_idx]; bool bone_enabled = b.enabled && !show_rest_only; @@ -905,6 +906,8 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { for (int i = 0; i < child_bone_size; i++) { bones_to_process.push_back(b.child_bones[i]); } + + index++; } } diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp index 918b5c0b41..002db30379 100644 --- a/scene/resources/portable_compressed_texture.cpp +++ b/scene/resources/portable_compressed_texture.cpp @@ -153,14 +153,14 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { Vector<uint8_t> data; if (p_compression_mode == COMPRESSION_MODE_LOSSY) { - data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality); encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); } else { if (use_webp) { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image); encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); } else { - data = Image::png_packer(p_image->get_image_from_mipmap(i)); + data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image); encode_uint16(DATA_FORMAT_PNG, buffer.ptrw() + 2); } } diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 8267e958b4..dfe5bd4a47 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -173,7 +173,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr } } #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) { + if (EditorHelp::get_doc_data() != nullptr && Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) { EditorHelp::get_doc_data()->add_doc(class_doc); } #endif diff --git a/scene/theme/SCsub b/scene/theme/SCsub index 5f62ae4b05..2372d1820a 100644 --- a/scene/theme/SCsub +++ b/scene/theme/SCsub @@ -4,7 +4,6 @@ Import("env") import default_theme_builders - env.add_source_files(env.scene_sources, "*.cpp") SConscript("icons/SCsub") diff --git a/scene/theme/icons/SCsub b/scene/theme/icons/SCsub index 46133ccceb..1f3b7f6d17 100644 --- a/scene/theme/icons/SCsub +++ b/scene/theme/icons/SCsub @@ -4,7 +4,6 @@ Import("env") import default_theme_icons_builders - env["BUILDERS"]["MakeDefaultThemeIconsBuilder"] = Builder( action=env.Run(default_theme_icons_builders.make_default_theme_icons_action), suffix=".h", diff --git a/scu_builders.py b/scu_builders.py index a9ae428222..fc5461196f 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -1,11 +1,11 @@ -"""Functions used to generate scu build source files during build time -""" +"""Functions used to generate scu build source files during build time""" -import glob, os +import glob import math -from methods import print_error +import os from pathlib import Path -from os.path import normpath, basename + +from methods import print_error base_folder_path = str(Path(__file__).parent) + "/" base_folder_only = os.path.basename(os.path.normpath(base_folder_path)) @@ -25,7 +25,7 @@ def clear_out_stale_files(output_folder, extension, fresh_files): for file in glob.glob(output_folder + "/*." + extension): file = Path(file) - if not file in fresh_files: + if file not in fresh_files: # print("removed stale file: " + str(file)) os.remove(file) @@ -56,7 +56,7 @@ def find_files_in_folder(folder, sub_folder, include_list, extension, sought_exc li = '#include "' + folder + "/" + sub_folder_slashed + file + '"' - if not simple_name in sought_exceptions: + if simple_name not in sought_exceptions: include_list.append(li) else: found_exceptions.append(li) @@ -78,9 +78,9 @@ def write_output_file(file_count, include_list, start_line, end_line, output_fol file_text = "" - for l in range(start_line, end_line): - if l < len(include_list): - line = include_list[l] + for i in range(start_line, end_line): + if i < len(include_list): + line = include_list[i] li = line + "\n" file_text += li @@ -221,7 +221,6 @@ def process_folder(folders, sought_exceptions=[], includes_per_scu=0, extension= lines_per_file = max(lines_per_file, 1) start_line = 0 - file_number = 0 # These do not vary throughout the loop output_folder = abs_main_folder + "/scu/" diff --git a/servers/rendering/rendering_light_culler.h b/servers/rendering/rendering_light_culler.h index 0bf975430b..b0437d2310 100644 --- a/servers/rendering/rendering_light_culler.h +++ b/servers/rendering/rendering_light_culler.h @@ -181,14 +181,14 @@ private: } // Prevent divide by zero. - if (lc > 0.00001f) { + if (lc > 0.001f) { // If the summed length of the smaller two // sides is close to the length of the longest side, // the points are colinear, and the triangle is near degenerate. float ld = ((la + lb) - lc) / lc; // ld will be close to zero for colinear tris. - return ld < 0.00001f; + return ld < 0.001f; } // Don't create planes from tiny triangles, diff --git a/tests/python_build/test_gles3_builder.py b/tests/python_build/test_gles3_builder.py index 6f16139eb9..b34d33bde7 100644 --- a/tests/python_build/test_gles3_builder.py +++ b/tests/python_build/test_gles3_builder.py @@ -2,7 +2,7 @@ import json import pytest -from gles3_builders import build_gles3_header, GLES3HeaderStruct +from gles3_builders import GLES3HeaderStruct, build_gles3_header @pytest.mark.parametrize( diff --git a/tests/python_build/test_glsl_builder.py b/tests/python_build/test_glsl_builder.py index 348ef8441c..9f548855ff 100644 --- a/tests/python_build/test_glsl_builder.py +++ b/tests/python_build/test_glsl_builder.py @@ -2,7 +2,7 @@ import json import pytest -from glsl_builders import build_raw_header, RAWHeaderStruct, build_rd_header, RDHeaderStruct +from glsl_builders import RAWHeaderStruct, RDHeaderStruct, build_raw_header, build_rd_header @pytest.mark.parametrize( |