summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct156
-rw-r--r--core/object/object.cpp19
-rw-r--r--core/object/object.h2
-rw-r--r--core/object/worker_thread_pool.cpp8
-rw-r--r--doc/classes/AcceptDialog.xml6
-rw-r--r--doc/classes/FileSystemDock.xml5
-rw-r--r--doc/classes/Object.xml11
-rw-r--r--doc/classes/SkeletonIK3D.xml3
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp58
-rw-r--r--editor/create_dialog.cpp9
-rw-r--r--editor/editor_file_system.cpp10
-rw-r--r--editor/editor_node.cpp14
-rw-r--r--editor/filesystem_dock.cpp15
-rw-r--r--editor/filesystem_dock.h5
-rw-r--r--editor/gui/editor_dir_dialog.cpp27
-rw-r--r--editor/gui/editor_dir_dialog.h2
-rw-r--r--editor/gui/editor_file_dialog.cpp6
-rw-r--r--editor/multi_node_edit.cpp2
-rw-r--r--editor/multi_node_edit.h15
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp12
-rw-r--r--editor/scene_tree_dock.cpp7
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp8
-rw-r--r--editor/themes/editor_theme_manager.h1
-rw-r--r--main/main.cpp11
-rw-r--r--misc/extension_api_validation/4.2-stable.expected3
-rw-r--r--modules/fbx/fbx_document.cpp7
-rw-r--r--modules/gltf/gltf_document.cpp6
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayer.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml2
-rw-r--r--scene/2d/tile_map.cpp211
-rw-r--r--scene/2d/tile_map.h11
-rw-r--r--scene/3d/cpu_particles_3d.cpp4
-rw-r--r--scene/3d/lightmap_gi.cpp59
-rw-r--r--scene/3d/node_3d.cpp2
-rw-r--r--scene/3d/physics/ray_cast_3d.cpp6
-rw-r--r--scene/3d/skeleton_ik_3d.cpp16
-rw-r--r--scene/3d/skeleton_ik_3d.h5
-rw-r--r--scene/gui/dialogs.cpp13
-rw-r--r--scene/gui/dialogs.h2
-rw-r--r--scene/gui/file_dialog.cpp55
-rw-r--r--scene/gui/file_dialog.h13
-rw-r--r--scene/gui/menu_button.cpp54
-rw-r--r--scene/gui/menu_button.h8
-rw-r--r--scene/gui/option_button.cpp64
-rw-r--r--scene/gui/option_button.h11
-rw-r--r--scene/gui/popup.cpp5
-rw-r--r--scene/gui/popup_menu.cpp38
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/tab_bar.cpp61
-rw-r--r--scene/gui/tab_bar.h14
-rw-r--r--scene/gui/tree.cpp14
-rw-r--r--scene/main/viewport.cpp16
-rw-r--r--scene/property_list_helper.cpp56
-rw-r--r--scene/property_list_helper.h6
-rw-r--r--scene/resources/particle_process_material.cpp4
-rw-r--r--servers/audio/audio_stream.cpp67
-rw-r--r--servers/audio/audio_stream.h14
-rw-r--r--servers/display_server.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp1
-rw-r--r--tests/servers/test_navigation_server_3d.h3
63 files changed, 650 insertions, 626 deletions
diff --git a/SConstruct b/SConstruct
index ff1eba681d..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.editor_build and 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/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/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/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/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/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_node.cpp b/editor/editor_node.cpp
index 20fa30ff34..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);
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 2ed547a970..b811d0f21b 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -234,15 +234,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 +328,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 +1068,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 +1082,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);
@@ -3086,6 +3086,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 +3798,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/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/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 ed74094c9e..801e8934b0 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -842,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 ea97a2150e..43349ae6ef 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -264,9 +264,6 @@ Validate extension JSON: API was removed: classes/VisualShaderNodeComment/proper
GH-87888
--------
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.
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/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/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 84d3a5f7fa..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: {
@@ -740,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);
@@ -791,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 {
@@ -1214,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/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 b6dd3ac0b4..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;
@@ -2827,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..1242d3bd16 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3995,12 +3995,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 +4045,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 +4072,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 +4091,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();
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/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/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/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 = &empty;
render_data.reflection_probes = &empty;
render_data.voxel_gi_instances = &empty;
+ render_data.lightmaps = &empty;
}
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED ||
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