diff options
104 files changed, 1014 insertions, 931 deletions
diff --git a/SConstruct b/SConstruct index f6c3b33fc6..dbcd9dfd68 100644 --- a/SConstruct +++ b/SConstruct @@ -121,38 +121,38 @@ elif os.name == "nt" and methods.get_cmdline_bool("use_mingw", False): # We let SCons build its default ENV as it includes OS-specific things which we don't # want to have to pull in manually. # Then we prepend PATH to make it take precedence, while preserving SCons' own entries. -env_base = Environment(tools=custom_tools) -env_base.PrependENVPath("PATH", os.getenv("PATH")) -env_base.PrependENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) +env = Environment(tools=custom_tools) +env.PrependENVPath("PATH", os.getenv("PATH")) +env.PrependENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) if "TERM" in os.environ: # Used for colored output. - env_base["ENV"]["TERM"] = os.environ["TERM"] + env["ENV"]["TERM"] = os.environ["TERM"] -env_base.disabled_modules = [] -env_base.module_version_string = "" -env_base.msvc = False +env.disabled_modules = [] +env.module_version_string = "" +env.msvc = False -env_base.__class__.disable_module = methods.disable_module +env.__class__.disable_module = methods.disable_module -env_base.__class__.add_module_version_string = methods.add_module_version_string +env.__class__.add_module_version_string = methods.add_module_version_string -env_base.__class__.add_source_files = methods.add_source_files -env_base.__class__.use_windows_spawn_fix = methods.use_windows_spawn_fix +env.__class__.add_source_files = methods.add_source_files +env.__class__.use_windows_spawn_fix = methods.use_windows_spawn_fix -env_base.__class__.add_shared_library = methods.add_shared_library -env_base.__class__.add_library = methods.add_library -env_base.__class__.add_program = methods.add_program -env_base.__class__.CommandNoCache = methods.CommandNoCache -env_base.__class__.Run = methods.Run -env_base.__class__.disable_warnings = methods.disable_warnings -env_base.__class__.force_optimization_on_debug = methods.force_optimization_on_debug -env_base.__class__.module_add_dependencies = methods.module_add_dependencies -env_base.__class__.module_check_dependencies = methods.module_check_dependencies +env.__class__.add_shared_library = methods.add_shared_library +env.__class__.add_library = methods.add_library +env.__class__.add_program = methods.add_program +env.__class__.CommandNoCache = methods.CommandNoCache +env.__class__.Run = methods.Run +env.__class__.disable_warnings = methods.disable_warnings +env.__class__.force_optimization_on_debug = methods.force_optimization_on_debug +env.__class__.module_add_dependencies = methods.module_add_dependencies +env.__class__.module_check_dependencies = methods.module_check_dependencies -env_base["x86_libtheora_opt_gcc"] = False -env_base["x86_libtheora_opt_vc"] = False +env["x86_libtheora_opt_gcc"] = False +env["x86_libtheora_opt_vc"] = False # avoid issues when building with different versions of python out of the same directory -env_base.SConsignFile(File("#.sconsign{0}.dblite".format(pickle.HIGHEST_PROTOCOL)).abspath) +env.SConsignFile(File("#.sconsign{0}.dblite".format(pickle.HIGHEST_PROTOCOL)).abspath) # Build options @@ -275,22 +275,22 @@ opts.Add("linkflags", "Custom flags for the linker") # Update the environment to have all above options defined # in following code (especially platform and custom_modules). -opts.Update(env_base) +opts.Update(env) # Copy custom environment variables if set. -if env_base["import_env_vars"]: - for env_var in str(env_base["import_env_vars"]).split(","): +if env["import_env_vars"]: + for env_var in str(env["import_env_vars"]).split(","): if env_var in os.environ: - env_base["ENV"][env_var] = os.environ[env_var] + env["ENV"][env_var] = os.environ[env_var] # Platform selection: validate input, and add options. selected_platform = "" -if env_base["platform"] != "": - selected_platform = env_base["platform"] -elif env_base["p"] != "": - selected_platform = env_base["p"] +if env["platform"] != "": + selected_platform = env["platform"] +elif env["p"] != "": + selected_platform = env["p"] else: # Missing `platform` argument, try to detect platform automatically if ( @@ -343,7 +343,7 @@ if selected_platform not in platform_list: # Make sure to update this to the found, valid platform as it's used through the buildsystem as the reference. # It should always be re-set after calling `opts.Update()` otherwise it uses the original input value. -env_base["platform"] = selected_platform +env["platform"] = selected_platform # Add platform-specific options. if selected_platform in platform_opts: @@ -351,15 +351,15 @@ if selected_platform in platform_opts: opts.Add(opt) # Update the environment to take platform-specific options into account. -opts.Update(env_base) -env_base["platform"] = selected_platform # Must always be re-set after calling opts.Update(). +opts.Update(env) +env["platform"] = selected_platform # Must always be re-set after calling opts.Update(). # Detect modules. modules_detected = OrderedDict() module_search_paths = ["modules"] # Built-in path. -if env_base["custom_modules"]: - paths = env_base["custom_modules"].split(",") +if env["custom_modules"]: + paths = env["custom_modules"].split(",") for p in paths: try: module_search_paths.append(methods.convert_custom_modules_path(p)) @@ -373,13 +373,13 @@ for path in module_search_paths: # so save the time it takes to parse directories. modules = methods.detect_modules(path, recursive=False) else: # Custom. - modules = methods.detect_modules(path, env_base["custom_modules_recursive"]) + modules = methods.detect_modules(path, env["custom_modules_recursive"]) # Provide default include path for both the custom module search `path` # and the base directory containing custom modules, as it may be different # from the built-in "modules" name (e.g. "custom_modules/summator/summator.h"), # so it can be referenced simply as `#include "summator/summator.h"` # independently of where a module is located on user's filesystem. - env_base.Prepend(CPPPATH=[path, os.path.dirname(path)]) + env.Prepend(CPPPATH=[path, os.path.dirname(path)]) # Note: custom modules can override built-in ones. modules_detected.update(modules) @@ -388,7 +388,7 @@ for name, path in modules_detected.items(): sys.path.insert(0, path) import config - if env_base["modules_enabled_by_default"]: + if env["modules_enabled_by_default"]: enabled = True try: enabled = config.is_enabled() @@ -412,17 +412,17 @@ for name, path in modules_detected.items(): methods.write_modules(modules_detected) # Update the environment again after all the module options are added. -opts.Update(env_base) -env_base["platform"] = selected_platform # Must always be re-set after calling opts.Update(). -Help(opts.GenerateHelpText(env_base)) +opts.Update(env) +env["platform"] = selected_platform # Must always be re-set after calling opts.Update(). +Help(opts.GenerateHelpText(env)) # add default include paths -env_base.Prepend(CPPPATH=["#"]) +env.Prepend(CPPPATH=["#"]) # configure ENV for platform -env_base.platform_exporters = platform_exporters -env_base.platform_apis = platform_apis +env.platform_exporters = platform_exporters +env.platform_apis = platform_apis # Configuration of build targets: # - Editor or template @@ -431,71 +431,69 @@ env_base.platform_apis = platform_apis # - Optimization level # - Debug symbols for crash traces / debuggers -env_base.editor_build = env_base["target"] == "editor" -env_base.dev_build = env_base["dev_build"] -env_base.debug_features = env_base["target"] in ["editor", "template_debug"] +env.editor_build = env["target"] == "editor" +env.dev_build = env["dev_build"] +env.debug_features = env["target"] in ["editor", "template_debug"] -if env_base.dev_build: +if env.dev_build: opt_level = "none" -elif env_base.debug_features: +elif env.debug_features: opt_level = "speed_trace" else: # Release opt_level = "speed" -env_base["optimize"] = ARGUMENTS.get("optimize", opt_level) -env_base["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", env_base.dev_build) +env["optimize"] = ARGUMENTS.get("optimize", opt_level) +env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", env.dev_build) -if env_base.editor_build: - env_base.Append(CPPDEFINES=["TOOLS_ENABLED"]) +if env.editor_build: + env.Append(CPPDEFINES=["TOOLS_ENABLED"]) -if env_base.debug_features: +if env.debug_features: # DEBUG_ENABLED enables debugging *features* and debug-only code, which is intended # to give *users* extra debugging information for their game development. - env_base.Append(CPPDEFINES=["DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) -if env_base.dev_build: +if env.dev_build: # DEV_ENABLED enables *engine developer* code which should only be compiled for those # working on the engine itself. - env_base.Append(CPPDEFINES=["DEV_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) else: # Disable assert() for production targets (only used in thirdparty code). - env_base.Append(CPPDEFINES=["NDEBUG"]) + env.Append(CPPDEFINES=["NDEBUG"]) # SCons speed optimization controlled by the `fast_unsafe` option, which provide # more than 10 s speed up for incremental rebuilds. # Unsafe as they reduce the certainty of rebuilding all changed files, so it's # enabled by default for `debug` builds, and can be overridden from command line. # Ref: https://github.com/SCons/scons/wiki/GoFastButton -if methods.get_cmdline_bool("fast_unsafe", env_base.dev_build): +if methods.get_cmdline_bool("fast_unsafe", env.dev_build): # Renamed to `content-timestamp` in SCons >= 4.2, keeping MD5 for compat. - env_base.Decider("MD5-timestamp") - env_base.SetOption("implicit_cache", 1) - env_base.SetOption("max_drift", 60) + env.Decider("MD5-timestamp") + env.SetOption("implicit_cache", 1) + env.SetOption("max_drift", 60) -if env_base["use_precise_math_checks"]: - env_base.Append(CPPDEFINES=["PRECISE_MATH_CHECKS"]) +if env["use_precise_math_checks"]: + env.Append(CPPDEFINES=["PRECISE_MATH_CHECKS"]) -if env_base["engine_update_check"]: - env_base.Append(CPPDEFINES=["ENGINE_UPDATE_CHECK_ENABLED"]) +if env.editor_build and env["engine_update_check"]: + env.Append(CPPDEFINES=["ENGINE_UPDATE_CHECK_ENABLED"]) -if not env_base.File("#main/splash_editor.png").exists(): +if not env.File("#main/splash_editor.png").exists(): # Force disabling editor splash if missing. - env_base["no_editor_splash"] = True -if env_base["no_editor_splash"]: - env_base.Append(CPPDEFINES=["NO_EDITOR_SPLASH"]) + env["no_editor_splash"] = True +if env["no_editor_splash"]: + env.Append(CPPDEFINES=["NO_EDITOR_SPLASH"]) -if not env_base["deprecated"]: - env_base.Append(CPPDEFINES=["DISABLE_DEPRECATED"]) +if not env["deprecated"]: + env.Append(CPPDEFINES=["DISABLE_DEPRECATED"]) -if env_base["precision"] == "double": - env_base.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) +if env["precision"] == "double": + env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) tmppath = "./platform/" + selected_platform sys.path.insert(0, tmppath) import detect -env = env_base.Clone() - # Default num_jobs to local cpu count if not user specified. # SCons has a peculiarity where user-specified options won't be overridden # by SetOption, so we can rely on this to know if we should use our default. @@ -570,7 +568,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_base.dev_build == True: + if env.dev_build == True: max_includes_per_scu = 1024 read_scu_limit = int(env["scu_limit"]) @@ -844,7 +842,7 @@ suffix += "." + env["target"] if env.dev_build: suffix += ".dev" -if env_base["precision"] == "double": +if env["precision"] == "double": suffix += ".double" suffix += "." + env["arch"] diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index c9493be4ef..18dbac991c 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -794,11 +794,11 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA); builtin_type = (Variant::Type)bt; - ERR_FAIL_COND_V(!p_allow_objects && builtin_type == Variant::OBJECT, ERR_UNAUTHORIZED); + if (!p_allow_objects && builtin_type == Variant::OBJECT) { + class_name = EncodedObjectAsID::get_class_static(); + } } break; case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: { - ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED); - String str; Error err = _decode_string(buf, len, r_len, str); if (err) { @@ -806,22 +806,28 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } builtin_type = Variant::OBJECT; - class_name = str; + if (p_allow_objects) { + class_name = str; + } else { + class_name = EncodedObjectAsID::get_class_static(); + } } break; case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: { - ERR_FAIL_COND_V(!p_allow_objects, ERR_UNAUTHORIZED); - String path; Error err = _decode_string(buf, len, r_len, path); if (err) { return err; } - ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'."); - script = ResourceLoader::load(path, "Script"); - ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'."); builtin_type = Variant::OBJECT; - class_name = script->get_instance_base_type(); + if (p_allow_objects) { + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'."); + script = ResourceLoader::load(path, "Script"); + ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'."); + class_name = script->get_instance_base_type(); + } else { + class_name = EncodedObjectAsID::get_class_static(); + } } break; default: ERR_FAIL_V(ERR_INVALID_DATA); // Future proofing. @@ -1243,13 +1249,10 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (array.is_typed()) { Ref<Script> script = array.get_typed_script(); if (script.is_valid()) { - ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE); header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT; } else if (array.get_typed_class_name() != StringName()) { - ERR_FAIL_COND_V(!p_full_objects, ERR_UNAVAILABLE); header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else { - ERR_FAIL_COND_V(!p_full_objects && array.get_typed_builtin() == Variant::OBJECT, ERR_UNAVAILABLE); header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN; } } diff --git a/core/object/object.cpp b/core/object/object.cpp index 06f6e8e9e6..f8d2feb5a8 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1100,6 +1100,20 @@ bool Object::_has_user_signal(const StringName &p_name) const { return signal_map[p_name].user.name.length() > 0; } +void Object::_remove_user_signal(const StringName &p_name) { + SignalData *s = signal_map.getptr(p_name); + ERR_FAIL_NULL_MSG(s, "Provided signal does not exist."); + ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal)."); + for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { + Object *target = slot_kv.key.get_object(); + if (likely(target)) { + target->connections.erase(slot_kv.value.cE); + } + } + + signal_map.erase(p_name); +} + Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (unlikely(p_argcount < 1)) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1248,6 +1262,10 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { } add_user_signal(mi); + + if (signal_map.has(p_name)) { + signal_map.getptr(p_name)->removable = true; + } } TypedArray<Dictionary> Object::_get_signal_list() const { @@ -1661,6 +1679,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); + ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal); { MethodInfo mi; diff --git a/core/object/object.h b/core/object/object.h index d9551ecd01..915c3a8c25 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -619,6 +619,7 @@ private: MethodInfo user; HashMap<Callable, Slot, HashableHasher<Callable>> slot_map; + bool removable = false; }; HashMap<StringName, SignalData> signal_map; @@ -646,6 +647,7 @@ private: void _add_user_signal(const String &p_name, const Array &p_args = Array()); bool _has_user_signal(const StringName &p_name) const; + void _remove_user_signal(const StringName &p_name); Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); TypedArray<Dictionary> _get_signal_list() const; TypedArray<Dictionary> _get_signal_connection_list(const StringName &p_signal) const; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index c10f491a11..e0b8730a67 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -491,12 +491,10 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; - -#ifdef DEBUG_ENABLED - if (task->pool_thread_index == get_thread_index()) { - WARN_PRINT("A worker thread is attempting to notify itself. That makes no sense."); + if (task->completed) { + task_mutex.unlock(); + return; } -#endif ThreadData &td = threads[task->pool_thread_index]; td.yield_is_over = true; diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml index 7fbdd4272d..392364425b 100644 --- a/doc/classes/AcceptDialog.xml +++ b/doc/classes/AcceptDialog.xml @@ -100,6 +100,12 @@ </signal> </signals> <theme_items> + <theme_item name="buttons_min_height" data_type="constant" type="int" default="0"> + The minimum height of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> + <theme_item name="buttons_min_width" data_type="constant" type="int" default="0"> + The minimum width of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> <theme_item name="buttons_separation" data_type="constant" type="int" default="10"> The size of the vertical space between the dialog's content and the button row. </theme_item> diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 6b9c0bcc76..4052e48400 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -190,7 +190,7 @@ <method name="get_line" qualifiers="const"> <return type="String" /> <description> - Returns the next line of the file as a [String]. + Returns the next line of the file as a [String]. The returned string doesn't include newline ([code]\n[/code]) or carriage return ([code]\r[/code]) characters, but does include any other leading or trailing whitespace. Text is interpreted as being UTF-8 encoded. </description> </method> diff --git a/doc/classes/FileSystemDock.xml b/doc/classes/FileSystemDock.xml index 9028dd4b9f..b3dc51ffaa 100644 --- a/doc/classes/FileSystemDock.xml +++ b/doc/classes/FileSystemDock.xml @@ -51,6 +51,11 @@ Emitted when a file is moved from [param old_file] path to [param new_file] path. </description> </signal> + <signal name="folder_color_changed"> + <description> + Emitted when folders change color. + </description> + </signal> <signal name="folder_moved"> <param index="0" name="old_folder" type="String" /> <param index="1" name="new_folder" type="String" /> diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml index c490620fc0..31ce1dba4a 100644 --- a/doc/classes/NavigationRegion2D.xml +++ b/doc/classes/NavigationRegion2D.xml @@ -23,13 +23,6 @@ Bakes the [NavigationPolygon]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread. </description> </method> - <method name="get_avoidance_layer_value" qualifiers="const"> - <return type="bool" /> - <param index="0" name="layer_number" type="int" /> - <description> - Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. - </description> - </method> <method name="get_navigation_layer_value" qualifiers="const"> <return type="bool" /> <param index="0" name="layer_number" type="int" /> @@ -61,14 +54,6 @@ Returns [code]true[/code] when the [NavigationPolygon] is being baked on a background thread. </description> </method> - <method name="set_avoidance_layer_value"> - <return type="void" /> - <param index="0" name="layer_number" type="int" /> - <param index="1" name="value" type="bool" /> - <description> - Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. - </description> - </method> <method name="set_navigation_layer_value"> <return type="void" /> <param index="0" name="layer_number" type="int" /> @@ -86,12 +71,6 @@ </method> </methods> <members> - <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> - A bitfield determining all avoidance layers for the avoidance constrain. - </member> - <member name="constrain_avoidance" type="bool" setter="set_constrain_avoidance" getter="get_constrain_avoidance" default="false" experimental="When enabled, agents are known to get stuck on the navigation polygon corners and edges, especially at a high frame rate. Not recommended for use in production at this stage."> - If [code]true[/code] constraints avoidance agent's with an avoidance mask bit that matches with a bit of the [member avoidance_layers] to the navigation polygon. Due to each navigation polygon outline creating an obstacle and each polygon edge creating an avoidance line constrain keep the navigation polygon shape as simple as possible for performance. - </member> <member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true"> Determines if the [NavigationRegion2D] is enabled or disabled. </member> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index c508591093..961cb2e684 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -357,7 +357,7 @@ <param index="0" name="signal" type="String" /> <param index="1" name="arguments" type="Array" default="[]" /> <description> - Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal]. + Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal] and [method remove_user_signal]. [codeblocks] [gdscript] add_user_signal("hurt", [ @@ -797,7 +797,7 @@ <return type="bool" /> <param index="0" name="signal" type="StringName" /> <description> - Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. + Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. See also [method remove_user_signal]. </description> </method> <method name="is_blocking_signals" qualifiers="const"> @@ -905,6 +905,13 @@ [b]Note:[/b] Metadata that has a name starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the Inspector and should not be edited, although it can still be found by this method. </description> </method> + <method name="remove_user_signal" experimental=""> + <return type="void" /> + <param index="0" name="signal" type="StringName" /> + <description> + Removes the given user signal [param signal] from the object. See also [method add_user_signal] and [method has_user_signal]. + </description> + </method> <method name="set"> <return type="void" /> <param index="0" name="property" type="StringName" /> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 015d16291b..1daa1b04e4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -488,6 +488,9 @@ <member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings. </member> + <member name="debug/gdscript/warnings/enum_variable_without_default" type="int" setter="" getter="" default="1"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a variable has an enum type but no explicit default value, but only if the enum does not contain [code]0[/code] as a valid value. + </member> <member name="debug/gdscript/warnings/exclude_addons" type="bool" setter="" getter="" default="true"> If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings. </member> diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml index 2c88ecd675..43ee4dda60 100644 --- a/doc/classes/ScriptEditor.xml +++ b/doc/classes/ScriptEditor.xml @@ -34,6 +34,35 @@ Returns an array with all [Script] objects which are currently open in editor. </description> </method> + <method name="goto_help"> + <return type="void" /> + <param index="0" name="topic" type="String" /> + <description> + Opens help for the given topic. The [param topic] is an encoded string that controls which class, method, constant, signal, annotation, property, or theme item should be focused. + The supported [param topic] formats include [code]class_name:class[/code], [code]class_method:class:method[/code], [code]class_constant:class:constant[/code], [code]class_signal:class:signal[/code], [code]class_annotation:class:@annotation[/code], [code]class_property:class:property[/code], and [code]class_theme_item:class:item[/code], where [code]class[/code] is the class name, [code]method[/code] is the method name, [code]constant[/code] is the constant name, [code]signal[/code] is the signal name, [code]annotation[/code] is the annotation name, [code]property[/code] is the property name, and [code]item[/code] is the theme item. + [b]Examples:[/b] + [codeblock] + # Shows help for the Node class. + class_name:Node + # Shows help for the global min function. + # Global objects are accessible in the `@GlobalScope` namespace, shown here. + class_method:@GlobalScope:min + # Shows help for get_viewport in the Node class. + class_method:Node:get_viewport + # Shows help for the Input constant MOUSE_BUTTON_MIDDLE. + class_constant:Input:MOUSE_BUTTON_MIDDLE + # Shows help for the BaseButton signal pressed. + class_signal:BaseButton:pressed + # Shows help for the CanvasItem property visible. + class_property:CanvasItem:visible + # Shows help for the GDScript annotation export. + # Annotations should be prefixed with the `@` symbol in the descriptor, as shown here. + class_annotation:@GDScript:@export + # Shows help for the GraphNode theme item named panel_selected. + class_theme_item:GraphNode:panel_selected + [/codeblock] + </description> + </method> <method name="goto_line"> <return type="void" /> <param index="0" name="line_number" type="int" /> diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml index 6de6d9d186..4858a6ce22 100644 --- a/doc/classes/SkeletonIK3D.xml +++ b/doc/classes/SkeletonIK3D.xml @@ -55,6 +55,9 @@ </method> </methods> <members> + <member name="interpolation" type="float" setter="set_interpolation" getter="get_interpolation" deprecated="Use [member SkeletonModifier3D.influence] instead."> + Interpolation value for how much the IK results are applied to the current skeleton bone chain. A value of [code]1.0[/code] will overwrite all skeleton bone transforms completely while a value of [code]0.0[/code] will visually disable the SkeletonIK. + </member> <member name="magnet" type="Vector3" setter="set_magnet_position" getter="get_magnet_position" default="Vector3(0, 0, 0)"> Secondary target position (first is [member target] property or [member target_node]) for the IK chain. Use magnet position (pole target) to control the bending of the IK chain. Only works if the bone chain has more than 2 bones. The middle chain bone position will be linearly interpolated with the magnet position. </member> diff --git a/doc/classes/VisualShaderNodeComment.xml b/doc/classes/VisualShaderNodeComment.xml new file mode 100644 index 0000000000..28496a715a --- /dev/null +++ b/doc/classes/VisualShaderNodeComment.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeComment" inherits="VisualShaderNodeFrame" deprecated="This class has no function anymore and only exists for compatibility." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Only exists for compatibility. Use [VisualShaderNodeFrame] as a replacement. + </brief_description> + <description> + This node was replaced by [VisualShaderNodeFrame] and only exists to preserve compatibility. In the [VisualShader] editor it behaves exactly like [VisualShaderNodeFrame]. + </description> + <tutorials> + </tutorials> + <members> + <member name="description" type="String" setter="set_description" getter="get_description" default=""""> + This property only exists to preserve data authored in earlier versions of Godot. It has currently no function. + </member> + </members> +</class> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index ecb563214c..bc1af86938 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -3112,39 +3112,47 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, if (pass == 0) { spec_constants |= SceneShaderGLES3::BASE_PASS; - if (inst->omni_light_gl_cache.size() == 0) { - spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; - } - if (inst->spot_light_gl_cache.size() == 0) { + if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; - } - - if (p_render_data->directional_light_count == p_render_data->directional_shadow_count) { spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; - } + spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP; + } else { + if (inst->omni_light_gl_cache.size() == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + } - if (inst->reflection_probe_rid_cache.size() == 0) { - // We don't have any probes. - spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE; - } else if (inst->reflection_probe_rid_cache.size() > 1) { - // We have a second probe. - spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE; - } + if (inst->spot_light_gl_cache.size() == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + } - if (inst->lightmap_instance.is_valid()) { - spec_constants |= SceneShaderGLES3::USE_LIGHTMAP; + if (p_render_data->directional_light_count == p_render_data->directional_shadow_count) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; + } - GLES3::LightmapInstance *li = GLES3::LightStorage::get_singleton()->get_lightmap_instance(inst->lightmap_instance); - GLES3::Lightmap *lm = GLES3::LightStorage::get_singleton()->get_lightmap(li->lightmap); + if (inst->reflection_probe_rid_cache.size() == 0) { + // We don't have any probes. + spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE; + } else if (inst->reflection_probe_rid_cache.size() > 1) { + // We have a second probe. + spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE; + } - if (lm->uses_spherical_harmonics) { - spec_constants |= SceneShaderGLES3::USE_SH_LIGHTMAP; + if (inst->lightmap_instance.is_valid()) { + spec_constants |= SceneShaderGLES3::USE_LIGHTMAP; + + GLES3::LightmapInstance *li = GLES3::LightStorage::get_singleton()->get_lightmap_instance(inst->lightmap_instance); + GLES3::Lightmap *lm = GLES3::LightStorage::get_singleton()->get_lightmap(li->lightmap); + + if (lm->uses_spherical_harmonics) { + spec_constants |= SceneShaderGLES3::USE_SH_LIGHTMAP; + } + } else if (inst->lightmap_sh) { + spec_constants |= SceneShaderGLES3::USE_LIGHTMAP_CAPTURE; + } else { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP; } - } else if (inst->lightmap_sh) { - spec_constants |= SceneShaderGLES3::USE_LIGHTMAP_CAPTURE; - } else { - spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP; } } else { // Only base pass uses the radiance map. diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 4c66068953..f7914d3aaa 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -516,7 +516,14 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) { favorite->set_disabled(false); favorite->set_pressed(favorite_list.has(p_type)); - get_ok_button()->set_disabled(false); + + if (to_select->get_meta("__instantiable", true)) { + get_ok_button()->set_disabled(false); + get_ok_button()->set_tooltip_text(String()); + } else { + get_ok_button()->set_disabled(true); + get_ok_button()->set_tooltip_text(TTR("The selected class can't be instantiated.")); + } } void CreateDialog::select_base() { diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 8ae1752ca7..14190a43a5 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1664,7 +1664,10 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path) { } void EditorFileSystem::_update_scene_groups() { - EditorProgress ep("update_scene_groups", TTR("Update Scene Groups"), update_scene_paths.size()); + EditorProgress *ep = nullptr; + if (update_scene_paths.size() > 1) { + ep = memnew(EditorProgress("update_scene_groups", TTR("Update Scene Groups"), update_scene_paths.size())); + } int step_count = 0; update_scene_mutex.lock(); @@ -1684,9 +1687,12 @@ void EditorFileSystem::_update_scene_groups() { ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); } - ep.step(TTR("Updating Scene Groups..."), step_count++); + if (ep) { + ep->step(TTR("Updating Scene Groups..."), step_count++); + } } + memdelete_notnull(ep); update_scene_paths.clear(); update_scene_mutex.unlock(); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 7919d61f26..aed1462eb6 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3220,12 +3220,13 @@ void EditorInspector::update_tree() { } // Search for the doc path in the cache. - HashMap<StringName, HashMap<StringName, String>>::Iterator E = doc_path_cache.find(classname); + HashMap<StringName, HashMap<StringName, DocCacheInfo>>::Iterator E = doc_cache.find(classname); if (E) { - HashMap<StringName, String>::Iterator F = E->value.find(propname); + HashMap<StringName, DocCacheInfo>::Iterator F = E->value.find(propname); if (F) { found = true; - doc_path = F->value; + doc_path = F->value.doc_path; + theme_item_name = F->value.theme_item_name; } } @@ -3246,23 +3247,22 @@ void EditorInspector::update_tree() { theme_item_name = F->value.theme_properties[i].name; } } - - if (is_native_class) { - doc_path_cache[classname][propname] = doc_path; - } } else { for (int i = 0; i < F->value.properties.size(); i++) { String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name; if (F->value.properties[i].name == propname.operator String()) { doc_path = doc_path_current; } - - if (is_native_class) { - doc_path_cache[classname][propname] = doc_path; - } } } + if (is_native_class) { + DocCacheInfo cache_info; + cache_info.doc_path = doc_path; + cache_info.theme_item_name = theme_item_name; + doc_cache[classname][propname] = cache_info; + } + if (!doc_path.is_empty() || F->value.inherits.is_empty()) { break; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 8c55950a2b..eff4f9caa5 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -505,7 +505,12 @@ class EditorInspector : public ScrollContainer { int property_focusable; int update_scroll_request; - HashMap<StringName, HashMap<StringName, String>> doc_path_cache; + struct DocCacheInfo { + String doc_path; + String theme_item_name; + }; + + HashMap<StringName, HashMap<StringName, DocCacheInfo>> doc_cache; HashSet<StringName> restart_request_props; HashMap<String, String> custom_property_descriptions; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e5ef888370..5b24fb1559 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2364,7 +2364,13 @@ static bool overrides_external_editor(Object *p_object) { void EditorNode::_add_to_history(const Object *p_object, const String &p_property, bool p_inspector_only) { ObjectID id = p_object->get_instance_id(); - if (id != editor_history.get_current()) { + ObjectID history_id = editor_history.get_current(); + if (id != history_id) { + const MultiNodeEdit *multi_node_edit = Object::cast_to<const MultiNodeEdit>(p_object); + const MultiNodeEdit *history_multi_node_edit = Object::cast_to<const MultiNodeEdit>(ObjectDB::get_instance(history_id)); + if (multi_node_edit && history_multi_node_edit && multi_node_edit->is_same_selection(history_multi_node_edit)) { + return; + } if (p_inspector_only) { editor_history.add_object(id, String(), true); } else if (p_property.is_empty()) { @@ -2458,6 +2464,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update if (current_node->is_inside_tree()) { NodeDock::get_singleton()->set_node(current_node); SceneTreeDock::get_singleton()->set_selected(current_node); + SceneTreeDock::get_singleton()->set_selection({ current_node }); InspectorDock::get_singleton()->update(current_node); if (!inspector_only && !skip_main_plugin) { skip_main_plugin = stay_in_script_editor_on_node_selected && !ScriptEditor::get_singleton()->is_editor_floating() && ScriptEditor::get_singleton()->is_visible_in_tree(); @@ -2479,13 +2486,13 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update } else { Node *selected_node = nullptr; + Vector<Node *> multi_nodes; if (current_obj->is_class("MultiNodeEdit")) { Node *scene = get_edited_scene(); if (scene) { MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(current_obj); int node_count = multi_node_edit->get_node_count(); if (node_count > 0) { - List<Node *> multi_nodes; for (int node_index = 0; node_index < node_count; ++node_index) { Node *node = scene->get_node(multi_node_edit->get_node(node_index)); if (node) { @@ -2495,7 +2502,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update if (!multi_nodes.is_empty()) { // Pick the top-most node. multi_nodes.sort_custom<Node::Comparator>(); - selected_node = multi_nodes.front()->get(); + selected_node = multi_nodes[0]; } } } @@ -2504,6 +2511,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update InspectorDock::get_inspector_singleton()->edit(current_obj); NodeDock::get_singleton()->set_node(nullptr); SceneTreeDock::get_singleton()->set_selected(selected_node); + SceneTreeDock::get_singleton()->set_selection(multi_nodes); InspectorDock::get_singleton()->update(nullptr); } @@ -3841,6 +3849,8 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) { if (tabs_to_close.is_empty()) { callable_mp(this, &EditorNode::_set_main_scene_state).call_deferred(state, get_edited_scene()); // Do after everything else is done setting up. } + + _update_undo_redo_allowed(); } void EditorNode::setup_color_picker(ColorPicker *p_picker) { @@ -3926,14 +3936,18 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b } int prev = editor_data.get_edited_scene(); - int idx = editor_data.add_edited_scene(-1); + int idx = prev; - if (!editor_data.get_edited_scene_root() && editor_data.get_edited_scene_count() == 2) { - _remove_edited_scene(); - } else if (p_silent_change_tab) { - _set_current_scene_nocheck(idx); + if (prev == -1 || editor_data.get_edited_scene_root() || !editor_data.get_scene_path(prev).is_empty()) { + idx = editor_data.add_edited_scene(-1); + + if (p_silent_change_tab) { + _set_current_scene_nocheck(idx); + } else { + _set_current_scene(idx); + } } else { - _set_current_scene(idx); + EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); } dependency_errors.clear(); @@ -3950,7 +3964,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b dependency_error->show(DependencyErrorDialog::MODE_SCENE, lpath, errors); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -3961,7 +3975,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _dialog_display_load_error(lpath, err); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -3997,7 +4011,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b sdata.unref(); _dialog_display_load_error(lpath, ERR_FILE_CORRUPT); opening_prev = false; - if (prev != -1) { + if (prev != -1 && prev != idx) { _set_current_scene(prev); editor_data.remove_scene(idx); } @@ -4023,10 +4037,6 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _load_editor_plugin_states_from_config(editor_state_cf); } - _update_title(); - scene_tabs->update_scene_tabs(); - _add_to_recent_scenes(lpath); - if (editor_folding.has_folding_data(lpath)) { editor_folding.load_scene_folding(new_scene, lpath); } else if (EDITOR_GET("interface/inspector/auto_unfold_foreign_scenes")) { @@ -4066,6 +4076,14 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b save_editor_layout_delayed(); } + if (p_set_inherited) { + EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(editor_data.get_current_edited_scene_history_id()); + } + + _update_title(); + scene_tabs->update_scene_tabs(); + _add_to_recent_scenes(lpath); + return OK; } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2ed547a970..34fd0363b1 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -59,8 +59,6 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/progress_bar.h" -#include "scene/gui/texture_rect.h" -#include "scene/main/window.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" @@ -234,15 +232,15 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory Color custom_color = has_custom_color ? folder_colors[assigned_folder_colors[lpath]] : Color(); if (has_custom_color) { - subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * 1.75); - subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? 0.1 : 0.15)); + subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * ITEM_COLOR_SCALE); + subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? ITEM_ALPHA_MIN : ITEM_ALPHA_MAX)); } else { TreeItem *parent = subdirectory_item->get_parent(); if (parent) { Color parent_bg_color = parent->get_custom_bg_color(0); if (parent_bg_color != Color()) { bool parent_has_custom_color = assigned_folder_colors.has(parent->get_metadata(0)); - subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(0.3) : parent_bg_color); + subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(ITEM_BG_DARK_SCALE) : parent_bg_color); subdirectory_item->set_icon_modulate(0, parent->get_icon_modulate(0)); } else { subdirectory_item->set_icon_modulate(0, get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); @@ -328,7 +326,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon_max_width(0, icon_size); Color parent_bg_color = subdirectory_item->get_custom_bg_color(0); if (has_custom_color) { - file_item->set_custom_bg_color(0, parent_bg_color.darkened(0.3)); + file_item->set_custom_bg_color(0, parent_bg_color.darkened(ITEM_BG_DARK_SCALE)); } else if (parent_bg_color != Color()) { file_item->set_custom_bg_color(0, parent_bg_color); } @@ -1068,7 +1066,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_metadata(-1, bd); files->set_item_selectable(-1, false); - files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE); } bool reversed = file_sort == FILE_SORT_NAME_REVERSE; @@ -1082,7 +1080,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->add_item(dname, folder_icon, true); files->set_item_metadata(-1, dpath); Color this_folder_color = has_custom_color ? folder_colors[assigned_folder_colors[dpath]] : inherited_folder_color; - files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * ITEM_COLOR_SCALE); if (previous_selection.has(dname)) { files->select(files->get_item_count() - 1, false); @@ -1778,8 +1776,19 @@ void FileSystemDock::_folder_removed(const String &p_folder) { current_path = current_path.get_base_dir(); } - if (assigned_folder_colors.has(p_folder)) { - assigned_folder_colors.erase(p_folder); + // Remove assigned folder color for all subfolders. + bool folder_colors_updated = false; + List<Variant> paths; + assigned_folder_colors.get_key_list(&paths); + for (const Variant &E : paths) { + const String &path = E; + // These folder paths are guaranteed to end with a "/". + if (path.begins_with(p_folder)) { + assigned_folder_colors.erase(path); + folder_colors_updated = true; + } + } + if (folder_colors_updated) { _update_folder_colors_setting(); } @@ -3086,6 +3095,8 @@ void FileSystemDock::_folder_color_index_pressed(int p_index, PopupMenu *p_menu) _update_tree(get_uncollapsed_paths()); _update_file_list(true); + + emit_signal(SNAME("folder_color_changed")); } void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vector<String> &p_paths, bool p_display_path_dependent_options) { @@ -3796,6 +3807,7 @@ void FileSystemDock::_bind_methods() { ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder"))); ADD_SIGNAL(MethodInfo("files_moved", PropertyInfo(Variant::STRING, "old_file"), PropertyInfo(Variant::STRING, "new_file"))); ADD_SIGNAL(MethodInfo("folder_moved", PropertyInfo(Variant::STRING, "old_folder"), PropertyInfo(Variant::STRING, "new_folder"))); + ADD_SIGNAL(MethodInfo("folder_color_changed")); ADD_SIGNAL(MethodInfo("display_mode_changed")); } diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 8830f31d2d..f514de15e5 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -381,6 +381,11 @@ protected: static void _bind_methods(); public: + static constexpr double ITEM_COLOR_SCALE = 1.75; + static constexpr double ITEM_ALPHA_MIN = 0.1; + static constexpr double ITEM_ALPHA_MAX = 0.15; + static constexpr double ITEM_BG_DARK_SCALE = 0.3; + const HashMap<String, Color> &get_folder_colors() const; Dictionary get_assigned_folder_colors() const; diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp index 8821a0eeae..9d5464210b 100644 --- a/editor/gui/editor_dir_dialog.cpp +++ b/editor/gui/editor_dir_dialog.cpp @@ -32,27 +32,44 @@ #include "editor/directory_create_dialog.h" #include "editor/editor_file_system.h" +#include "editor/filesystem_dock.h" +#include "editor/themes/editor_theme_manager.h" #include "scene/gui/box_container.h" #include "scene/gui/tree.h" #include "servers/display_server.h" -void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { +void EditorDirDialog::_update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { updating = true; const String path = p_dir->get_path(); p_item->set_metadata(0, path); p_item->set_icon(0, tree->get_editor_theme_icon(SNAME("Folder"))); - p_item->set_icon_modulate(0, tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); if (!p_item->get_parent()) { p_item->set_text(0, "res://"); + p_item->set_icon_modulate(0, p_default_folder_color); } else { if (!opened_paths.has(path) && (p_select_path.is_empty() || !p_select_path.begins_with(path))) { p_item->set_collapsed(true); } p_item->set_text(0, p_dir->get_name()); + + if (p_assigned_folder_colors.has(path)) { + const Color &folder_color = p_folder_colors[p_assigned_folder_colors[path]]; + p_item->set_icon_modulate(0, p_is_dark_theme ? folder_color : folder_color * FileSystemDock::ITEM_COLOR_SCALE); + p_item->set_custom_bg_color(0, Color(folder_color, p_is_dark_theme ? FileSystemDock::ITEM_ALPHA_MIN : FileSystemDock::ITEM_ALPHA_MAX)); + } else { + TreeItem *parent_item = p_item->get_parent(); + Color parent_bg_color = parent_item->get_custom_bg_color(0); + if (parent_bg_color != Color()) { + p_item->set_custom_bg_color(0, p_assigned_folder_colors.has(parent_item->get_metadata(0)) ? parent_bg_color.darkened(FileSystemDock::ITEM_BG_DARK_SCALE) : parent_bg_color); + p_item->set_icon_modulate(0, parent_item->get_icon_modulate(0)); + } else { + p_item->set_icon_modulate(0, p_default_folder_color); + } + } } if (path == new_dir_path || !p_item->get_parent()) { @@ -62,7 +79,7 @@ void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p updating = false; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *ti = tree->create_item(p_item); - _update_dir(ti, p_dir->get_subdir(i)); + _update_dir(p_default_folder_color, p_assigned_folder_colors, p_folder_colors, p_is_dark_theme, ti, p_dir->get_subdir(i)); } } @@ -90,7 +107,7 @@ void EditorDirDialog::reload(const String &p_path) { tree->clear(); TreeItem *root = tree->create_item(); - _update_dir(root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); + _update_dir(tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")), FileSystemDock::get_singleton()->get_assigned_folder_colors(), FileSystemDock::get_singleton()->get_folder_colors(), EditorThemeManager::is_dark_theme(), root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); _item_collapsed(root); new_dir_path.clear(); must_reload = false; @@ -99,6 +116,8 @@ void EditorDirDialog::reload(const String &p_path) { void EditorDirDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); reload(); diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h index 40badec212..b10cc8dd9f 100644 --- a/editor/gui/editor_dir_dialog.h +++ b/editor/gui/editor_dir_dialog.h @@ -53,7 +53,7 @@ class EditorDirDialog : public ConfirmationDialog { void _item_collapsed(Object *p_item); void _item_activated(); - void _update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); + void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); void _make_dir(); void _make_dir_confirm(const String &p_path); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 25c8610ff4..a7cdb65145 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -968,8 +968,10 @@ void EditorFileDialog::update_file_list() { } } else if (!dir_access->current_is_hidden()) { String full_path = cdir == "res://" ? item : dir_access->get_current_dir() + "/" + item; - if (dir_access->current_is_dir() && (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path))) { - dirs.push_back(item); + if (dir_access->current_is_dir()) { + if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) { + dirs.push_back(item); + } } else { files.push_back(item); } diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index b6cb3d7371..5d1e68f008 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -135,6 +135,17 @@ void EditorSceneTabs::_scene_tab_input(const Ref<InputEvent> &p_input) { } } +void EditorSceneTabs::unhandled_key_input(const Ref<InputEvent> &p_event) { + if (!tab_preview_panel->is_visible()) { + return; + } + + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) { + tab_preview_panel->hide(); + } +} + void EditorSceneTabs::_reposition_active_tab(int p_to_index) { EditorNode::get_editor_data().move_edited_scene_to_index(p_to_index); update_scene_tabs(); @@ -369,6 +380,7 @@ EditorSceneTabs::EditorSceneTabs() { singleton = this; set_process_shortcut_input(true); + set_process_unhandled_key_input(true); tabbar_panel = memnew(PanelContainer); add_child(tabbar_panel); diff --git a/editor/gui/editor_scene_tabs.h b/editor/gui/editor_scene_tabs.h index 770114835a..ac9e6b8c43 100644 --- a/editor/gui/editor_scene_tabs.h +++ b/editor/gui/editor_scene_tabs.h @@ -79,6 +79,7 @@ class EditorSceneTabs : public MarginContainer { protected: void _notification(int p_what); + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); public: diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index e2aac6c75d..a667f94a62 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -587,7 +587,7 @@ void InputEventConfigurationDialog::_notification(int p_what) { void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event, const String &p_current_action_name) { if (p_event.is_valid()) { - _set_event(p_event->duplicate(), p_event); + _set_event(p_event->duplicate(), p_event->duplicate()); } else { // Clear Event _set_event(Ref<InputEvent>(), Ref<InputEvent>()); diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp index 4f0db70681..45786c0ab5 100644 --- a/editor/multi_node_edit.cpp +++ b/editor/multi_node_edit.cpp @@ -244,7 +244,7 @@ int MultiNodeEdit::get_node_count() const { } NodePath MultiNodeEdit::get_node(int p_index) const { - ERR_FAIL_INDEX_V(p_index, nodes.size(), NodePath()); + ERR_FAIL_INDEX_V(p_index, get_node_count(), NodePath()); return nodes[p_index]; } diff --git a/editor/multi_node_edit.h b/editor/multi_node_edit.h index deef88633e..000d41c4c1 100644 --- a/editor/multi_node_edit.h +++ b/editor/multi_node_edit.h @@ -36,7 +36,7 @@ class MultiNodeEdit : public RefCounted { GDCLASS(MultiNodeEdit, RefCounted); - List<NodePath> nodes; + LocalVector<NodePath> nodes; struct PLData { int uses = 0; PropertyInfo info; @@ -67,6 +67,19 @@ public: void set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field); + // If the nodes selected are the same independently of order then return true. + bool is_same_selection(const MultiNodeEdit *p_other) const { + if (get_node_count() != p_other->get_node_count()) { + return false; + } + for (int i = 0; i < get_node_count(); i++) { + if (nodes.find(p_other->get_node(i)) == -1) { + return false; + } + } + + return true; + } MultiNodeEdit(); }; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 8d24e90cce..8a9118a03e 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5860,13 +5860,34 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons } void CanvasItemEditorViewport::_perform_drop_data() { + ERR_FAIL_COND(selected_files.size() <= 0); + _remove_preview(); - // Without root dropping multiple files is not allowed - if (!target_node && selected_files.size() > 1) { - accept->set_text(TTR("Cannot instantiate multiple nodes without root.")); - accept->popup_centered(); - return; + 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; + } + + const String &path = selected_files[0]; + Ref<Resource> res = ResourceLoader::load(path); + if (res.is_null()) { + return; + } + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + // Without root node act the same as "Load Inherited Scene". + Error err = EditorNode::get_singleton()->load_scene(path, false, true); + if (err != OK) { + accept->set_text(vformat(TTR("Error instantiating scene from %s."), path.get_file())); + accept->popup_centered(); + } + return; + } } PackedStringArray error_files; @@ -5882,27 +5903,21 @@ void CanvasItemEditorViewport::_perform_drop_data() { if (res.is_null()) { continue; } - Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - if (scene != nullptr && scene.is_valid()) { - if (!target_node) { - // Without root node act the same as "Load Inherited Scene" - Error err = EditorNode::get_singleton()->load_scene(path, false, true); - if (err != OK) { - error_files.push_back(path.get_file()); - } - } else { - bool success = _create_instance(target_node, path, drop_pos); - if (!success) { - error_files.push_back(path.get_file()); - } - } - } else { - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); - if (texture != nullptr && texture.is_valid()) { - Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type)); - _create_nodes(target_node, child, path, drop_pos); - undo_redo->add_do_method(editor_selection, "add_node", child); + + Ref<PackedScene> scene = res; + if (scene.is_valid()) { + bool success = _create_instance(target_node, path, drop_pos); + if (!success) { + error_files.push_back(path.get_file()); } + continue; + } + + 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); + undo_redo->add_do_method(editor_selection, "add_node", child); } } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 6c63d9ff0d..c832570fee 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -3886,6 +3886,8 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("get_open_scripts"), &ScriptEditor::_get_open_scripts); ClassDB::bind_method(D_METHOD("open_script_create_dialog", "base_name", "base_path"), &ScriptEditor::open_script_create_dialog); + ClassDB::bind_method(D_METHOD("goto_help", "topic"), &ScriptEditor::goto_help); + ADD_SIGNAL(MethodInfo("editor_script_changed", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); } diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index e4e66b38e9..5e67cbc6ce 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1476,6 +1476,10 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { _fetch_sprite_node(); // Fetch node after set frames. } +bool SpriteFramesEditor::is_editing() const { + return frames.is_valid(); +} + Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (read_only) { return false; @@ -2325,7 +2329,7 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const { if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) { return true; } - return p_object->is_class("SpriteFrames"); + return !frames_editor->is_editing() && Object::cast_to<SpriteFrames>(p_object); } void SpriteFramesEditorPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 2d0e43be1e..e9fbaf7dde 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -253,7 +253,6 @@ class SpriteFramesEditor : public HSplitContainer { void _update_show_settings(); void _edit(); - void _regist_scene_undo(EditorUndoRedoManager *undo_redo); void _fetch_sprite_node(); void _remove_sprite_node(); @@ -270,6 +269,8 @@ protected: public: void edit(Ref<SpriteFrames> p_frames); + bool is_editing() const; + SpriteFramesEditor(); }; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index f9efc62f03..1da2f89c1c 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1838,7 +1838,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (area.position * (tile_size + separation)); - TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, area.size * tile_size)); + Vector2i size = area.size * tile_size + (area.size - Vector2i(1, 1)).max(Vector2i(0, 0)) * separation; + TilesEditorUtils::draw_selection_rect(tile_atlas_control, Rect2i(origin, size)); } else { Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) { diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 05b8b81fa7..0dd845270a 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -953,8 +953,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool bool port_left_used = false; String name_left; if (valid_left) { - name_left = vsnode->get_input_port_name(i); - port_left = vsnode->get_input_port_type(i); + name_left = vsnode->get_input_port_name(j); + port_left = vsnode->get_input_port_type(j); for (const VisualShader::Connection &E : connections) { if (E.to_node == p_id && E.to_port == j) { port_left_used = true; @@ -989,15 +989,15 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Variant default_value; if (valid_left && !port_left_used) { - default_value = vsnode->get_input_port_default_value(i); + default_value = vsnode->get_input_port_default_value(j); } Button *button = memnew(Button); hb->add_child(button); - register_default_input_button(p_id, i, button); - button->connect("pressed", callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, i)); + register_default_input_button(p_id, j, button); + button->connect("pressed", callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, j)); if (default_value.get_type() != Variant::NIL) { // only a label - set_input_port_default_value(p_type, p_id, i, default_value); + set_input_port_default_value(p_type, p_id, j, default_value); } else { button->hide(); } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0b489cbb36..e1084d4873 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -3020,6 +3020,13 @@ void SceneTreeDock::set_edited_scene(Node *p_scene) { edited_scene = p_scene; } +void SceneTreeDock::set_selection(const Vector<Node *> &p_nodes) { + editor_selection->clear(); + for (Node *node : p_nodes) { + editor_selection->add_node(node); + } +} + void SceneTreeDock::set_selected(Node *p_node, bool p_emit_selected) { scene_tree->set_selected(p_node, p_emit_selected); } diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 86e9ff8a47..21e1b00f93 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -322,6 +322,7 @@ public: void set_edited_scene(Node *p_scene); void instantiate(const String &p_file); void instantiate_scenes(const Vector<String> &p_files, Node *p_parent = nullptr); + void set_selection(const Vector<Node *> &p_nodes); void set_selected(Node *p_node, bool p_emit_selected = false); void fill_path_renames(Node *p_node, Node *p_new_parent, HashMap<Node *, NodePath> *p_renames); void perform_node_renames(Node *p_base, HashMap<Node *, NodePath> *p_renames, HashMap<Ref<Animation>, HashSet<int>> *r_rem_anims = nullptr); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 734fa415a3..f74be7d978 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -356,20 +356,25 @@ EditorThemeManager::ThemeConfiguration EditorThemeManager::_create_theme_config( if (config.spacing_preset != "Custom") { int preset_base_spacing = 0; int preset_extra_spacing = 0; + Size2 preset_dialogs_buttons_min_size; if (config.spacing_preset == "Compact") { preset_base_spacing = 0; preset_extra_spacing = 4; + preset_dialogs_buttons_min_size = Size2(90, 26); } else if (config.spacing_preset == "Spacious") { preset_base_spacing = 6; preset_extra_spacing = 2; + preset_dialogs_buttons_min_size = Size2(112, 36); } else { // Default preset_base_spacing = 4; preset_extra_spacing = 0; + preset_dialogs_buttons_min_size = Size2(105, 34); } config.base_spacing = preset_base_spacing; config.extra_spacing = preset_extra_spacing; + config.dialogs_buttons_min_size = preset_dialogs_buttons_min_size; EditorSettings::get_singleton()->set_initial_value("interface/theme/base_spacing", config.base_spacing); EditorSettings::get_singleton()->set_initial_value("interface/theme/additional_spacing", config.extra_spacing); @@ -1271,6 +1276,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // AcceptDialog. p_theme->set_stylebox("panel", "AcceptDialog", p_config.dialog_style); p_theme->set_constant("buttons_separation", "AcceptDialog", 8 * EDSCALE); + // Make buttons with short texts such as "OK" easier to click/tap. + p_theme->set_constant("buttons_min_width", "AcceptDialog", p_config.dialogs_buttons_min_size.x * EDSCALE); + p_theme->set_constant("buttons_min_height", "AcceptDialog", p_config.dialogs_buttons_min_size.y * EDSCALE); // FileDialog. p_theme->set_icon("folder", "FileDialog", p_theme->get_icon(SNAME("Folder"), EditorStringName(EditorIcons))); diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h index 3eb1dd5ffd..5e7bd00083 100644 --- a/editor/themes/editor_theme_manager.h +++ b/editor/themes/editor_theme_manager.h @@ -62,6 +62,7 @@ class EditorThemeManager { int base_spacing = 4; int extra_spacing = 0; + Size2 dialogs_buttons_min_size = Size2(105, 34); int border_width = 0; int corner_radius = 3; diff --git a/main/main.cpp b/main/main.cpp index a1031b5385..801e8934b0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -697,7 +697,9 @@ Error Main::test_setup() { /** INITIALIZE SERVERS **/ register_server_types(); +#ifndef _3D_DISABLED XRServer::set_xr_mode(XRServer::XRMODE_OFF); // Skip in tests. +#endif // _3D_DISABLED initialize_modules(MODULE_INITIALIZATION_LEVEL_SERVERS); GDExtensionManager::get_singleton()->initialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS); @@ -840,21 +842,26 @@ void Main::test_cleanup() { #endif int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { -#ifdef TESTS_ENABLED for (int x = 0; x < argc; x++) { if ((strncmp(argv[x], "--test", 6) == 0) && (strlen(argv[x]) == 6)) { tests_need_run = true; +#ifdef TESTS_ENABLED // TODO: need to come up with different test contexts. // Not every test requires high-level functionality like `ClassDB`. test_setup(); int status = test_main(argc, argv); test_cleanup(); return status; +#else + ERR_PRINT( + "`--test` was specified on the command line, but this Godot binary was compiled without support for unit tests. Aborting.\n" + "To be able to run unit tests, use the `tests=yes` SCons option when compiling Godot.\n"); + return EXIT_FAILURE; +#endif } } -#endif tests_need_run = false; - return 0; + return EXIT_SUCCESS; } /* Engine initialization diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index e8195d0aa0..43349ae6ef 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -256,20 +256,14 @@ Compatibility methods registered. GH-88014 -------- -Validate extension JSON: API was removed: classes/VisualShaderNodeComment - -Removed VisualShaderNodeComment, which is replaced by VisualShaderNodeFrame. +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/get_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/set_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/properties/title GH-87888 -------- -Validate extension JSON: API was removed: classes/OpenXRHand/methods/get_hand_skeleton -Validate extension JSON: API was removed: classes/OpenXRHand/methods/set_hand_skeleton -Validate extension JSON: API was removed: classes/OpenXRHand/properties/hand_skeleton Validate extension JSON: API was removed: classes/Skeleton3D/properties/animate_physical_bones -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/get_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/set_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/properties/interpolation These base class is changed to SkeletonModifier3D which is processed by Skeleton3D with the assumption that it is Skeleton3D's child. @@ -280,3 +274,16 @@ Validate extension JSON: API was removed: classes/BoneAttachment3D/methods/on_bo Validate extension JSON: API was removed: classes/Skeleton3D/signals/bone_pose_changed They have been replaced by a safer API due to performance concerns. Compatibility method registered. + +GH-90747 +-------- +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get_avoidance_layers +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/set_avoidance_layers +Validate extension JSON: API was removed: classes/NavigationRegion2D/properties/avoidance_layers +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get_avoidance_layer_value +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/set_avoidance_layer_value +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/set_constrain_avoidance +Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get_constrain_avoidance +Validate extension JSON: API was removed: classes/NavigationRegion2D/properties/constrain_avoidance + +Experimental NavigationRegion2D feature "constrain_avoidance" was discontinued with no replacement. diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index bde23289f4..44a929d285 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -1629,6 +1629,9 @@ void FBXDocument::_generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTF active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -2019,8 +2022,8 @@ Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool GLTFNodeIndex fbx_root = state->root_nodes.write[0]; Node *fbx_root_node = state->get_scene_node(fbx_root); Node *root = fbx_root_node; - if (fbx_root_node && fbx_root_node->get_parent()) { - root = fbx_root_node->get_parent(); + if (root && root->get_owner() && root->get_owner() != root) { + root = root->get_owner(); } ERR_FAIL_NULL_V(root, nullptr); _process_mesh_instances(state, root); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index aab2ac572b..ec75663e97 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1957,6 +1957,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); } + } else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { + // Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine. + bool has_zero_value = false; + for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) { + if (kv.value == 0) { + has_zero_value = true; + break; + } + } + if (!has_zero_value) { + parser->push_warning(p_assignable, GDScriptWarning::ENUM_VARIABLE_WITHOUT_DEFAULT, p_assignable->identifier->name); + } } #endif diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 708966a0a8..48a0abe617 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -126,6 +126,9 @@ String GDScriptWarning::get_message() const { case INT_AS_ENUM_WITHOUT_MATCH: CHECK_SYMBOLS(3); return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]); + case ENUM_VARIABLE_WITHOUT_DEFAULT: + CHECK_SYMBOLS(1); + return vformat(R"(The variable "%s" has an enum type and does not set an explicit default value. The default will be set to "0".)", symbols[0]); case EMPTY_FILE: return "Empty script file."; case DEPRECATED_KEYWORD: @@ -221,6 +224,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "NARROWING_CONVERSION", "INT_AS_ENUM_WITHOUT_CAST", "INT_AS_ENUM_WITHOUT_MATCH", + "ENUM_VARIABLE_WITHOUT_DEFAULT", "EMPTY_FILE", "DEPRECATED_KEYWORD", "RENAMED_IN_GODOT_4_HINT", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 93c232a0f8..3ad9488138 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -78,6 +78,7 @@ public: NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting. INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. + ENUM_VARIABLE_WITHOUT_DEFAULT, // A variable with an enum type does not have a default value. The default will be set to `0` instead of the first enum value. EMPTY_FILE, // A script file is empty. DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4. @@ -129,6 +130,7 @@ public: WARN, // NARROWING_CONVERSION WARN, // INT_AS_ENUM_WITHOUT_CAST WARN, // INT_AS_ENUM_WITHOUT_MATCH + WARN, // ENUM_VARIABLE_WITHOUT_DEFAULT WARN, // EMPTY_FILE WARN, // DEPRECATED_KEYWORD WARN, // RENAMED_IN_GODOT_4_HINT diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd new file mode 100644 index 0000000000..13e3edf93f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd @@ -0,0 +1,9 @@ +enum HasZero { A = 0, B = 1 } +enum HasNoZero { A = 1, B = 2 } +var has_zero: HasZero # No warning, because the default `0` is valid. +var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum. + + +func test(): + print(has_zero) + print(has_no_zero) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out new file mode 100644 index 0000000000..ae40e0bc8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> ENUM_VARIABLE_WITHOUT_DEFAULT +>> The variable "has_no_zero" has an enum type and does not set an explicit default value. The default will be set to "0". +0 +0 diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index d7485f49e6..42b29eee43 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -23,6 +23,7 @@ var test_var_hard_int: int var test_var_hard_variant_type: Variant.Type @export var test_var_hard_variant_type_exported: Variant.Type var test_var_hard_node_process_mode: Node.ProcessMode +@warning_ignore("enum_variable_without_default") var test_var_hard_my_enum: MyEnum var test_var_hard_array: Array var test_var_hard_array_int: Array[int] diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f8c35ab6d1..8f0f0d219e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5685,6 +5685,9 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -7104,6 +7107,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { if (p_state->extensions_used.has("GODOT_single_root")) { _generate_scene_node(p_state, 0, nullptr, nullptr); single_root = p_state->scene_nodes[0]; + if (single_root && single_root->get_owner() && single_root->get_owner() != single_root) { + single_root = single_root->get_owner(); + } } else { single_root = memnew(Node3D); for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 168e0bf077..b9c69075e1 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayer" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayer" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> The parent class of all OpenXR composition layer nodes. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml index 2de1977671..dd8a11e2b9 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a cylinder. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml index f6eba7e228..716ea72854 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a sphere. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml index fff592bc4f..6632f90ed2 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as a quad. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml index 9cc548dd6f..23d932ddd7 100644 --- a/modules/openxr/doc_classes/OpenXRHand.xml +++ b/modules/openxr/doc_classes/OpenXRHand.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRHand" inherits="SkeletonModifier3D" deprecated="Use [XRHandModifier3D] instead." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRHand" inherits="Node3D" deprecated="Use [XRHandModifier3D] instead." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Node supporting hand and finger tracking in OpenXR. </brief_description> @@ -18,11 +18,14 @@ <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0"> Specifies whether this node tracks the left or right hand of the player. </member> + <member name="hand_skeleton" type="NodePath" setter="set_hand_skeleton" getter="get_hand_skeleton" default="NodePath("")"> + Set a [Skeleton3D] node for which the pose positions will be updated. + </member> <member name="motion_range" type="int" setter="set_motion_range" getter="get_motion_range" enum="OpenXRHand.MotionRange" default="0"> Set the motion range (if supported) limiting the hand motion. </member> <member name="skeleton_rig" type="int" setter="set_skeleton_rig" getter="get_skeleton_rig" enum="OpenXRHand.SkeletonRig" default="0"> - Set the type of skeleton rig the parent [Skeleton3D] is compliant with. + Set the type of skeleton rig the [member hand_skeleton] is compliant with. </member> </members> <constants> diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp index f20d1f8e19..2a4104f6ee 100644 --- a/modules/openxr/scene/openxr_hand.cpp +++ b/modules/openxr/scene/openxr_hand.cpp @@ -40,6 +40,9 @@ void OpenXRHand::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hand", "hand"), &OpenXRHand::set_hand); ClassDB::bind_method(D_METHOD("get_hand"), &OpenXRHand::get_hand); + ClassDB::bind_method(D_METHOD("set_hand_skeleton", "hand_skeleton"), &OpenXRHand::set_hand_skeleton); + ClassDB::bind_method(D_METHOD("get_hand_skeleton"), &OpenXRHand::get_hand_skeleton); + ClassDB::bind_method(D_METHOD("set_motion_range", "motion_range"), &OpenXRHand::set_motion_range); ClassDB::bind_method(D_METHOD("get_motion_range"), &OpenXRHand::get_motion_range); @@ -51,6 +54,7 @@ void OpenXRHand::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton"); ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton_rig", PROPERTY_HINT_ENUM, "OpenXR,Humanoid"), "set_skeleton_rig", "get_skeleton_rig"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); @@ -86,6 +90,12 @@ OpenXRHand::Hands OpenXRHand::get_hand() const { return hand; } +void OpenXRHand::set_hand_skeleton(const NodePath &p_hand_skeleton) { + hand_skeleton = p_hand_skeleton; + + // TODO if inside tree call _get_bones() +} + void OpenXRHand::set_motion_range(MotionRange p_motion_range) { ERR_FAIL_INDEX(p_motion_range, MOTION_RANGE_MAX); motion_range = p_motion_range; @@ -97,6 +107,10 @@ OpenXRHand::MotionRange OpenXRHand::get_motion_range() const { return motion_range; } +NodePath OpenXRHand::get_hand_skeleton() const { + return hand_skeleton; +} + void OpenXRHand::_set_motion_range() { if (!hand_tracking_ext) { return; @@ -138,6 +152,20 @@ OpenXRHand::BoneUpdate OpenXRHand::get_bone_update() const { return bone_update; } +Skeleton3D *OpenXRHand::get_skeleton() { + if (!has_node(hand_skeleton)) { + return nullptr; + } + + Node *node = get_node(hand_skeleton); + if (!node) { + return nullptr; + } + + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); + return skeleton; +} + void OpenXRHand::_get_joint_data() { // Table of bone names for different rig types. static const String bone_names[SKELETON_RIG_MAX][XR_HAND_JOINT_COUNT_EXT] = { @@ -262,7 +290,7 @@ void OpenXRHand::_get_joint_data() { } } -void OpenXRHand::_process_modification() { +void OpenXRHand::_update_skeleton() { if (openxr_api == nullptr || !openxr_api->is_initialized()) { return; } else if (hand_tracking_ext == nullptr || !hand_tracking_ext->get_active()) { @@ -367,14 +395,21 @@ void OpenXRHand::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _get_joint_data(); + + set_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { + set_process_internal(false); + // reset for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { joints[i].bone = -1; joints[i].parent_joint = -1; } } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _update_skeleton(); + } break; default: { } break; } diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h index fc0a994f48..4c77e7277c 100644 --- a/modules/openxr/scene/openxr_hand.h +++ b/modules/openxr/scene/openxr_hand.h @@ -31,15 +31,16 @@ #ifndef OPENXR_HAND_H #define OPENXR_HAND_H -#include "scene/3d/skeleton_modifier_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" #include <openxr/openxr.h> class OpenXRAPI; class OpenXRHandTrackingExtension; -class OpenXRHand : public SkeletonModifier3D { - GDCLASS(OpenXRHand, SkeletonModifier3D); +class OpenXRHand : public Node3D { + GDCLASS(OpenXRHand, Node3D); public: enum Hands { // Deprecated, need to change this to OpenXRInterface::Hands. @@ -85,13 +86,13 @@ private: void _set_motion_range(); + Skeleton3D *get_skeleton(); void _get_joint_data(); + void _update_skeleton(); protected: static void _bind_methods(); - virtual void _process_modification() override; - public: OpenXRHand(); @@ -101,6 +102,9 @@ public: void set_motion_range(MotionRange p_motion_range); MotionRange get_motion_range() const; + void set_hand_skeleton(const NodePath &p_hand_skeleton); + NodePath get_hand_skeleton() const; + void set_skeleton_rig(SkeletonRig p_skeleton_rig); SkeletonRig get_skeleton_rig() const; diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm index 6b55b70629..6ffd939545 100644 --- a/platform/macos/godot_open_save_delegate.mm +++ b/platform/macos/godot_open_save_delegate.mm @@ -177,14 +177,14 @@ if ([new_allowed_types count] > 0) { NSMutableArray *type_filters = [new_allowed_types objectAtIndex:0]; if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) { - [p_panel setAllowedFileTypes:@[]]; + [p_panel setAllowedFileTypes:nil]; [p_panel setAllowsOtherFileTypes:true]; } else { [p_panel setAllowsOtherFileTypes:false]; [p_panel setAllowedFileTypes:type_filters]; } } else { - [p_panel setAllowedFileTypes:@[]]; + [p_panel setAllowedFileTypes:nil]; [p_panel setAllowsOtherFileTypes:true]; } } @@ -248,7 +248,7 @@ if (allowed_types && index < [allowed_types count]) { NSMutableArray *type_filters = [allowed_types objectAtIndex:index]; if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) { - [dialog setAllowedFileTypes:@[]]; + [dialog setAllowedFileTypes:nil]; [dialog setAllowsOtherFileTypes:true]; } else { [dialog setAllowsOtherFileTypes:false]; @@ -256,7 +256,7 @@ } cur_index = index; } else { - [dialog setAllowedFileTypes:@[]]; + [dialog setAllowedFileTypes:nil]; [dialog setAllowsOtherFileTypes:true]; cur_index = -1; } diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 5510b59903..ab44e57d05 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -31,7 +31,6 @@ #include "navigation_region_2d.h" #include "core/math/geometry_2d.h" -#include "scene/2d/navigation_obstacle_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" @@ -212,11 +211,6 @@ void NavigationRegion2D::set_navigation_map(RID p_navigation_map) { map_override = p_navigation_map; NavigationServer2D::get_singleton()->region_set_map(region, map_override); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override); - } - } } RID NavigationRegion2D::get_navigation_map() const { @@ -265,7 +259,6 @@ void NavigationRegion2D::_navigation_polygon_changed() { if (navigation_polygon.is_valid()) { NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon); } - _update_avoidance_constrain(); } #ifdef DEBUG_ENABLED @@ -317,13 +310,6 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value); ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value); - ClassDB::bind_method(D_METHOD("set_constrain_avoidance", "enabled"), &NavigationRegion2D::set_constrain_avoidance); - ClassDB::bind_method(D_METHOD("get_constrain_avoidance"), &NavigationRegion2D::get_constrain_avoidance); - ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationRegion2D::set_avoidance_layers); - ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationRegion2D::get_avoidance_layers); - ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationRegion2D::set_avoidance_layer_value); - ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationRegion2D::get_avoidance_layer_value); - ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid); ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost); @@ -343,8 +329,6 @@ void NavigationRegion2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); ADD_SIGNAL(MethodInfo("navigation_polygon_changed")); ADD_SIGNAL(MethodInfo("bake_finished")); @@ -391,131 +375,12 @@ NavigationRegion2D::~NavigationRegion2D() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); NavigationServer2D::get_singleton()->free(region); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); - } - } - constrain_avoidance_obstacles.clear(); - #ifdef DEBUG_ENABLED NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed)); #endif // DEBUG_ENABLED } -void NavigationRegion2D::_update_avoidance_constrain() { - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); - constrain_avoidance_obstacles[i] = RID(); - } - } - constrain_avoidance_obstacles.clear(); - - if (!constrain_avoidance) { - return; - } - - if (get_navigation_polygon() == nullptr) { - return; - } - - Ref<NavigationPolygon> _navpoly = get_navigation_polygon(); - int _outline_count = _navpoly->get_outline_count(); - if (_outline_count == 0) { - return; - } - - for (int outline_index(0); outline_index < _outline_count; outline_index++) { - const Vector<Vector2> &_outline = _navpoly->get_outline(outline_index); - - const int outline_size = _outline.size(); - if (outline_size < 3) { - ERR_FAIL_COND_MSG(_outline.size() < 3, "NavigationPolygon outline needs to have at least 3 vertex to create avoidance obstacles to constrain avoidance agent's"); - continue; - } - - RID obstacle_rid = NavigationServer2D::get_singleton()->obstacle_create(); - constrain_avoidance_obstacles.push_back(obstacle_rid); - - Vector<Vector2> new_obstacle_outline; - - if (outline_index == 0) { - for (int i(0); i < outline_size; i++) { - new_obstacle_outline.push_back(_outline[outline_size - i - 1]); - } - ERR_FAIL_COND_MSG(Geometry2D::is_polygon_clockwise(_outline), "Outer most outline needs to be clockwise to push avoidance agent inside"); - } else { - for (int i(0); i < outline_size; i++) { - new_obstacle_outline.push_back(_outline[i]); - } - } - new_obstacle_outline.resize(outline_size); - - NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle_rid, new_obstacle_outline); - NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle_rid, avoidance_layers); - if (is_inside_tree()) { - if (map_override.is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, map_override); - } else { - NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, get_world_2d()->get_navigation_map()); - } - NavigationServer2D::get_singleton()->obstacle_set_position(obstacle_rid, get_global_position()); - } - } - constrain_avoidance_obstacles.resize(_outline_count); -} - -void NavigationRegion2D::set_constrain_avoidance(bool p_enabled) { - constrain_avoidance = p_enabled; - _update_avoidance_constrain(); - notify_property_list_changed(); -} - -bool NavigationRegion2D::get_constrain_avoidance() const { - return constrain_avoidance; -} - -void NavigationRegion2D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "avoidance_layers") { - if (!constrain_avoidance) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } - } -} - -void NavigationRegion2D::set_avoidance_layers(uint32_t p_layers) { - avoidance_layers = p_layers; - if (constrain_avoidance_obstacles.size() > 0) { - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(constrain_avoidance_obstacles[i], avoidance_layers); - } - } -} - -uint32_t NavigationRegion2D::get_avoidance_layers() const { - return avoidance_layers; -} - -void NavigationRegion2D::set_avoidance_layer_value(int p_layer_number, bool p_value) { - ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); - uint32_t avoidance_layers_new = get_avoidance_layers(); - if (p_value) { - avoidance_layers_new |= 1 << (p_layer_number - 1); - } else { - avoidance_layers_new &= ~(1 << (p_layer_number - 1)); - } - set_avoidance_layers(avoidance_layers_new); -} - -bool NavigationRegion2D::get_avoidance_layer_value(int p_layer_number) const { - ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); - return get_avoidance_layers() & (1 << (p_layer_number - 1)); -} - void NavigationRegion2D::_region_enter_navigation_map() { if (!is_inside_tree()) { return; @@ -523,27 +388,12 @@ void NavigationRegion2D::_region_enter_navigation_map() { if (map_override.is_valid()) { NavigationServer2D::get_singleton()->region_set_map(region, map_override); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override); - } - } } else { NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], get_world_2d()->get_navigation_map()); - } - } } current_global_transform = get_global_transform(); NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); - } - } NavigationServer2D::get_singleton()->region_set_enabled(region, enabled); @@ -552,11 +402,6 @@ void NavigationRegion2D::_region_enter_navigation_map() { void NavigationRegion2D::_region_exit_navigation_map() { NavigationServer2D::get_singleton()->region_set_map(region, RID()); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], RID()); - } - } } void NavigationRegion2D::_region_update_transform() { @@ -568,11 +413,6 @@ void NavigationRegion2D::_region_update_transform() { if (current_global_transform != new_global_transform) { current_global_transform = new_global_transform; NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); - } - } } queue_redraw(); diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 1482617a64..5a86dd607d 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -46,10 +46,6 @@ class NavigationRegion2D : public Node2D { real_t travel_cost = 1.0; Ref<NavigationPolygon> navigation_polygon; - bool constrain_avoidance = false; - LocalVector<RID> constrain_avoidance_obstacles; - uint32_t avoidance_layers = 1; - Transform2D current_global_transform; void _navigation_polygon_changed(); @@ -65,7 +61,6 @@ private: protected: void _notification(int p_what); - void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); #ifndef DISABLE_DEPRECATED @@ -106,15 +101,6 @@ public: void set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon); Ref<NavigationPolygon> get_navigation_polygon() const; - void set_constrain_avoidance(bool p_enabled); - bool get_constrain_avoidance() const; - - void set_avoidance_layers(uint32_t p_layers); - uint32_t get_avoidance_layers() const; - - void set_avoidance_layer_value(int p_layer_number, bool p_value); - bool get_avoidance_layer_value(int p_layer_number) const; - PackedStringArray get_configuration_warnings() const override; void bake_navigation_polygon(bool p_on_thread); @@ -125,7 +111,6 @@ public: ~NavigationRegion2D(); private: - void _update_avoidance_constrain(); void _region_enter_navigation_map(); void _region_exit_navigation_map(); void _region_update_transform(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index bbf1d09bbc..165d4d5a67 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -161,6 +161,10 @@ Vector<int> TileMap::_get_tile_map_data_using_compatibility_format(int p_layer) return tile_data; } +void TileMap::_set_layer_tile_data(int p_layer, const PackedInt32Array &p_data) { + _set_tile_map_data_using_compatibility_format(p_layer, format, p_data); +} + void TileMap::_notification(int p_what) { switch (p_what) { case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -429,6 +433,7 @@ Color TileMap::get_layer_modulate(int p_layer) const { void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_enabled, p_y_sort_enabled); + update_configuration_warnings(); } bool TileMap::is_layer_y_sort_enabled(int p_layer) const { @@ -437,6 +442,7 @@ bool TileMap::is_layer_y_sort_enabled(int p_layer) const { void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_origin, p_y_sort_origin); + update_configuration_warnings(); } int TileMap::get_layer_y_sort_origin(int p_layer) const { @@ -518,9 +524,6 @@ void TileMap::set_y_sort_enabled(bool p_enable) { return; } Node2D::set_y_sort_enabled(p_enable); - for (TileMapLayer *layer : layers) { - layer->set_y_sort_enabled(p_enable); - } _emit_changed(); update_configuration_warnings(); } @@ -741,41 +744,23 @@ Rect2 TileMap::_edit_get_rect() const { #endif bool TileMap::_set(const StringName &p_name, const Variant &p_value) { + int index; + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); - if (p_name == "format") { + if (sname == "format") { if (p_value.get_type() == Variant::INT) { format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } #ifndef DISABLE_DEPRECATED - else if (p_name == "tile_data") { // Kept for compatibility reasons. - if (p_value.is_array()) { - if (layers.size() == 0) { - TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->set_as_tile_map_internal_node(0); - new_layer->set_name("Layer0"); - new_layer->set_tile_set(tile_set); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); - layers.push_back(new_layer); - } - _set_tile_map_data_using_compatibility_format(0, format, p_value); - _emit_changed(); - return true; - } - return false; - } else if (p_name == "cell_quadrant_size") { + else if (sname == "cell_quadrant_size") { set_rendering_quadrant_size(p_value); return true; } #endif // DISABLE_DEPRECATED - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0) { - return false; - } - + else if (property_helper.is_property_valid(sname, &index)) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); @@ -792,172 +777,38 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { update_configuration_warnings(); } - if (components[1] == "name") { - set_layer_name(index, p_value); - return true; - } else if (components[1] == "enabled") { - set_layer_enabled(index, p_value); - return true; - } else if (components[1] == "modulate") { - set_layer_modulate(index, p_value); - return true; - } else if (components[1] == "y_sort_enabled") { - set_layer_y_sort_enabled(index, p_value); - return true; - } else if (components[1] == "y_sort_origin") { - set_layer_y_sort_origin(index, p_value); - return true; - } else if (components[1] == "z_index") { - set_layer_z_index(index, p_value); - return true; - } else if (components[1] == "navigation_enabled") { - set_layer_navigation_enabled(index, p_value); - return true; - } else if (components[1] == "tile_data") { - _set_tile_map_data_using_compatibility_format(index, format, p_value); - _emit_changed(); + if (property_helper.property_set_value(sname, p_value)) { + if (components[1] == "tile_data") { + _emit_changed(); + } return true; - } else { - return false; } } return false; } bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { r_ret = TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX - 1; // When saving, always save highest format. return true; } #ifndef DISABLE_DEPRECATED - else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons. + else if (sname == "cell_quadrant_size") { // Kept for compatibility reasons. r_ret = get_rendering_quadrant_size(); return true; } #endif - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_ret = get_layer_name(index); - return true; - } else if (components[1] == "enabled") { - r_ret = is_layer_enabled(index); - return true; - } else if (components[1] == "modulate") { - r_ret = get_layer_modulate(index); - return true; - } else if (components[1] == "y_sort_enabled") { - r_ret = is_layer_y_sort_enabled(index); - return true; - } else if (components[1] == "y_sort_origin") { - r_ret = get_layer_y_sort_origin(index); - return true; - } else if (components[1] == "z_index") { - r_ret = get_layer_z_index(index); - return true; - } else if (components[1] == "navigation_enabled") { - r_ret = is_layer_navigation_enabled(index); - return true; - } else if (components[1] == "tile_data") { - r_ret = _get_tile_map_data_using_compatibility_format(index); - return true; - } else { - return false; - } + else { + return property_helper.property_get_value(sname, r_ret); } - return false; } void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - -#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ - { \ - const String property_name = vformat("layer_%d/" m_name, i); \ - p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \ - } - - for (uint32_t i = 0; i < layers.size(); i++) { - MAKE_LAYER_PROPERTY(Variant::STRING, "name", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", ""); - MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); - MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); - MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - -#undef MAKE_LAYER_PROPERTY -} - -bool TileMap::_property_can_revert(const StringName &p_name) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - return layers[index]->get_name() != default_layer->get_name(); - } else if (components[1] == "enabled") { - return layers[index]->is_enabled() != default_layer->is_enabled(); - } else if (components[1] == "modulate") { - return layers[index]->get_modulate() != default_layer->get_modulate(); - } else if (components[1] == "y_sort_enabled") { - return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); - } else if (components[1] == "y_sort_origin") { - return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); - } else if (components[1] == "z_index") { - return layers[index]->get_z_index() != default_layer->get_z_index(); - } else if (components[1] == "navigation_enabled") { - return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); - } - } - - return false; -} - -bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_property = default_layer->get_name(); - return true; - } else if (components[1] == "enabled") { - r_property = default_layer->is_enabled(); - return true; - } else if (components[1] == "modulate") { - r_property = default_layer->get_modulate(); - return true; - } else if (components[1] == "y_sort_enabled") { - r_property = default_layer->is_y_sort_enabled(); - return true; - } else if (components[1] == "y_sort_origin") { - r_property = default_layer->get_y_sort_origin(); - return true; - } else if (components[1] == "z_index") { - r_property = default_layer->get_z_index(); - return true; - } else if (components[1] == "navigation_enabled") { - r_property = default_layer->is_navigation_enabled(); - return true; - } - } - - return false; + property_helper.get_property_list(p_list, layers.size()); } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -1215,11 +1066,26 @@ TileMap::TileMap() { new_layer->set_tile_set(tile_set); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); - default_layer = memnew(TileMapLayer); -} -TileMap::~TileMap() { - memdelete(default_layer); + if (!base_property_helper.is_initialized()) { + // Initialize static PropertyListHelper if it wasn't yet. This has to be done here, + // because creating TileMapLayer in a static context is not always safe. + TileMapLayer *defaults = memnew(TileMapLayer); + + base_property_helper.set_prefix("layer_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled); + base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "y_sort_enabled"), defaults->is_y_sort_enabled(), &TileMap::set_layer_y_sort_enabled, &TileMap::is_layer_y_sort_enabled); + base_property_helper.register_property(PropertyInfo(Variant::INT, "y_sort_origin", PROPERTY_HINT_NONE, "suffix:px"), defaults->get_y_sort_origin(), &TileMap::set_layer_y_sort_origin, &TileMap::get_layer_y_sort_origin); + base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format); + + memdelete(defaults); + } + + property_helper.setup_for_instance(base_property_helper, this); } #undef TILEMAP_CALL_FOR_LAYER diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 41068ea978..45604bfb8a 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -32,6 +32,7 @@ #define TILE_MAP_H #include "scene/2d/tile_map_layer.h" +#include "scene/property_list_helper.h" #include "scene/resources/2d/tile_set.h" class Control; @@ -73,7 +74,9 @@ private: // Layers. LocalVector<TileMapLayer *> layers; - TileMapLayer *default_layer; // Dummy layer to fetch default values. + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; // Transforms for collision_animatable. Transform2D last_valid_transform; @@ -86,13 +89,14 @@ private: // Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array. void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data); Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const; + void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data); protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - bool _property_can_revert(const StringName &p_name) const; - bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -235,7 +239,6 @@ public: PackedStringArray get_configuration_warnings() const override; TileMap(); - ~TileMap(); }; VARIANT_ENUM_CAST(TileMap::VisibilityMode); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index e7b6aa1dba..0dc9834539 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -881,9 +881,9 @@ void CPUParticles3D::_particles_process(double p_delta) { case EMISSION_SHAPE_RING: { real_t ring_random_angle = Math::randf() * Math_TAU; real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; - Vector3 axis = emission_ring_axis.normalized(); + Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized(); Vector3 ortho_axis; - if (axis == Vector3(1.0, 0.0, 0.0)) { + if (axis.abs() == Vector3(1.0, 0.0, 0.0)) { ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); } else { ortho_axis = Vector3(1.0, 0.0, 0.0).cross(axis); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 86ff6d15dd..cc923b6676 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -397,7 +397,10 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { + // The tolerance used here comes from experiments on scenes up to + // 1000x1000x100 meters. If it's any smaller, some simplices will + // appear to self-intersect due to a lack of precision in Plane. + if (p_plane.has_point(v, 1.0 / (1 << 13))) { // Coplanar. } else if (p_plane.is_point_over(v)) { over++; @@ -419,7 +422,8 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc //#define DEBUG_BSP int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes) { - //if we reach here, it means there is more than one simplex + ERR_FAIL_COND_V(p_simplex_indices.size() < 2, -1); + int32_t node_index = (int32_t)bsp_nodes.size(); bsp_nodes.push_back(BSPNode()); @@ -477,13 +481,14 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc float score = 0; //by default, score is 0 (worst) if (over_count > 0) { - //give score mainly based on ratio (under / over), this means that this plane is splitting simplices a lot, but its balanced - score = float(under_count) / over_count; + // Simplices that are intersected by the plane are moved into both the over + // and under subtrees which makes the entire tree deeper, so the best plane + // will have the least intersections while separating the simplices evenly. + float balance = float(under_count) / over_count; + float separation = float(over_count + under_count) / p_simplex_indices.size(); + score = balance * separation * separation; } - //adjusting priority over least splits, probably not a great idea - //score *= Math::sqrt(float(over_count + under_count) / p_simplex_indices.size()); //also multiply score - if (score > best_plane_score) { best_plane = plane; best_plane_score = score; @@ -491,6 +496,44 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc } } + // We often end up with two (or on rare occasions, three) simplices that are + // either disjoint or share one vertex and don't have a separating plane + // among their faces. The fallback is to loop through new planes created + // with one vertex of the first simplex and two vertices of the second until + // we find a winner. + if (best_plane_score == 0) { + const BSPSimplex &simplex0 = p_simplices[p_simplex_indices[0]]; + const BSPSimplex &simplex1 = p_simplices[p_simplex_indices[1]]; + + for (uint32_t i = 0; i < 4 && !best_plane_score; i++) { + Vector3 v0 = p_points[simplex0.vertices[i]]; + for (uint32_t j = 0; j < 3 && !best_plane_score; j++) { + if (simplex0.vertices[i] == simplex1.vertices[j]) { + break; + } + Vector3 v1 = p_points[simplex1.vertices[j]]; + for (uint32_t k = j + 1; k < 4; k++) { + if (simplex0.vertices[i] == simplex1.vertices[k]) { + break; + } + Vector3 v2 = p_points[simplex1.vertices[k]]; + + Plane plane = Plane(v0, v1, v2); + if (plane == Plane()) { // When v0, v1, and v2 are collinear, they can't form a plane. + continue; + } + int32_t side0 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[0]); + int32_t side1 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[1]); + if ((side0 == 1 && side1 == -1) || (side0 == -1 && side1 == 1)) { + best_plane = plane; + best_plane_score = 1.0; + break; + } + } + } + } + } + LocalVector<int32_t> indices_over; LocalVector<int32_t> indices_under; @@ -515,8 +558,6 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc #endif if (best_plane_score < 0.0 || indices_over.size() == p_simplex_indices.size() || indices_under.size() == p_simplex_indices.size()) { - ERR_FAIL_COND_V(p_simplex_indices.size() <= 1, 0); //should not happen, this is a bug - // Failed to separate the tetrahedrons using planes // this means Delaunay broke at some point. // Luckily, because we are using tetrahedrons, we can resort to diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 5c081a0b47..b11a1d9506 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -293,7 +293,7 @@ Vector3 Node3D::get_global_rotation_degrees() const { void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; Transform3D transform = get_global_transform(); - transform.basis = Basis::from_euler(p_euler_rad); + transform.basis = Basis::from_euler(p_euler_rad) * Basis::from_scale(transform.basis.get_scale()); set_global_transform(transform); } diff --git a/scene/3d/physics/ray_cast_3d.cpp b/scene/3d/physics/ray_cast_3d.cpp index 0cb722a77e..a9272388c1 100644 --- a/scene/3d/physics/ray_cast_3d.cpp +++ b/scene/3d/physics/ray_cast_3d.cpp @@ -204,8 +204,10 @@ void RayCast3D::_notification(int p_what) { bool prev_collision_state = collided; _update_raycast_state(); - if (prev_collision_state != collided && get_tree()->is_debugging_collisions_hint()) { - _update_debug_shape_material(true); + if (get_tree()->is_debugging_collisions_hint()) { + if (prev_collision_state != collided) { + _update_debug_shape_material(true); + } if (is_inside_tree() && debug_instance.is_valid()) { RenderingServer::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); } diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 9581ae58d8..78a21ba9e1 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -366,6 +366,12 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::_set_interpolation); + ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::_get_interpolation); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001", PROPERTY_USAGE_NONE), "set_interpolation", "get_interpolation"); +#endif } void SkeletonIK3D::_process_modification() { @@ -415,6 +421,16 @@ StringName SkeletonIK3D::get_tip_bone() const { return tip_bone; } +#ifndef DISABLE_DEPRECATED +void SkeletonIK3D::_set_interpolation(real_t p_interpolation) { + set_influence(p_interpolation); +} + +real_t SkeletonIK3D::_get_interpolation() const { + return get_influence(); +} +#endif + void SkeletonIK3D::set_target_transform(const Transform3D &p_target) { target = p_target; reload_goal(); diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index eff018f2cc..5d6020194e 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -134,6 +134,11 @@ class SkeletonIK3D : public SkeletonModifier3D { Variant target_node_override_ref = Variant(); FabrikInverseKinematic::Task *task = nullptr; +#ifndef DISABLE_DEPRECATED + void _set_interpolation(real_t p_interpolation); + real_t _get_interpolation() const; +#endif // DISABLE_DEPRECATED + protected: void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b1870eea55..ad3f607661 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -358,7 +358,7 @@ void Button::_notification(int p_what) { if (!xl_text.is_empty()) { text_buf->set_alignment(align_rtl_checked); - float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf. + float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf. text_buf->set_width(text_buf_width); Point2 text_ofs; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index c13a2e281a..e481b78715 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -202,7 +202,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 dlg_size = get_size(); + Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); @@ -210,6 +210,15 @@ void AcceptDialog::_update_child_rects() { bg_panel->set_position(Point2()); bg_panel->set_size(dlg_size); + for (int i = 0; i < buttons_hbox->get_child_count(); i++) { + Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i)); + if (!b) { + continue; + } + + b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height)); + } + // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); @@ -389,6 +398,8 @@ void AcceptDialog::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height); } bool AcceptDialog::swap_cancel_ok = false; diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 6f9f450778..12b48c903a 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -57,6 +57,8 @@ class AcceptDialog : public Window { struct ThemeCache { Ref<StyleBox> panel_style; int buttons_separation = 0; + int buttons_min_width = 0; + int buttons_min_height = 0; } theme_cache; void _custom_action(const String &p_action); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 12b2364ddf..c3a586a1ee 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1255,52 +1255,6 @@ int FileDialog::get_option_count() const { return options.size(); } -bool FileDialog::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - set_option_name(item_index, p_value); - return true; - } else if (property == "values") { - set_option_values(item_index, p_value); - return true; - } else if (property == "default") { - set_option_default(item_index, p_value); - return true; - } - } - return false; -} - -bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - r_ret = get_option_name(item_index); - return true; - } else if (property == "values") { - r_ret = get_option_values(item_index); - return true; - } else if (property == "default") { - r_ret = get_option_default(item_index); - return true; - } - } - return false; -} - -void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < options.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i))); - p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i))); - p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i))); - } -} - void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); @@ -1386,6 +1340,13 @@ void FileDialog::_bind_methods() { BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button"); + + Option defaults; + + base_property_helper.set_prefix("option_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); + base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); } void FileDialog::set_show_hidden_files(bool p_show) { @@ -1563,6 +1524,8 @@ FileDialog::FileDialog() { if (register_func) { register_func(this); } + + property_helper.setup_for_instance(base_property_helper, this); } FileDialog::~FileDialog() { diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 7caae7e216..4236f0a56b 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -37,6 +37,7 @@ #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" #include "scene/gui/tree.h" +#include "scene/property_list_helper.h" class GridContainer; @@ -137,6 +138,10 @@ private: Vector<String> values; int default_idx = 0; }; + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + Vector<Option> options; Dictionary selected_options; bool options_dirty = false; @@ -187,9 +192,11 @@ private: protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); public: diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 89c627a7a8..e83d9c7c1b 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) { } bool MenuButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); return valid; } return false; } bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + r_ret = popup->get(sname.trim_prefix("popup/"), &valid); return valid; } return false; } -void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); - pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); - pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); @@ -215,6 +186,17 @@ void MenuButton::_bind_methods() { ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("about_to_popup")); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); } void MenuButton::set_disable_shortcuts(bool p_disabled) { @@ -235,6 +217,8 @@ MenuButton::MenuButton(const String &p_text) : add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true)); popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index eea6b8e877..2bd577ddd0 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -42,13 +43,18 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup = nullptr; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 0e10652f07..509c6aca99 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) { } bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } + int index; + const String sname = p_name; + if (property_helper.is_property_valid(sname, &index)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); - int idx = components[1].get_slice("_", 1).to_int(); - if (idx == current) { + if (index == current) { // Force refreshing currently displayed item. current = NONE_SELECTED; - _select(idx, false); + _select(index, false); } + const String property = sname.get_slice("/", 2); if (property == "text" || property == "icon") { _queue_update_size_cache(); } @@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { return false; } -bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } - - bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); - return valid; - } - return false; -} - -void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void OptionButton::_focused(int p_which) { emit_signal(SNAME("item_focused"), p_which); } @@ -606,6 +567,15 @@ void OptionButton::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); } void OptionButton::set_disable_shortcuts(bool p_disabled) { @@ -625,6 +595,8 @@ OptionButton::OptionButton(const String &p_text) : popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 9c15b295a9..4b5164161a 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -64,11 +65,15 @@ class OptionButton : public Button { int modulate_arrow = 0; } theme_cache; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); void _select_int(int p_which); void _refresh_size_cache(); + void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it). virtual void pressed() override; @@ -78,8 +83,10 @@ protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index d90d5da2e9..87383283fd 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -230,7 +230,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const { void PopupPanel::_update_child_rects() { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); + Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); + Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -244,7 +245,7 @@ void PopupPanel::_update_child_rects() { if (c == panel) { c->set_position(Vector2()); - c->set_size(get_size()); + c->set_size(panel_size); } else { c->set_position(cpos); c->set_size(csize); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 56346f5edc..260956a775 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -312,18 +312,17 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { - if (p_over.x < 0 || p_over.x >= get_size().width || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP)) { + float win_scale = get_content_scale_factor(); + if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { return -1; } - Point2 ofs; + Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; for (int i = 0; i < items.size(); i++) { - ofs.y += theme_cache.v_separation; - - ofs.y += _get_item_height(i); - - if (p_over.y - control->get_position().y < ofs.y) { + ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs.y += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs.y) { return i; } } @@ -341,15 +340,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 this_rect(this_pos, get_size()); float scroll_offset = control->get_position().y; + float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); + float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); submenu_popup->reset_size(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); Point2 submenu_pos; if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } else { - submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } // Fix pos if going outside parent rect. @@ -386,8 +387,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; + safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; + safe_area.size.y = scaled_height_cache + theme_cache.v_separation; Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -400,11 +401,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) { + int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -576,6 +577,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { } item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; } + item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); Ref<InputEventMouseButton> b = p_event; @@ -640,11 +642,17 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { for (const Rect2 &E : autohide_areas) { if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) { + // The mouse left the safe area, prepare to close. _close_pressed(); return; } } + if (!minimum_lifetime_timer->is_stopped()) { + // The mouse left the safe area, but came back again, so cancel the auto-closing. + minimum_lifetime_timer->stop(); + } + if (!item_clickable_area.has_point(m->get_position())) { return; } @@ -2821,6 +2829,14 @@ void PopupMenu::popup(const Rect2i &p_bounds) { } else { moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); + if (!is_embedded()) { + float win_scale = get_parent_visible_window()->get_content_scale_factor(); + set_content_scale_factor(win_scale); + Size2 minsize = get_contents_minimum_size(); + minsize.height += 0.5 * win_scale; // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing. + set_min_size(minsize * win_scale); + set_size(Vector2(0, 0)); // Shrinkwraps to min size. + } Popup::popup(p_bounds); } } diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index f7097fffaf..832c1bcc8b 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -97,6 +97,10 @@ class PopupMenu : public Popup { static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; + // To make Item available. + friend class OptionButton; + friend class MenuButton; + RID global_menu; RID system_menu; NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index d20fef8164..2d687eb201 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1720,58 +1720,6 @@ bool TabBar::get_deselect_enabled() const { return deselect_enabled; } -bool TabBar::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - set_tab_title(tab_index, p_value); - return true; - } else if (property == "icon") { - set_tab_icon(tab_index, p_value); - return true; - } else if (property == "disabled") { - set_tab_disabled(tab_index, p_value); - return true; - } - } - return false; -} - -bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - r_ret = get_tab_title(tab_index); - return true; - } else if (property == "icon") { - r_ret = get_tab_icon(tab_index); - return true; - } else if (property == "disabled") { - r_ret = is_tab_disabled(tab_index); - return true; - } - } - return false; -} - -void TabBar::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < tabs.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i)); - pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count); @@ -1890,10 +1838,19 @@ void TabBar::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, close_icon, "close"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_pressed_style, "button_pressed"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_hl_style, "button_highlight"); + + Tab defaults(true); + + base_property_helper.set_prefix("tab_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled); } TabBar::TabBar() { set_size(Size2(get_size().width, get_minimum_size().height)); set_focus_mode(FOCUS_ALL); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); + + property_helper.setup_for_instance(base_property_helper, this); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 65a1d5bd4f..6c09e960f1 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -32,6 +32,7 @@ #define TAB_BAR_H #include "scene/gui/control.h" +#include "scene/property_list_helper.h" #include "scene/resources/text_line.h" class TabBar : public Control { @@ -77,8 +78,13 @@ private: Tab() { text_buf.instantiate(); } + + Tab(bool p_dummy) {} }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + int offset = 0; int max_drawn_tab = 0; int highlight_arrow = -1; @@ -163,9 +169,11 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, tabs.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1daf86fe0f..9a2ba23ce8 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2459,7 +2459,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width(); } - p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size); button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); item_width_with_buttons -= button_size.width + theme_cache.button_margin; } @@ -3995,12 +3994,14 @@ bool Tree::edit_selected(bool p_force_edit) { return false; } + float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor(); Rect2 rect; if (select_mode == SELECT_ROW) { rect = s->get_meta("__focus_col_" + itos(selected_col)); } else { rect = s->get_meta("__focus_rect"); } + rect.position *= popup_scale; popup_edited_item = s; popup_edited_item_col = col; @@ -4043,7 +4044,7 @@ bool Tree::edit_selected(bool p_force_edit) { popup_rect.size = rect.size; // Account for icon. - Size2 icon_size = _get_cell_icon_size(c); + Size2 icon_size = _get_cell_icon_size(c) * popup_scale; popup_rect.position.x += icon_size.x; popup_rect.size.x -= icon_size.x; @@ -4070,7 +4071,10 @@ bool Tree::edit_selected(bool p_force_edit) { } popup_editor->set_position(popup_rect.position); - popup_editor->set_size(popup_rect.size); + popup_editor->set_size(popup_rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4086,7 +4090,10 @@ bool Tree::edit_selected(bool p_force_edit) { text_editor->show(); popup_editor->set_position(get_screen_position() + rect.position); - popup_editor->set_size(rect.size); + popup_editor->set_size(rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -5400,7 +5407,6 @@ String Tree::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } - Point2 button_pos = pos; if (h_scroll->is_visible_in_tree()) { pos.x += h_scroll->get_value(); } @@ -5413,13 +5419,22 @@ String Tree::get_tooltip(const Point2 &p_pos) const { if (it) { const TreeItem::Cell &c = it->cells[col]; + int col_width = get_column_width(col); + + for (int i = 0; i < col; i++) { + pos.x -= get_column_width(i); + } + for (int j = c.buttons.size() - 1; j >= 0; j--) { - if (c.buttons[j].rect.has_point(button_pos)) { + Ref<Texture2D> b = c.buttons[j].texture; + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); + if (pos.x > col_width - size.width) { String tooltip = c.buttons[j].tooltip; if (!tooltip.is_empty()) { return tooltip; } } + col_width -= size.width; } String ret; if (it->get_tooltip_text(col) == "") { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 21696d8216..c7c266a2e7 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -108,7 +108,6 @@ private: Ref<Texture2D> texture; Color color = Color(1, 1, 1, 1); String tooltip; - Rect2i rect; }; Vector<Button> buttons; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 17d7fec230..2d30ea345d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1461,6 +1461,8 @@ void Viewport::_gui_show_tooltip() { panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); + // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. + panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1469,17 +1471,25 @@ void Viewport::_gui_show_tooltip() { tooltip_owner->add_child(gui.tooltip_popup); + Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder()); + if (!window) { // Not embedded. + window = gui.tooltip_popup->get_parent_visible_window(); + } + float win_scale = window->content_scale_factor; Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + if (!gui.tooltip_popup->is_embedded()) { + tooltip_offset *= win_scale; + } Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size()); - r.size = r.size.min(panel->get_max_size()); - - Window *window = gui.tooltip_popup->get_parent_visible_window(); Rect2i vr; if (gui.tooltip_popup->is_embedded()) { vr = gui.tooltip_popup->get_embedder()->get_visible_rect(); } else { + panel->content_scale_factor = win_scale; + r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { // Place it in the opposite direction. If it fails, just hug the border. diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index d9a80011b0..b666e4c52d 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -31,7 +31,7 @@ #include "property_list_helper.h" const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const { - const Vector<String> components = p_property.split("/", true, 2); + const Vector<String> components = p_property.rsplit("/", true, 1); if (components.size() < 2 || !components[0].begins_with(prefix)) { return nullptr; } @@ -48,36 +48,73 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri } void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const { + DEV_ASSERT(p_setter); Variant args[] = { p_index, p_value }; const Variant *argptrs[] = { &args[0], &args[1] }; Callable::CallError ce; p_setter->call(object, argptrs, 2, ce); } -Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const { +Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const { + if (!p_property->getter) { + return object->get(prefix + itos(p_index) + "/" + p_property->info.name); + } + Callable::CallError ce; Variant args[] = { p_index }; const Variant *argptrs[] = { &args[0] }; - return p_getter->call(object, argptrs, 1, ce); + return p_property->getter->call(object, argptrs, 1, ce); } void PropertyListHelper::set_prefix(const String &p_prefix) { prefix = p_prefix; } +void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) { + Property property; + property.info = p_info; + property.default_value = p_default; + + property_list[p_info.name] = property; +} + +bool PropertyListHelper::is_initialized() const { + return !property_list.is_empty(); +} + void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) { prefix = p_base.prefix; property_list = p_base.property_list; object = p_object; } +bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const { + const Vector<String> components = p_property.rsplit("/", true, 1); + if (components.size() < 2 || !components[0].begins_with(prefix)) { + return false; + } + + { + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return false; + } + + if (r_index) { + *r_index = index_string.to_int(); + } + } + + return property_list.has(components[1]); +} + void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const { for (int i = 0; i < p_count; i++) { for (const KeyValue<String, Property> &E : property_list) { const Property &property = E.value; PropertyInfo info = property.info; - if (_call_getter(property.getter, i) == property.default_value) { + if (_call_getter(&property, i) == property.default_value) { info.usage &= (~PROPERTY_USAGE_STORAGE); } @@ -92,7 +129,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r const Property *property = _get_property(p_property, &index); if (property) { - r_ret = _call_getter(property->getter, index); + r_ret = _call_getter(property, index); return true; } return false; @@ -110,8 +147,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari } bool PropertyListHelper::property_can_revert(const String &p_property) const { - int index; - return _get_property(p_property, &index) != nullptr; + return is_property_valid(p_property); } bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const { @@ -129,8 +165,10 @@ PropertyListHelper::~PropertyListHelper() { // No object = it's the main helper. Do a cleanup. if (!object) { for (const KeyValue<String, Property> &E : property_list) { - memdelete(E.value.setter); - memdelete(E.value.getter); + if (E.value.setter) { + memdelete(E.value.setter); + memdelete(E.value.getter); + } } } } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index 6c1ad21a05..eac6b03d47 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -48,10 +48,12 @@ class PropertyListHelper { const Property *_get_property(const String &p_property, int *r_index) const; void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const; - Variant _call_getter(const MethodBind *p_getter, int p_index) const; + Variant _call_getter(const Property *p_property, int p_index) const; public: void set_prefix(const String &p_prefix); + // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic. + void register_property(const PropertyInfo &p_info, const Variant &p_default); template <typename S, typename G> void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) { @@ -64,7 +66,9 @@ public: property_list[p_info.name] = property; } + bool is_initialized() const; void setup_for_instance(const PropertyListHelper &p_base, Object *p_object); + bool is_property_valid(const String &p_property, int *r_index = nullptr) const; void get_property_list(List<PropertyInfo> *p_list, int p_count) const; bool property_get_value(const String &p_property, Variant &r_ret) const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 1c8833494d..d13f338444 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -657,6 +657,9 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase); GDREGISTER_CLASS(VisualShaderNodeFrame); +#ifndef DISABLE_DEPRECATED + GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility. +#endif GDREGISTER_CLASS(VisualShaderNodeFloatConstant); GDREGISTER_CLASS(VisualShaderNodeIntConstant); GDREGISTER_CLASS(VisualShaderNodeUIntConstant); diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 5acb08de14..30b90841e3 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -635,9 +635,9 @@ void ParticleProcessMaterial::_update_shader() { code += " \n"; code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n"; - code += " vec3 axis = normalize(emission_ring_axis);\n"; + code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n"; code += " vec3 ortho_axis = vec3(0.0);\n"; - code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n"; + code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n"; code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n"; code += " } else {\n"; code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n"; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 9ac899ad78..7e80d0be3c 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -4206,7 +4206,7 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { set_allow_v_resize(true); } -////////////// Comment +////////////// Frame String VisualShaderNodeFrame::get_caption() const { return title; @@ -4323,6 +4323,25 @@ void VisualShaderNodeFrame::_bind_methods() { VisualShaderNodeFrame::VisualShaderNodeFrame() { } +////////////// Comment (Deprecated) + +#ifndef DISABLE_DEPRECATED +void VisualShaderNodeComment::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); +} + +void VisualShaderNodeComment::set_description(const String &p_description) { + description = p_description; +} + +String VisualShaderNodeComment::get_description() const { + return description; +} +#endif + ////////////// GroupBase void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 3ef6dcd4f9..d7270f3ac6 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -767,6 +767,28 @@ public: VisualShaderNodeFrame(); }; +#ifndef DISABLE_DEPRECATED +// Deprecated, for compatibility only. +class VisualShaderNodeComment : public VisualShaderNodeFrame { + GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame); + + String description; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override { return "Comment(Deprecated)"; } + + virtual Category get_category() const override { return CATEGORY_NONE; } + + void set_description(const String &p_description); + String get_description() const; + + VisualShaderNodeComment() {} +}; +#endif + class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 3e3a7d2381..b5333d91c6 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -676,63 +676,6 @@ bool AudioStreamRandomizer::is_monophonic() const { return false; } -bool AudioStreamRandomizer::_get(const StringName &p_name, Variant &r_ret) const { - if (AudioStream::_get(p_name, r_ret)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - r_ret = get_stream(index); - return true; - } else if (components[1] == "weight") { - r_ret = get_stream_probability_weight(index); - return true; - } else { - return false; - } - } - return false; -} - -bool AudioStreamRandomizer::_set(const StringName &p_name, const Variant &p_value) { - if (AudioStream::_set(p_name, p_value)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - set_stream(index, p_value); - return true; - } else if (components[1] == "weight") { - set_stream_probability_weight(index, p_value); - return true; - } else { - return false; - } - } - return false; -} - -void AudioStreamRandomizer::_get_property_list(List<PropertyInfo> *p_list) const { - AudioStream::_get_property_list(p_list); // Define the trivial scalar properties. - p_list->push_back(PropertyInfo(Variant::NIL, "Streams", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < audio_stream_pool.size(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("stream_%d/stream", i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("stream_%d/weight", i), PROPERTY_HINT_RANGE, "0,100,0.001,or_greater")); - } -} - void AudioStreamRandomizer::_bind_methods() { ClassDB::bind_method(D_METHOD("add_stream", "index", "stream", "weight"), &AudioStreamRandomizer::add_stream, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("move_stream", "index_from", "index_to"), &AudioStreamRandomizer::move_stream); @@ -764,9 +707,17 @@ void AudioStreamRandomizer::_bind_methods() { BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS); BIND_ENUM_CONSTANT(PLAYBACK_RANDOM); BIND_ENUM_CONSTANT(PLAYBACK_SEQUENTIAL); + + PoolEntry defaults; + + base_property_helper.set_prefix("stream_"); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream); + base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight); } -AudioStreamRandomizer::AudioStreamRandomizer() {} +AudioStreamRandomizer::AudioStreamRandomizer() { + property_helper.setup_for_instance(base_property_helper, this); +} void AudioStreamPlaybackRandomizer::start(double p_from_pos) { playing = playback; diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index f8123fbe15..01a4a09942 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -33,6 +33,7 @@ #include "core/io/image.h" #include "core/io/resource.h" +#include "scene/property_list_helper.h" #include "servers/audio/audio_filter_sw.h" #include "servers/audio_server.h" @@ -236,9 +237,12 @@ private: struct PoolEntry { Ref<AudioStream> stream; - float weight; + float weight = 1.0; }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + HashSet<AudioStreamPlaybackRandomizer *> playbacks; Vector<PoolEntry> audio_stream_pool; float random_pitch_scale = 1.0f; @@ -254,9 +258,11 @@ private: protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, audio_stream_pool.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } public: void add_stream(int p_index, Ref<AudioStream> p_stream, float p_weight = 1.0); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 351c03c158..9600caa214 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -212,7 +212,7 @@ String DisplayServer::global_menu_get_item_submenu(const String &p_menu_root, in ERR_FAIL_NULL_V(nmenu, String()); RID rid = nmenu->get_item_submenu(_get_rid_from_name(nmenu, p_menu_root), p_idx); if (!nmenu->is_system_menu(rid)) { - for (HashMap<String, RID>::Iterator E = menu_names.begin(); E;) { + for (HashMap<String, RID>::Iterator E = menu_names.begin(); E; ++E) { if (E->value == rid) { return E->key; } diff --git a/servers/physics_2d/godot_shape_2d.cpp b/servers/physics_2d/godot_shape_2d.cpp index db5e6b2353..d77b1a77e3 100644 --- a/servers/physics_2d/godot_shape_2d.cpp +++ b/servers/physics_2d/godot_shape_2d.cpp @@ -123,7 +123,7 @@ void GodotWorldBoundaryShape2D::set_data(const Variant &p_data) { ERR_FAIL_COND(arr.size() != 2); normal = arr[0]; d = arr[1]; - configure(Rect2(Vector2(-1e4, -1e4), Vector2(1e4 * 2, 1e4 * 2))); + configure(Rect2(Vector2(-1e15, -1e15), Vector2(1e15 * 2, 1e15 * 2))); } Variant GodotWorldBoundaryShape2D::get_data() const { diff --git a/servers/physics_3d/godot_shape_3d.cpp b/servers/physics_3d/godot_shape_3d.cpp index ea389ff59c..6eb983d5e0 100644 --- a/servers/physics_3d/godot_shape_3d.cpp +++ b/servers/physics_3d/godot_shape_3d.cpp @@ -152,7 +152,7 @@ Vector3 GodotWorldBoundaryShape3D::get_moment_of_inertia(real_t p_mass) const { void GodotWorldBoundaryShape3D::_setup(const Plane &p_plane) { plane = p_plane; - configure(AABB(Vector3(-1e4, -1e4, -1e4), Vector3(1e4 * 2, 1e4 * 2, 1e4 * 2))); + configure(AABB(Vector3(-1e15, -1e15, -1e15), Vector3(1e15 * 2, 1e15 * 2, 1e15 * 2))); } void GodotWorldBoundaryShape3D::set_data(const Variant &p_data) { diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index e32164ea98..46c84fd230 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -332,7 +332,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 child_item_count = ci->ysort_children_count + 1; child_items = (Item **)alloca(child_item_count * sizeof(Item *)); - ci->ysort_xform = final_xform.affine_inverse(); + ci->ysort_xform = ci->xform_curr.affine_inverse(); ci->ysort_pos = Vector2(); ci->ysort_modulate = Color(1, 1, 1, 1); ci->ysort_index = 0; diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 27c07f23fa..b5d31f5414 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1472,7 +1472,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } @@ -1491,7 +1491,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 0e69ad99b8..c7ab7ea462 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1413,7 +1413,7 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } @@ -1930,7 +1930,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } if (needs_pre_resolve) { //pre clear the depth framebuffer, as AMD (and maybe others?) use compute for it, and barrier other compute shaders. - RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear); + RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear, 0.0); RD::get_singleton()->draw_list_end(); //start compute processes here, so they run at the same time as depth pre-pass _post_prepass_render(p_render_data, using_sdfgi || using_voxelgi); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 48c9cda253..5715d94d95 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -614,7 +614,7 @@ void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) { if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index e61bb9eae8..6cb03871c9 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -1157,6 +1157,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render render_data.lights = ∅ render_data.reflection_probes = ∅ render_data.voxel_gi_instances = ∅ + render_data.lightmaps = ∅ } if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED || diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index c8277024cf..9a898a2fca 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1103,7 +1103,7 @@ private: public: DrawListID draw_list_begin_for_screen(DisplayServer::WindowID p_screen = 0, const Color &p_clear_color = Color()); - DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 0.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); + DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); void draw_list_set_blend_constants(DrawListID p_list, const Color &p_color); void draw_list_bind_render_pipeline(DrawListID p_list, RID p_render_pipeline); diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 827a1bed17..8778ea86a6 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -764,6 +764,8 @@ TEST_SUITE("[Navigation]") { navigation_server->process(0.0); // Give server some cycles to commit. } + // FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613). + /* TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh); @@ -781,6 +783,7 @@ TEST_SUITE("[Navigation]") { CHECK_EQ(navigation_mesh->get_polygon_count(), 0); CHECK_EQ(navigation_mesh->get_vertices().size(), 0); } + */ } } //namespace TestNavigationServer3D diff --git a/tests/test_main.cpp b/tests/test_main.cpp index bb6837c965..56bd8739c6 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -100,7 +100,6 @@ #include "tests/core/variant/test_variant.h" #include "tests/core/variant/test_variant_utility.h" #include "tests/scene/test_animation.h" -#include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" #include "tests/scene/test_camera_2d.h" @@ -127,6 +126,7 @@ #include "tests/test_validate_testing.h" #ifndef _3D_DISABLED +#include "tests/scene/test_arraymesh.h" #include "tests/scene/test_camera_3d.h" #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" |