summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/object/class_db.cpp27
-rw-r--r--core/object/object.cpp19
-rw-r--r--core/object/object.h2
-rw-r--r--core/object/script_language.cpp8
-rw-r--r--core/object/script_language.h2
-rw-r--r--core/object/worker_thread_pool.cpp9
-rw-r--r--core/object/worker_thread_pool.h5
-rw-r--r--doc/classes/AcceptDialog.xml6
-rw-r--r--doc/classes/EditorSettings.xml3
-rw-r--r--doc/classes/FileSystemDock.xml5
-rw-r--r--doc/classes/Object.xml11
-rw-r--r--doc/classes/SkeletonIK3D.xml3
-rw-r--r--doc/classes/VisualShaderNodeComment.xml16
-rw-r--r--editor/connections_dialog.cpp17
-rw-r--r--editor/connections_dialog.h2
-rw-r--r--editor/create_dialog.cpp16
-rw-r--r--editor/editor_build_profile.cpp22
-rw-r--r--editor/editor_build_profile.h1
-rw-r--r--editor/editor_feature_profile.cpp23
-rw-r--r--editor/editor_feature_profile.h1
-rw-r--r--editor/editor_help.cpp860
-rw-r--r--editor/editor_help.h92
-rw-r--r--editor/editor_inspector.cpp65
-rw-r--r--editor/editor_inspector.h1
-rw-r--r--editor/editor_properties_array_dict.cpp1
-rw-r--r--editor/editor_properties_array_dict.h1
-rw-r--r--editor/editor_quick_open.cpp61
-rw-r--r--editor/editor_quick_open.h2
-rw-r--r--editor/editor_settings.cpp1
-rw-r--r--editor/filesystem_dock.cpp81
-rw-r--r--editor/filesystem_dock.h8
-rw-r--r--editor/gui/editor_dir_dialog.cpp27
-rw-r--r--editor/gui/editor_dir_dialog.h2
-rw-r--r--editor/icons/PreviewRotate.svg1
-rw-r--r--editor/import/3d/scene_import_settings.cpp72
-rw-r--r--editor/import/3d/scene_import_settings.h17
-rw-r--r--editor/input_event_configuration_dialog.cpp2
-rw-r--r--editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp79
-rw-r--r--editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h49
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp6
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h3
-rw-r--r--editor/plugins/theme_editor_plugin.cpp8
-rw-r--r--editor/plugins/theme_editor_plugin.h2
-rw-r--r--editor/project_manager.cpp11
-rw-r--r--editor/project_manager.h2
-rw-r--r--editor/project_manager/project_list.cpp42
-rw-r--r--editor/project_manager/project_list.h1
-rw-r--r--editor/property_selector.cpp33
-rw-r--r--editor/themes/editor_theme_manager.cpp30
-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.expected9
-rw-r--r--modules/fbx/fbx_document.cpp7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp6
-rw-r--r--modules/gdscript/gdscript_compiler.h6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/constants.out32
-rw-r--r--modules/gdscript/tests/scripts/parser/features/static_typing.out20
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/unused_constant.out5
-rw-r--r--modules/gltf/gltf_document.cpp6
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp2
-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/lightmap_gi.cpp59
-rw-r--r--scene/3d/node_3d.cpp11
-rw-r--r--scene/3d/node_3d.h1
-rw-r--r--scene/3d/skeleton_ik_3d.cpp16
-rw-r--r--scene/3d/skeleton_ik_3d.h5
-rw-r--r--scene/3d/visual_instance_3d.cpp1
-rw-r--r--scene/gui/button.cpp2
-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.cpp27
-rw-r--r--scene/gui/tree.h1
-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/register_scene_types.cpp3
-rw-r--r--scene/resources/visual_shader.cpp21
-rw-r--r--scene/resources/visual_shader.h22
-rw-r--r--servers/audio/audio_stream.cpp67
-rw-r--r--servers/audio/audio_stream.h14
-rw-r--r--servers/rendering/renderer_rd/environment/sky.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp2
-rw-r--r--servers/rendering/rendering_device.h2
-rw-r--r--tests/core/math/test_transform_3d.h29
104 files changed, 1791 insertions, 1021 deletions
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 80a2703c2f..7ef1ce74ed 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -182,8 +182,20 @@ public:
// Construct a placeholder.
Object *obj = native_parent->creation_func();
+
+ // ClassDB::set_object_extension_instance() won't be called for placeholders.
+ // We need need to make sure that all the things it would have done (even if
+ // done in a different way to support placeholders) will also be done here.
+
obj->_extension = ClassDB::get_placeholder_extension(ti->name);
obj->_extension_instance = memnew(PlaceholderExtensionInstance(ti->name));
+
+#ifdef TOOLS_ENABLED
+ if (obj->_extension->track_instance) {
+ obj->_extension->track_instance(obj->_extension->tracking_userdata, obj);
+ }
+#endif
+
return obj;
}
@@ -506,14 +518,7 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
extension = get_placeholder_extension(ti->name);
}
#endif
- Object *obj = (Object *)extension->create_instance(extension->class_userdata);
-
-#ifdef TOOLS_ENABLED
- if (extension->track_instance) {
- extension->track_instance(extension->tracking_userdata, obj);
- }
-#endif
- return obj;
+ return (Object *)extension->create_instance(extension->class_userdata);
} else {
#ifdef TOOLS_ENABLED
if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) {
@@ -638,6 +643,12 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
p_object->_extension = ti->gdextension;
p_object->_extension_instance = p_instance;
+
+#ifdef TOOLS_ENABLED
+ if (p_object->_extension->track_instance) {
+ p_object->_extension->track_instance(p_object->_extension->tracking_userdata, p_object);
+ }
+#endif
}
bool ClassDB::can_instantiate(const StringName &p_class) {
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/script_language.cpp b/core/object/script_language.cpp
index 660e13e819..bd3199ca0a 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -51,7 +51,7 @@ void Script::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
if (EngineDebugger::is_active()) {
- EngineDebugger::get_script_debugger()->set_break_language(get_language());
+ callable_mp(this, &Script::_set_debugger_break_language).call_deferred();
}
} break;
}
@@ -103,6 +103,12 @@ Dictionary Script::_get_script_constant_map() {
return ret;
}
+void Script::_set_debugger_break_language() {
+ if (EngineDebugger::is_active()) {
+ EngineDebugger::get_script_debugger()->set_break_language(get_language());
+ }
+}
+
int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
MethodInfo mi = get_method_info(p_method);
diff --git a/core/object/script_language.h b/core/object/script_language.h
index c6c6f3de9f..223f114150 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -124,6 +124,8 @@ protected:
TypedArray<Dictionary> _get_script_signal_list();
Dictionary _get_script_constant_map();
+ void _set_debugger_break_language();
+
public:
virtual void reload_from_file() override;
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index e0b8730a67..9c9e0fa899 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -72,6 +72,9 @@ void WorkerThreadPool::_process_task(Task *p_task) {
p_task->pool_thread_index = pool_thread_index;
prev_task = curr_thread.current_task;
curr_thread.current_task = p_task;
+ if (p_task->pending_notify_yield_over) {
+ curr_thread.yield_is_over = true;
+ }
task_mutex.unlock();
}
#endif
@@ -491,7 +494,11 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
ERR_FAIL_MSG("Invalid Task ID.");
}
Task *task = *taskp;
- if (task->completed) {
+ if (task->pool_thread_index == -1) { // Completed or not started yet.
+ if (!task->completed) {
+ // This avoids a race condition where a task is created and yield-over called before it's processed.
+ task->pending_notify_yield_over = true;
+ }
task_mutex.unlock();
return;
}
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index 64f24df79f..a9cf260a0f 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -81,7 +81,8 @@ private:
void *native_func_userdata = nullptr;
String description;
Semaphore done_semaphore; // For user threads awaiting.
- bool completed = false;
+ bool completed : 1;
+ bool pending_notify_yield_over : 1;
Group *group = nullptr;
SelfList<Task> task_elem;
uint32_t waiting_pool = 0;
@@ -92,6 +93,8 @@ private:
void free_template_userdata();
Task() :
+ completed(false),
+ pending_notify_yield_over(false),
task_elem(this) {}
};
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/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 20ee65403c..93a7b09fce 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -384,6 +384,9 @@
<member name="editors/3d/selection_box_color" type="Color" setter="" getter="">
The color to use for the selection box that surrounds selected nodes in the 3D editor viewport. The color's alpha channel influences the selection box's opacity.
</member>
+ <member name="editors/3d_gizmos/gizmo_colors/aabb" type="Color" setter="" getter="">
+ The color to use for the AABB gizmo that displays the [GeometryInstance3D]'s custom [AABB].
+ </member>
<member name="editors/3d_gizmos/gizmo_colors/instantiated" type="Color" setter="" getter="">
The color override to use for 3D editor gizmos if the [Node3D] in question is part of an instantiated scene file (from the perspective of the current scene).
</member>
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/doc/classes/VisualShaderNodeComment.xml b/doc/classes/VisualShaderNodeComment.xml
new file mode 100644
index 0000000000..28496a715a
--- /dev/null
+++ b/doc/classes/VisualShaderNodeComment.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeComment" inherits="VisualShaderNodeFrame" deprecated="This class has no function anymore and only exists for compatibility." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Only exists for compatibility. Use [VisualShaderNodeFrame] as a replacement.
+ </brief_description>
+ <description>
+ This node was replaced by [VisualShaderNodeFrame] and only exists to preserve compatibility. In the [VisualShader] editor it behaves exactly like [VisualShaderNodeFrame].
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="description" type="String" setter="set_description" getter="get_description" default="&quot;&quot;">
+ This property only exists to preserve data authored in earlier versions of Godot. It has currently no function.
+ </member>
+ </members>
+</class>
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index de5f9ecf89..f57e9cb5f8 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -47,6 +47,7 @@
#include "scene/gui/check_box.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
+#include "scene/gui/margin_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/spin_box.h"
@@ -872,7 +873,13 @@ ConnectDialog::~ConnectDialog() {
Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
// If it's not a doc tooltip, fallback to the default one.
- return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text));
+ if (p_text.contains("::")) {
+ return nullptr;
+ }
+
+ EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
+ EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ConnectionsDockTree *>(this));
+ return memnew(Control); // Make the standard tooltip invisible.
}
struct _ConnectionsDockMethodInfoSort {
@@ -1458,8 +1465,8 @@ void ConnectionsDock::update_tree() {
section_item = tree->create_item(root);
section_item->set_text(0, class_name);
- // `|` separators used in `EditorHelpTooltip` for formatting.
- section_item->set_tooltip_text(0, "class|" + doc_class_name + "||");
+ // `|` separators used in `EditorHelpBit`.
+ section_item->set_tooltip_text(0, "class|" + doc_class_name + "|");
section_item->set_icon(0, class_icon);
section_item->set_selectable(0, false);
section_item->set_editable(0, false);
@@ -1490,8 +1497,8 @@ void ConnectionsDock::update_tree() {
sinfo["args"] = argnames;
signal_item->set_metadata(0, sinfo);
signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal")));
- // `|` separators used in `EditorHelpTooltip` for formatting.
- signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name));
+ // `|` separators used in `EditorHelpBit`.
+ signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name));
// List existing connections.
List<Object::Connection> existing_connections;
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index f644d3335a..78f1b76e23 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -191,7 +191,7 @@ public:
//////////////////////////////////////////
-// Custom `Tree` needed to use `EditorHelpTooltip` to display signal documentation.
+// Custom `Tree` needed to use `EditorHelpBit` to display signal documentation.
class ConnectionsDockTree : public Tree {
virtual Control *make_custom_tooltip(const String &p_text) const;
};
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index f7914d3aaa..b00f059b36 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -202,8 +202,7 @@ void CreateDialog::_update_search() {
select_type(_top_result(candidates, search_text));
} else {
favorite->set_disabled(true);
- help_bit->set_text(vformat(TTR("No results for \"%s\"."), search_text));
- help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
+ help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]")));
get_ok_button()->set_disabled(true);
search_options->deselect_all();
}
@@ -502,17 +501,7 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
to_select->select(0);
search_options->scroll_to_item(to_select, p_center_on_item);
- String text = help_bit->get_class_description(p_type);
- if (!text.is_empty()) {
- // Display both class name and description, since the help bit may be displayed
- // far away from the location (especially if the dialog was resized to be taller).
- help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text));
- help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- } else {
- // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
- help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", p_type)));
- help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
- }
+ help_bit->parse_symbol("class|" + p_type + "|");
favorite->set_disabled(false);
favorite->set_pressed(favorite_list.has(p_type));
@@ -837,6 +826,7 @@ CreateDialog::CreateDialog() {
vbc->add_margin_child(TTR("Matches:"), search_options, true);
help_bit = memnew(EditorHelpBit);
+ help_bit->set_content_height_limits(64 * EDSCALE, 64 * EDSCALE);
help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested));
vbc->add_margin_child(TTR("Description:"), help_bit);
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index c6ecaba230..c1db674cbe 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -593,6 +593,10 @@ void EditorBuildProfileManager::_action_confirm() {
}
}
+void EditorBuildProfileManager::_hide_requested() {
+ _cancel_pressed(); // From AcceptDialog.
+}
+
void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
TreeItem *class_item = class_list->create_item(p_parent);
class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
@@ -646,21 +650,10 @@ void EditorBuildProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String text = description_bit->get_class_description(md);
- if (!text.is_empty()) {
- // Display both class name and description, since the help bit may be displayed
- // far away from the location (especially if the dialog was resized to be taller).
- description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- } else {
- // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
- description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
- }
+ description_bit->parse_symbol("class|" + md.operator String() + "|");
} else if (md.get_type() == Variant::INT) {
String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
- description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description)));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(build_option_description));
}
}
@@ -864,7 +857,8 @@ EditorBuildProfileManager::EditorBuildProfileManager() {
main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true);
description_bit = memnew(EditorHelpBit);
- description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
+ description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
+ description_bit->connect("request_hide", callable_mp(this, &EditorBuildProfileManager::_hide_requested));
main_vbc->add_margin_child(TTR("Description:"), description_bit, false);
confirm_dialog = memnew(ConfirmationDialog);
diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h
index 649784afc3..a947365c7f 100644
--- a/editor/editor_build_profile.h
+++ b/editor/editor_build_profile.h
@@ -150,6 +150,7 @@ class EditorBuildProfileManager : public AcceptDialog {
void _profile_action(int p_action);
void _action_confirm();
+ void _hide_requested();
void _update_edited_profile();
void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected);
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 541bcd5e02..86b7b3eb2f 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -493,6 +493,10 @@ void EditorFeatureProfileManager::_profile_selected(int p_what) {
_update_selected_profile();
}
+void EditorFeatureProfileManager::_hide_requested() {
+ _cancel_pressed(); // From AcceptDialog.
+}
+
void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
TreeItem *class_item = class_list->create_item(p_parent);
class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
@@ -555,22 +559,10 @@ void EditorFeatureProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String text = description_bit->get_class_description(md);
- if (!text.is_empty()) {
- // Display both class name and description, since the help bit may be displayed
- // far away from the location (especially if the dialog was resized to be taller).
- description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- } else {
- // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
- description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
- }
+ description_bit->parse_symbol("class|" + md.operator String() + "|");
} else if (md.get_type() == Variant::INT) {
String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));
- description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description)));
- description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
-
+ description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(feature_description));
return;
} else {
return;
@@ -991,8 +983,9 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
description_bit = memnew(EditorHelpBit);
+ description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
+ description_bit->connect("request_hide", callable_mp(this, &EditorFeatureProfileManager::_hide_requested));
property_list_vbc->add_margin_child(TTR("Description:"), description_bit, false);
- description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
property_list = memnew(Tree);
property_list_vbc->add_margin_child(TTR("Extra Options:"), property_list, true);
diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h
index 25ee1c9ba4..2fa6ae9813 100644
--- a/editor/editor_feature_profile.h
+++ b/editor/editor_feature_profile.h
@@ -142,6 +142,7 @@ class EditorFeatureProfileManager : public AcceptDialog {
void _profile_action(int p_action);
void _profile_selected(int p_what);
+ void _hide_requested();
String current_profile;
void _update_profile_list(const String &p_select_profile = String());
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 1f7505633b..5cc09b7104 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -30,6 +30,7 @@
#include "editor_help.h"
+#include "core/config/project_settings.h"
#include "core/core_constants.h"
#include "core/extension/gdextension.h"
#include "core/input/input.h"
@@ -193,7 +194,6 @@ void EditorHelp::_update_theme_item_cache() {
class_desc->add_theme_font_override("normal_font", theme_cache.doc_font);
class_desc->add_theme_font_size_override("normal_font_size", theme_cache.doc_font_size);
- class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp")));
class_desc->add_theme_constant_override("line_separation", get_theme_constant(SNAME("line_separation"), SNAME("EditorHelp")));
class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp")));
class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp")));
@@ -222,29 +222,35 @@ void EditorHelp::_class_list_select(const String &p_select) {
}
void EditorHelp::_class_desc_select(const String &p_select) {
- if (p_select.begins_with("$")) { // enum
- String select = p_select.substr(1, p_select.length());
- String class_name;
- int rfind = select.rfind(".");
- if (rfind != -1) {
- class_name = select.substr(0, rfind);
- select = select.substr(rfind + 1);
+ if (p_select.begins_with("$")) { // Enum.
+ const String link = p_select.substr(1);
+
+ String enum_class_name;
+ String enum_name;
+ if (CoreConstants::is_global_enum(link)) {
+ enum_class_name = "@GlobalScope";
+ enum_name = link;
} else {
- class_name = "@GlobalScope";
+ const int dot_pos = link.rfind(".");
+ if (dot_pos >= 0) {
+ enum_class_name = link.left(dot_pos);
+ enum_name = link.substr(dot_pos + 1);
+ } else {
+ enum_class_name = edited_class;
+ enum_name = link;
+ }
}
- emit_signal(SNAME("go_to_help"), "class_enum:" + class_name + ":" + select);
- return;
- } else if (p_select.begins_with("#")) {
- emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1, p_select.length()));
- return;
- } else if (p_select.begins_with("@")) {
- int tag_end = p_select.find_char(' ');
- String tag = p_select.substr(1, tag_end - 1);
- String link = p_select.substr(tag_end + 1, p_select.length()).lstrip(" ");
+ emit_signal(SNAME("go_to_help"), "class_enum:" + enum_class_name + ":" + enum_name);
+ } else if (p_select.begins_with("#")) { // Class.
+ emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1));
+ } else if (p_select.begins_with("@")) { // Member.
+ const int tag_end = p_select.find_char(' ');
+ const String tag = p_select.substr(1, tag_end - 1);
+ const String link = p_select.substr(tag_end + 1).lstrip(" ");
String topic;
- HashMap<String, int> *table = nullptr;
+ const HashMap<String, int> *table = nullptr;
if (tag == "method") {
topic = "class_method";
@@ -311,14 +317,14 @@ void EditorHelp::_class_desc_select(const String &p_select) {
}
if (link.contains(".")) {
- int class_end = link.find_char('.');
- emit_signal(SNAME("go_to_help"), topic + ":" + link.substr(0, class_end) + ":" + link.substr(class_end + 1, link.length()));
+ const int class_end = link.find_char('.');
+ emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
}
}
- } else if (p_select.begins_with("http")) {
+ } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
OS::get_singleton()->shell_open(p_select);
- } else if (p_select.begins_with("^")) {
- DisplayServer::get_singleton()->clipboard_set(p_select.trim_prefix("^"));
+ } else if (p_select.begins_with("^")) { // Copy button.
+ DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
}
}
@@ -341,13 +347,15 @@ void EditorHelp::_class_desc_resized(bool p_force_update_theme) {
}
}
-void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) {
+static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_is_bitfield, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
+ const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
+
if (p_type.is_empty() || p_type == "void") {
- class_desc->push_color(Color(theme_cache.type_color, 0.5));
- class_desc->push_hint(TTR("No return value."));
- class_desc->add_text("void");
- class_desc->pop(); // hint
- class_desc->pop(); // color
+ p_rt->push_color(Color(type_color, 0.5));
+ p_rt->push_hint(TTR("No return value."));
+ p_rt->add_text("void");
+ p_rt->pop(); // hint
+ p_rt->pop(); // color
return;
}
@@ -359,12 +367,12 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is
String display_t; // For display purposes.
if (is_enum_type) {
link_t = p_enum; // The link for enums is always the full enum description
- display_t = _contextualize_class_specifier(p_enum, edited_class);
+ display_t = _contextualize_class_specifier(p_enum, p_class);
} else {
- display_t = _contextualize_class_specifier(p_type, edited_class);
+ display_t = _contextualize_class_specifier(p_type, p_class);
}
- class_desc->push_color(theme_cache.type_color);
+ p_rt->push_color(type_color);
bool add_array = false;
if (can_ref) {
if (link_t.ends_with("[]")) {
@@ -372,37 +380,41 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is
link_t = link_t.trim_suffix("[]");
display_t = display_t.trim_suffix("[]");
- class_desc->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
- class_desc->add_text("Array");
- class_desc->pop(); // meta
- class_desc->add_text("[");
+ p_rt->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->add_text("Array");
+ p_rt->pop(); // meta
+ p_rt->add_text("[");
} else if (is_bitfield) {
- class_desc->push_color(Color(theme_cache.type_color, 0.5));
- class_desc->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
- class_desc->add_text("BitField");
- class_desc->pop(); // hint
- class_desc->add_text("[");
- class_desc->pop(); // color
+ p_rt->push_color(Color(type_color, 0.5));
+ p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
+ p_rt->add_text("BitField");
+ p_rt->pop(); // hint
+ p_rt->add_text("[");
+ p_rt->pop(); // color
}
if (is_enum_type) {
- class_desc->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum
+ p_rt->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum
} else {
- class_desc->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class
}
}
- class_desc->add_text(display_t);
+ p_rt->add_text(display_t);
if (can_ref) {
- class_desc->pop(); // meta
+ p_rt->pop(); // meta
if (add_array) {
- class_desc->add_text("]");
+ p_rt->add_text("]");
} else if (is_bitfield) {
- class_desc->push_color(Color(theme_cache.type_color, 0.5));
- class_desc->add_text("]");
- class_desc->pop(); // color
+ p_rt->push_color(Color(type_color, 0.5));
+ p_rt->add_text("]");
+ p_rt->pop(); // color
}
}
- class_desc->pop(); // color
+ p_rt->pop(); // color
+}
+
+void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) {
+ _add_type_to_rt(p_type, p_enum, p_is_bitfield, class_desc, this, edited_class);
}
void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) {
@@ -717,10 +729,10 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD
String group_prefix;
for (int i = 0; i < m.size(); i++) {
- const String new_prefix = m[i].name.substr(0, 3);
+ const String new_prefix = m[i].name.left(3);
bool is_new_group = false;
- if (i < m.size() - 1 && new_prefix == m[i + 1].name.substr(0, 3) && new_prefix != group_prefix) {
+ if (i < m.size() - 1 && new_prefix == m[i + 1].name.left(3) && new_prefix != group_prefix) {
is_new_group = i > 0;
group_prefix = new_prefix;
} else if (!group_prefix.is_empty() && new_prefix != group_prefix) {
@@ -748,7 +760,7 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD
}
void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) {
-#define DTR_DOC(m_string) (p_classdoc.is_script_doc ? (m_string) : DTR(m_string))
+#define HANDLE_DOC(m_string) ((p_classdoc.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
class_desc->add_newline();
class_desc->add_newline();
@@ -807,7 +819,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc
TTRC("This constructor may be changed or removed in future versions."),
TTRC("This operator may be changed or removed in future versions."),
};
- DEPRECATED_DOC_MSG(DTR_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type]));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type]));
}
if (method.is_experimental) {
@@ -822,7 +834,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc
TTRC("This constructor may be changed or removed in future versions."),
TTRC("This operator may be changed or removed in future versions."),
};
- EXPERIMENTAL_DOC_MSG(DTR_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type]));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type]));
}
if (!method.errors_returned.is_empty()) {
@@ -856,7 +868,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc
class_desc->pop(); // list
}
- const String descr = DTR_DOC(method.description).strip_edges();
+ const String descr = HANDLE_DOC(method.description);
const bool is_documented = method.is_deprecated || method.is_experimental || !descr.is_empty();
if (!descr.is_empty()) {
if (has_prev_text) {
@@ -903,7 +915,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc
}
}
-#undef DTR_DOC
+#undef HANDLE_DOC
}
void EditorHelp::_update_doc() {
@@ -922,7 +934,7 @@ void EditorHelp::_update_doc() {
DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying.
-#define DTR_DOC(m_string) (cd.is_script_doc ? (m_string) : DTR(m_string))
+#define HANDLE_DOC(m_string) ((cd.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
// Class name
@@ -940,12 +952,12 @@ void EditorHelp::_update_doc() {
if (cd.is_deprecated) {
class_desc->add_newline();
- DEPRECATED_DOC_MSG(DTR_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions."));
}
if (cd.is_experimental) {
class_desc->add_newline();
- EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions."));
}
// Inheritance tree
@@ -1003,7 +1015,7 @@ void EditorHelp::_update_doc() {
bool has_description = false;
// Brief description
- const String brief_class_descr = DTR_DOC(cd.brief_description).strip_edges();
+ const String brief_class_descr = HANDLE_DOC(cd.brief_description);
if (!brief_class_descr.is_empty()) {
has_description = true;
@@ -1022,7 +1034,7 @@ void EditorHelp::_update_doc() {
}
// Class description
- const String class_descr = DTR_DOC(cd.description).strip_edges();
+ const String class_descr = HANDLE_DOC(cd.description);
if (!class_descr.is_empty()) {
has_description = true;
@@ -1106,9 +1118,9 @@ void EditorHelp::_update_doc() {
class_desc->push_color(theme_cache.symbol_color);
for (const DocData::TutorialDoc &tutorial : cd.tutorials) {
- const String link = DTR_DOC(tutorial.link).strip_edges();
+ const String link = HANDLE_DOC(tutorial.link);
- String link_text = DTR_DOC(tutorial.title).strip_edges();
+ String link_text = HANDLE_DOC(tutorial.title);
if (link_text.is_empty()) {
const int sep_pos = link.find("//");
if (sep_pos >= 0) {
@@ -1441,7 +1453,7 @@ void EditorHelp::_update_doc() {
_push_normal_font();
class_desc->push_color(theme_cache.comment_color);
- const String descr = DTR_DOC(theme_item.description).strip_edges();
+ const String descr = HANDLE_DOC(theme_item.description);
if (!descr.is_empty()) {
_add_text(descr);
} else {
@@ -1538,13 +1550,13 @@ void EditorHelp::_update_doc() {
_push_normal_font();
class_desc->push_color(theme_cache.comment_color);
- const String descr = DTR_DOC(signal.description).strip_edges();
+ const String descr = HANDLE_DOC(signal.description);
const bool is_multiline = descr.find_char('\n') > 0;
bool has_prev_text = false;
if (signal.is_deprecated) {
has_prev_text = true;
- DEPRECATED_DOC_MSG(DTR_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions."));
}
if (signal.is_experimental) {
@@ -1555,7 +1567,7 @@ void EditorHelp::_update_doc() {
}
}
has_prev_text = true;
- EXPERIMENTAL_DOC_MSG(DTR_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions."));
}
if (!descr.is_empty()) {
@@ -1669,7 +1681,7 @@ void EditorHelp::_update_doc() {
// Enum description.
if (key != "@unnamed_enums" && cd.enums.has(key)) {
- const String descr = DTR_DOC(cd.enums[key].description).strip_edges();
+ const String descr = HANDLE_DOC(cd.enums[key].description);
const bool is_multiline = descr.find_char('\n') > 0;
if (cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !descr.is_empty()) {
class_desc->add_newline();
@@ -1682,7 +1694,7 @@ void EditorHelp::_update_doc() {
if (cd.enums[key].is_deprecated) {
has_prev_text = true;
- DEPRECATED_DOC_MSG(DTR_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions."));
}
if (cd.enums[key].is_experimental) {
@@ -1693,7 +1705,7 @@ void EditorHelp::_update_doc() {
}
}
has_prev_text = true;
- EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions."));
}
if (!descr.is_empty()) {
@@ -1718,7 +1730,7 @@ void EditorHelp::_update_doc() {
bool prev_is_multiline = true; // Use a large margin for the first item.
for (const DocData::ConstantDoc &enum_value : E.value) {
- const String descr = DTR_DOC(enum_value.description).strip_edges();
+ const String descr = HANDLE_DOC(enum_value.description);
const bool is_multiline = descr.find_char('\n') > 0;
class_desc->add_newline();
@@ -1766,7 +1778,7 @@ void EditorHelp::_update_doc() {
if (enum_value.is_deprecated) {
has_prev_text = true;
- DEPRECATED_DOC_MSG(DTR_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions."));
}
if (enum_value.is_experimental) {
@@ -1777,7 +1789,7 @@ void EditorHelp::_update_doc() {
}
}
has_prev_text = true;
- EXPERIMENTAL_DOC_MSG(DTR_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions."));
}
if (!descr.is_empty()) {
@@ -1817,7 +1829,7 @@ void EditorHelp::_update_doc() {
bool prev_is_multiline = true; // Use a large margin for the first item.
for (const DocData::ConstantDoc &constant : constants) {
- const String descr = DTR_DOC(constant.description).strip_edges();
+ const String descr = HANDLE_DOC(constant.description);
const bool is_multiline = descr.find_char('\n') > 0;
class_desc->add_newline();
@@ -1871,7 +1883,7 @@ void EditorHelp::_update_doc() {
if (constant.is_deprecated) {
has_prev_text = true;
- DEPRECATED_DOC_MSG(DTR_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions."));
}
if (constant.is_experimental) {
@@ -1882,7 +1894,7 @@ void EditorHelp::_update_doc() {
}
}
has_prev_text = true;
- EXPERIMENTAL_DOC_MSG(DTR_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions."));
}
if (!descr.is_empty()) {
@@ -2000,7 +2012,7 @@ void EditorHelp::_update_doc() {
_push_normal_font();
class_desc->push_color(theme_cache.comment_color);
- const String descr = DTR_DOC(annotation.description).strip_edges();
+ const String descr = HANDLE_DOC(annotation.description);
if (!descr.is_empty()) {
_add_text(descr);
} else {
@@ -2185,7 +2197,7 @@ void EditorHelp::_update_doc() {
if (prop.is_deprecated) {
has_prev_text = true;
- DEPRECATED_DOC_MSG(DTR_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions."));
+ DEPRECATED_DOC_MSG(HANDLE_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions."));
}
if (prop.is_experimental) {
@@ -2194,10 +2206,10 @@ void EditorHelp::_update_doc() {
class_desc->add_newline();
}
has_prev_text = true;
- EXPERIMENTAL_DOC_MSG(DTR_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions."));
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions."));
}
- const String descr = DTR_DOC(prop.description).strip_edges();
+ const String descr = HANDLE_DOC(prop.description);
if (!descr.is_empty()) {
if (has_prev_text) {
class_desc->add_newline();
@@ -2251,7 +2263,7 @@ void EditorHelp::_update_doc() {
// Free the scroll.
scroll_locked = false;
-#undef DTR_DOC
+#undef HANDLE_DOC
}
void EditorHelp::_request_help(const String &p_string) {
@@ -2331,7 +2343,7 @@ void EditorHelp::_help_callback(const String &p_topic) {
}
}
-static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class) {
+static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
const DocTools *doc = EditorHelp::get_doc_data();
bool is_native = false;
@@ -2452,7 +2464,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
if (tag.begins_with("/")) {
- bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length());
+ bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);
if (!tag_ok) {
p_rt->add_text("[");
@@ -2467,8 +2479,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
}
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
const int tag_end = tag.find_char(' ');
- const String link_tag = tag.substr(0, tag_end);
- const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
+ const String link_tag = tag.left(tag_end);
+ const String link_target = tag.substr(tag_end + 1).lstrip(" ");
Color target_color = link_color;
RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER;
@@ -2520,7 +2532,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
pos = brk_end + 1;
} else if (tag.begins_with("param ")) {
const int tag_end = tag.find_char(' ');
- const String param_name = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
+ const String param_name = tag.substr(tag_end + 1).lstrip(" ");
// Use monospace font with translucent background color to make code easier to distinguish from other text.
p_rt->push_font(doc_code_font);
@@ -2741,7 +2753,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("url=")) {
- String url = tag.substr(4, tag.length());
+ String url = tag.substr(4);
p_rt->push_meta(url);
pos = brk_end + 1;
@@ -2751,13 +2763,13 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
int height = 0;
bool size_in_percent = false;
if (tag.length() > 4) {
- Vector<String> subtags = tag.substr(4, tag.length()).split(" ");
+ Vector<String> subtags = tag.substr(4).split(" ");
HashMap<String, String> bbcode_options;
for (int i = 0; i < subtags.size(); i++) {
const String &expr = subtags[i];
int value_pos = expr.find_char('=');
if (value_pos > -1) {
- bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote();
+ bbcode_options[expr.left(value_pos)] = expr.substr(value_pos + 1).unquote();
}
}
HashMap<String, String>::Iterator width_option = bbcode_options.find("width");
@@ -2787,14 +2799,14 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
pos = end;
tag_stack.push_front("img");
} else if (tag.begins_with("color=")) {
- String col = tag.substr(6, tag.length());
+ String col = tag.substr(6);
Color color = Color::from_string(col, Color());
p_rt->push_color(color);
pos = brk_end + 1;
tag_stack.push_front("color");
} else if (tag.begins_with("font=")) {
- String font_path = tag.substr(5, tag.length());
+ String font_path = tag.substr(5);
Ref<Font> font = ResourceLoader::load(font_path, "Font");
if (font.is_valid()) {
p_rt->push_font(font);
@@ -3120,68 +3132,50 @@ DocTools *EditorHelp::get_doc_data() {
/// EditorHelpBit ///
-void EditorHelpBit::_go_to_help(const String &p_what) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
- ScriptEditor::get_singleton()->goto_help(p_what);
- emit_signal(SNAME("request_hide"));
-}
+#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
-void EditorHelpBit::_meta_clicked(const String &p_select) {
- if (p_select.begins_with("$")) { // enum
- String select = p_select.substr(1, p_select.length());
- String class_name;
- int rfind = select.rfind(".");
- if (rfind != -1) {
- class_name = select.substr(0, rfind);
- select = select.substr(rfind + 1);
- } else {
- class_name = "@GlobalScope";
- }
- _go_to_help("class_enum:" + class_name + ":" + select);
- return;
- } else if (p_select.begins_with("#")) {
- _go_to_help("class_name:" + p_select.substr(1, p_select.length()));
- return;
- } else if (p_select.begins_with("@")) {
- String m = p_select.substr(1, p_select.length());
-
- if (m.contains(".")) {
- _go_to_help("class_method:" + m.get_slice(".", 0) + ":" + m.get_slice(".", 0)); // Must go somewhere else.
- }
- }
-}
-
-String EditorHelpBit::get_class_description(const StringName &p_class_name) const {
+EditorHelpBit::HelpData EditorHelpBit::_get_class_help_data(const StringName &p_class_name) {
if (doc_class_cache.has(p_class_name)) {
return doc_class_cache[p_class_name];
}
- String description;
+ HelpData result;
const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
if (E) {
// Non-native class shouldn't be cached, nor translated.
const bool is_native = !E->value.is_script_doc;
- description = is_native ? DTR(E->value.brief_description) : E->value.brief_description;
+
+ result.description = HANDLE_DOC(E->value.brief_description);
+ if (E->value.is_deprecated) {
+ if (E->value.deprecated_message.is_empty()) {
+ result.deprecated_message = TTR("This class may be changed or removed in future versions.");
+ } else {
+ result.deprecated_message = HANDLE_DOC(E->value.deprecated_message);
+ }
+ }
+ if (E->value.is_experimental) {
+ if (E->value.experimental_message.is_empty()) {
+ result.experimental_message = TTR("This class may be changed or removed in future versions.");
+ } else {
+ result.experimental_message = HANDLE_DOC(E->value.experimental_message);
+ }
+ }
if (is_native) {
- doc_class_cache[p_class_name] = description;
+ doc_class_cache[p_class_name] = result;
}
}
- return description;
+ return result;
}
-String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const {
- if (!custom_description.is_empty()) {
- return custom_description;
- }
-
+EditorHelpBit::HelpData EditorHelpBit::_get_property_help_data(const StringName &p_class_name, const StringName &p_property_name) {
if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
return doc_property_cache[p_class_name][p_property_name];
}
- String description;
+ HelpData result;
const DocTools *dd = EditorHelp::get_doc_data();
const HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
@@ -3190,7 +3184,22 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c
const bool is_native = !E->value.is_script_doc;
for (const DocData::PropertyDoc &property : E->value.properties) {
- String description_current = is_native ? DTR(property.description) : property.description;
+ HelpData current;
+ current.description = HANDLE_DOC(property.description);
+ if (property.is_deprecated) {
+ if (property.deprecated_message.is_empty()) {
+ current.deprecated_message = TTR("This property may be changed or removed in future versions.");
+ } else {
+ current.deprecated_message = HANDLE_DOC(property.deprecated_message);
+ }
+ }
+ if (property.is_experimental) {
+ if (property.experimental_message.is_empty()) {
+ current.experimental_message = TTR("This property may be changed or removed in future versions.");
+ } else {
+ current.experimental_message = HANDLE_DOC(property.experimental_message);
+ }
+ }
String enum_class_name;
String enum_name;
@@ -3215,18 +3224,19 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c
if (constant.enumeration == enum_name && !constant.name.ends_with("_MAX")) {
// Prettify the enum value display, so that "<ENUM_NAME>_<ITEM>" becomes "Item".
const String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(constant.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED).trim_prefix(enum_prefix);
- String item_descr = (is_native ? DTR(constant.description) : constant.description).strip_edges();
+ String item_descr = HANDLE_DOC(constant.description);
if (item_descr.is_empty()) {
- item_descr = ("[i]" + DTR("No description available.") + "[/i]");
+ item_descr = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
}
- description_current += vformat("\n[b]%s:[/b] %s", item_name, item_descr);
+ current.description += vformat("\n[b]%s:[/b] %s", item_name, item_descr);
}
}
+ current.description = current.description.lstrip("\n");
}
}
if (property.name == p_property_name) {
- description = description_current;
+ result = current;
if (!is_native) {
break;
@@ -3234,20 +3244,20 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c
}
if (is_native) {
- doc_property_cache[p_class_name][property.name] = description_current;
+ doc_property_cache[p_class_name][property.name] = current;
}
}
}
- return description;
+ return result;
}
-String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const {
+EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p_class_name, const StringName &p_method_name) {
if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
return doc_method_cache[p_class_name][p_method_name];
}
- String description;
+ HelpData result;
const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
if (E) {
@@ -3255,10 +3265,30 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con
const bool is_native = !E->value.is_script_doc;
for (const DocData::MethodDoc &method : E->value.methods) {
- String description_current = is_native ? DTR(method.description) : method.description;
+ HelpData current;
+ current.description = HANDLE_DOC(method.description);
+ if (method.is_deprecated) {
+ if (method.deprecated_message.is_empty()) {
+ current.deprecated_message = TTR("This method may be changed or removed in future versions.");
+ } else {
+ current.deprecated_message = HANDLE_DOC(method.deprecated_message);
+ }
+ }
+ if (method.is_experimental) {
+ if (method.experimental_message.is_empty()) {
+ current.experimental_message = TTR("This method may be changed or removed in future versions.");
+ } else {
+ current.experimental_message = HANDLE_DOC(method.experimental_message);
+ }
+ }
+ current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield };
+ for (const DocData::ArgumentDoc &argument : method.arguments) {
+ const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
+ current.arguments.push_back({ argument.name, argument_type, argument.default_value });
+ }
if (method.name == p_method_name) {
- description = description_current;
+ result = current;
if (!is_native) {
break;
@@ -3266,20 +3296,20 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con
}
if (is_native) {
- doc_method_cache[p_class_name][method.name] = description_current;
+ doc_method_cache[p_class_name][method.name] = current;
}
}
}
- return description;
+ return result;
}
-String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const {
+EditorHelpBit::HelpData EditorHelpBit::_get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name) {
if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
return doc_signal_cache[p_class_name][p_signal_name];
}
- String description;
+ HelpData result;
const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
if (E) {
@@ -3287,10 +3317,29 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con
const bool is_native = !E->value.is_script_doc;
for (const DocData::MethodDoc &signal : E->value.signals) {
- String description_current = is_native ? DTR(signal.description) : signal.description;
+ HelpData current;
+ current.description = HANDLE_DOC(signal.description);
+ if (signal.is_deprecated) {
+ if (signal.deprecated_message.is_empty()) {
+ current.deprecated_message = TTR("This signal may be changed or removed in future versions.");
+ } else {
+ current.deprecated_message = HANDLE_DOC(signal.deprecated_message);
+ }
+ }
+ if (signal.is_experimental) {
+ if (signal.experimental_message.is_empty()) {
+ current.experimental_message = TTR("This signal may be changed or removed in future versions.");
+ } else {
+ current.experimental_message = HANDLE_DOC(signal.experimental_message);
+ }
+ }
+ for (const DocData::ArgumentDoc &argument : signal.arguments) {
+ const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
+ current.arguments.push_back({ argument.name, argument_type, argument.default_value });
+ }
if (signal.name == p_signal_name) {
- description = description_current;
+ result = current;
if (!is_native) {
break;
@@ -3298,20 +3347,20 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con
}
if (is_native) {
- doc_signal_cache[p_class_name][signal.name] = description_current;
+ doc_signal_cache[p_class_name][signal.name] = current;
}
}
}
- return description;
+ return result;
}
-String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const {
+EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name) {
if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
return doc_theme_item_cache[p_class_name][p_theme_item_name];
}
- String description;
+ HelpData result;
bool found = false;
const DocTools *dd = EditorHelp::get_doc_data();
@@ -3321,142 +3370,477 @@ String EditorHelpBit::get_theme_item_description(const StringName &p_class_name,
const bool is_native = !E->value.is_script_doc;
for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) {
- String description_current = is_native ? DTR(theme_item.description) : theme_item.description;
+ HelpData current;
+ current.description = HANDLE_DOC(theme_item.description);
if (theme_item.name == p_theme_item_name) {
- description = description_current;
+ result = current;
found = true;
-
if (!is_native) {
break;
}
}
if (is_native) {
- doc_theme_item_cache[p_class_name][theme_item.name] = description_current;
+ doc_theme_item_cache[p_class_name][theme_item.name] = current;
}
}
if (found || E->value.inherits.is_empty()) {
break;
}
+
// Check for inherited theme items.
E = dd->class_list.find(E->value.inherits);
}
- return description;
+ return result;
+}
+
+#undef HANDLE_DOC
+
+void EditorHelpBit::_add_type_to_title(const DocType &p_doc_type) {
+ _add_type_to_rt(p_doc_type.type, p_doc_type.enumeration, p_doc_type.is_bitfield, title, this, symbol_class_name);
+}
+
+void EditorHelpBit::_update_labels() {
+ const Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
+
+ if (!symbol_visible_type.is_empty() || !symbol_name.is_empty()) {
+ title->clear();
+
+ title->push_font(doc_bold_font);
+
+ if (!symbol_visible_type.is_empty()) {
+ title->push_color(get_theme_color(SNAME("title_color"), SNAME("EditorHelp")));
+ title->add_text(symbol_visible_type);
+ title->pop(); // color
+ }
+
+ if (!symbol_visible_type.is_empty() && !symbol_name.is_empty()) {
+ title->add_text(" ");
+ }
+
+ if (!symbol_name.is_empty()) {
+ title->push_underline();
+ title->add_text(symbol_name);
+ title->pop(); // underline
+ }
+
+ title->pop(); // font
+
+ if (symbol_type == "method" || symbol_type == "signal") {
+ const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
+ const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
+
+ title->push_font(get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts)));
+ title->push_font_size(get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)) * 0.9);
+
+ title->push_color(symbol_color);
+ title->add_text("(");
+ title->pop(); // color
+
+ for (int i = 0; i < help_data.arguments.size(); i++) {
+ const ArgumentData &argument = help_data.arguments[i];
+
+ if (i > 0) {
+ title->push_color(symbol_color);
+ title->add_text(", ");
+ title->pop(); // color
+ }
+
+ title->add_text(argument.name);
+
+ title->push_color(symbol_color);
+ title->add_text(": ");
+ title->pop(); // color
+
+ _add_type_to_title(argument.doc_type);
+
+ if (!argument.default_value.is_empty()) {
+ title->push_color(symbol_color);
+ title->add_text(" = ");
+ title->pop(); // color
+
+ title->push_color(value_color);
+ title->add_text(argument.default_value);
+ title->pop(); // color
+ }
+ }
+
+ title->push_color(symbol_color);
+ title->add_text(")");
+ title->pop(); // color
+
+ if (symbol_type == "method") {
+ title->push_color(symbol_color);
+ title->add_text(" -> ");
+ title->pop(); // color
+
+ _add_type_to_title(help_data.doc_type);
+ }
+
+ title->pop(); // font_size
+ title->pop(); // font
+ }
+
+ title->show();
+ } else {
+ title->hide();
+ }
+
+ content->clear();
+
+ bool has_prev_text = false;
+
+ if (!help_data.deprecated_message.is_empty()) {
+ has_prev_text = true;
+
+ Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError"));
+ content->add_image(error_icon, error_icon->get_width(), error_icon->get_height());
+ content->add_text(" ");
+ content->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
+ content->push_font(doc_bold_font);
+ content->add_text(TTR("Deprecated:"));
+ content->pop(); // font
+ content->pop(); // color
+ content->add_text(" ");
+ _add_text_to_rt(help_data.deprecated_message, content, this, symbol_class_name);
+ }
+
+ if (!help_data.experimental_message.is_empty()) {
+ if (has_prev_text) {
+ content->add_newline();
+ content->add_newline();
+ }
+ has_prev_text = true;
+
+ Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning"));
+ content->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height());
+ content->add_text(" ");
+ content->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
+ content->push_font(doc_bold_font);
+ content->add_text(TTR("Experimental:"));
+ content->pop(); // font
+ content->pop(); // color
+ content->add_text(" ");
+ _add_text_to_rt(help_data.experimental_message, content, this, symbol_class_name);
+ }
+
+ if (!help_data.description.is_empty()) {
+ if (has_prev_text) {
+ content->add_newline();
+ content->add_newline();
+ }
+ has_prev_text = true;
+
+ const Color comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp"));
+ _add_text_to_rt(help_data.description.replace("<EditorHelpBitCommentColor>", comment_color.to_html()), content, this, symbol_class_name);
+ }
+
+ if (is_inside_tree()) {
+ update_content_height();
+ }
+}
+
+void EditorHelpBit::_go_to_help(const String &p_what) {
+ EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ ScriptEditor::get_singleton()->goto_help(p_what);
+ emit_signal(SNAME("request_hide"));
+}
+
+void EditorHelpBit::_meta_clicked(const String &p_select) {
+ if (p_select.begins_with("$")) { // Enum.
+ const String link = p_select.substr(1);
+
+ String enum_class_name;
+ String enum_name;
+ if (CoreConstants::is_global_enum(link)) {
+ enum_class_name = "@GlobalScope";
+ enum_name = link;
+ } else {
+ const int dot_pos = link.rfind(".");
+ if (dot_pos >= 0) {
+ enum_class_name = link.left(dot_pos);
+ enum_name = link.substr(dot_pos + 1);
+ } else {
+ enum_class_name = symbol_class_name;
+ enum_name = link;
+ }
+ }
+
+ _go_to_help("class_enum:" + enum_class_name + ":" + enum_name);
+ } else if (p_select.begins_with("#")) { // Class.
+ _go_to_help("class_name:" + p_select.substr(1));
+ } else if (p_select.begins_with("@")) { // Member.
+ const int tag_end = p_select.find_char(' ');
+ const String tag = p_select.substr(1, tag_end - 1);
+ const String link = p_select.substr(tag_end + 1).lstrip(" ");
+
+ String topic;
+ if (tag == "method") {
+ topic = "class_method";
+ } else if (tag == "constructor") {
+ topic = "class_method";
+ } else if (tag == "operator") {
+ topic = "class_method";
+ } else if (tag == "member") {
+ topic = "class_property";
+ } else if (tag == "enum") {
+ topic = "class_enum";
+ } else if (tag == "signal") {
+ topic = "class_signal";
+ } else if (tag == "constant") {
+ topic = "class_constant";
+ } else if (tag == "annotation") {
+ topic = "class_annotation";
+ } else if (tag == "theme_item") {
+ topic = "class_theme_item";
+ } else {
+ return;
+ }
+
+ if (link.contains(".")) {
+ const int class_end = link.find_char('.');
+ _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
+ } else {
+ _go_to_help(topic + ":" + symbol_class_name + ":" + link);
+ }
+ } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
+ OS::get_singleton()->shell_open(p_select);
+ } else if (p_select.begins_with("^")) { // Copy button.
+ DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
+ }
}
void EditorHelpBit::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text);
ADD_SIGNAL(MethodInfo("request_hide"));
}
void EditorHelpBit::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_THEME_CHANGED: {
- rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp")));
- rich_text->clear();
- _add_text_to_rt(text, rich_text, this, doc_class_name);
- rich_text->reset_size(); // Force recalculating size after parsing bbcode.
- } break;
+ case NOTIFICATION_THEME_CHANGED:
+ _update_labels();
+ break;
}
}
-void EditorHelpBit::set_text(const String &p_text) {
- text = p_text;
- rich_text->clear();
- _add_text_to_rt(text, rich_text, this, doc_class_name);
+void EditorHelpBit::parse_symbol(const String &p_symbol) {
+ const PackedStringArray slices = p_symbol.split("|", true, 2);
+ ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'.");
+
+ const String &item_type = slices[0];
+ const String &class_name = slices[1];
+ const String &item_name = slices[2];
+
+ String visible_type;
+ String name = item_name;
+
+ if (item_type == "class") {
+ visible_type = TTR("Class:");
+ name = class_name;
+ help_data = _get_class_help_data(class_name);
+ } else if (item_type == "property") {
+ if (name.begins_with("metadata/")) {
+ visible_type = TTR("Metadata:");
+ name = name.trim_prefix("metadata/");
+ } else if (class_name == "ProjectSettings" || class_name == "EditorSettings") {
+ visible_type = TTR("Setting:");
+ } else {
+ visible_type = TTR("Property:");
+ }
+ help_data = _get_property_help_data(class_name, item_name);
+ } else if (item_type == "internal_property") {
+ visible_type = TTR("Internal Property:");
+ help_data = HelpData();
+ help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This property can only be set in the Inspector.") + "[/i][/color]";
+ } else if (item_type == "method") {
+ visible_type = TTR("Method:");
+ help_data = _get_method_help_data(class_name, item_name);
+ } else if (item_type == "signal") {
+ visible_type = TTR("Signal:");
+ help_data = _get_signal_help_data(class_name, item_name);
+ } else if (item_type == "theme_item") {
+ visible_type = TTR("Theme Property:");
+ help_data = _get_theme_item_help_data(class_name, item_name);
+ } else {
+ ERR_FAIL_MSG("Invalid tooltip type '" + item_type + "'. Valid types are 'class', 'property', 'internal_property', 'method', 'signal', and 'theme_item'.");
+ }
+
+ symbol_class_name = class_name;
+ symbol_type = item_type;
+ symbol_visible_type = visible_type;
+ symbol_name = name;
+
+ if (help_data.description.is_empty()) {
+ help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
+ }
+
+ if (is_inside_tree()) {
+ _update_labels();
+ }
}
-EditorHelpBit::EditorHelpBit() {
- rich_text = memnew(RichTextLabel);
- add_child(rich_text);
- rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
- rich_text->set_fit_content(true);
- set_custom_minimum_size(Size2(0, 50 * EDSCALE));
+void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, const String &p_description) {
+ symbol_class_name = String();
+ symbol_type = String();
+ symbol_visible_type = p_type;
+ symbol_name = p_name;
+
+ help_data = HelpData();
+ help_data.description = p_description;
+
+ if (is_inside_tree()) {
+ _update_labels();
+ }
}
-/// EditorHelpTooltip ///
+void EditorHelpBit::prepend_description(const String &p_text) {
+ if (help_data.description.is_empty()) {
+ help_data.description = p_text;
+ } else {
+ help_data.description = p_text + "\n" + help_data.description;
+ }
-void EditorHelpTooltip::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_POSTINITIALIZE: {
- if (!tooltip_text.is_empty()) {
- parse_tooltip(tooltip_text);
- }
- } break;
+ if (is_inside_tree()) {
+ _update_labels();
}
}
-// `p_text` is expected to be something like these:
-// - `class|Control||`;
-// - `property|Control|size|`;
-// - `signal|Control|gui_input|(event: InputEvent)`.
-void EditorHelpTooltip::parse_tooltip(const String &p_text) {
- tooltip_text = p_text;
+void EditorHelpBit::set_content_height_limits(float p_min, float p_max) {
+ ERR_FAIL_COND(p_min > p_max);
+ content_min_height = p_min;
+ content_max_height = p_max;
- PackedStringArray slices = p_text.split("|", true, 3);
- ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'.");
+ if (is_inside_tree()) {
+ update_content_height();
+ }
+}
- const String &type = slices[0];
- const String &class_name = slices[1];
- const String &property_name = slices[2];
- const String &property_args = slices[3];
+void EditorHelpBit::update_content_height() {
+ float content_height = content->get_content_height();
+ const Ref<StyleBox> style = content->get_theme_stylebox("normal");
+ if (style.is_valid()) {
+ content_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM);
+ }
+ content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height)));
+}
- doc_class_name = class_name;
+EditorHelpBit::EditorHelpBit(const String &p_symbol) {
+ add_theme_constant_override("separation", 0);
+
+ title = memnew(RichTextLabel);
+ title->set_theme_type_variation("EditorHelpBitTitle");
+ title->set_fit_content(true);
+ title->set_selection_enabled(true);
+ //title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
+ title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
+ title->hide();
+ add_child(title);
+
+ content_min_height = 48 * EDSCALE;
+ content_max_height = 360 * EDSCALE;
+
+ content = memnew(RichTextLabel);
+ content->set_theme_type_variation("EditorHelpBitContent");
+ content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height));
+ content->set_selection_enabled(true);
+ //content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
+ content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
+ add_child(content);
+
+ if (!p_symbol.is_empty()) {
+ parse_symbol(p_symbol);
+ }
+}
- String formatted_text;
+/// EditorHelpBitTooltip ///
- // Exclude internal properties, they are not documented.
- if (type == "internal_property") {
- formatted_text = "[i]" + TTR("This property can only be set in the Inspector.") + "[/i]";
- set_text(formatted_text);
- return;
+void EditorHelpBitTooltip::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_WM_MOUSE_ENTER:
+ timer->stop();
+ break;
+ case NOTIFICATION_WM_MOUSE_EXIT:
+ timer->start();
+ break;
}
+}
- String title;
- String description;
+// Forwards non-mouse input to the parent viewport.
+void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) {
+ if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+ hide(); // Will be deleted on its timer.
+ } else {
+ const Ref<InputEventMouse> mouse_event = p_event;
+ if (mouse_event.is_null()) {
+ get_parent_viewport()->push_input(p_event);
+ }
+ }
+}
+
+void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) {
+ ERR_FAIL_NULL(p_help_bit);
+ EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
+ p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer.
+ tooltip->add_child(p_help_bit);
+ p_target->get_viewport()->add_child(tooltip);
+ p_help_bit->update_content_height();
+ tooltip->popup_under_cursor();
+}
- if (type == "class") {
- title = class_name;
- description = get_class_description(class_name);
- formatted_text = TTR("Class:");
+// Copy-paste from `Viewport::_gui_show_tooltip()`.
+void EditorHelpBitTooltip::popup_under_cursor() {
+ Point2 mouse_pos = get_mouse_position();
+ Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset");
+ Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size());
+ r.size = r.size.min(get_max_size());
+
+ Window *window = get_parent_visible_window();
+ Rect2i vr;
+ if (is_embedded()) {
+ vr = get_embedder()->get_visible_rect();
} else {
- title = property_name;
+ vr = window->get_usable_parent_rect();
+ }
- if (type == "property") {
- description = get_property_description(class_name, property_name);
- if (property_name.begins_with("metadata/")) {
- formatted_text = TTR("Metadata:");
- } else {
- formatted_text = TTR("Property:");
- }
- } else if (type == "method") {
- description = get_method_description(class_name, property_name);
- formatted_text = TTR("Method:");
- } else if (type == "signal") {
- description = get_signal_description(class_name, property_name);
- formatted_text = TTR("Signal:");
- } else if (type == "theme_item") {
- description = get_theme_item_description(class_name, property_name);
- formatted_text = TTR("Theme Property:");
- } else {
- ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'.");
+ 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.
+ r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x;
+
+ if (r.position.x < vr.position.x) {
+ r.position.x = vr.position.x + vr.size.x - r.size.x;
+ }
+ } else if (r.position.x < vr.position.x) {
+ r.position.x = vr.position.x;
+ }
+
+ if (r.size.y + r.position.y > vr.size.y + vr.position.y) {
+ // Same as above.
+ r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y;
+
+ if (r.position.y < vr.position.y) {
+ r.position.y = vr.position.y + vr.size.y - r.size.y;
}
+ } else if (r.position.y < vr.position.y) {
+ r.position.y = vr.position.y;
}
- // Metadata special handling replaces "Property:" with "Metadata": above.
- formatted_text += " [u][b]" + title.trim_prefix("metadata/") + "[/b][/u]" + property_args.replace("[", "[lb]") + "\n";
- formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description;
- set_text(formatted_text);
+ set_flag(Window::FLAG_NO_FOCUS, true);
+ popup(r);
}
-EditorHelpTooltip::EditorHelpTooltip(const String &p_text, const String &p_custom_description) {
- tooltip_text = p_text;
- custom_description = p_custom_description;
+EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
+ set_theme_type_variation("TooltipPanel");
+
+ timer = memnew(Timer);
+ timer->set_wait_time(0.2);
+ timer->connect("timeout", callable_mp(static_cast<Node *>(this), &Node::queue_free));
+ add_child(timer);
- get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
+ ERR_FAIL_NULL(p_target);
+ p_target->connect("mouse_entered", callable_mp(timer, &Timer::stop));
+ p_target->connect("mouse_exited", callable_mp(timer, &Timer::start).bind(-1));
}
#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
diff --git a/editor/editor_help.h b/editor/editor_help.h
index f8686b964a..078b42b439 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -35,9 +35,9 @@
#include "editor/code_editor.h"
#include "editor/doc_tools.h"
#include "editor/editor_plugin.h"
-#include "scene/gui/margin_container.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/popup.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/split_container.h"
#include "scene/gui/tab_container.h"
@@ -251,53 +251,91 @@ public:
~EditorHelp();
};
-class EditorHelpBit : public MarginContainer {
- GDCLASS(EditorHelpBit, MarginContainer);
+class EditorHelpBit : public VBoxContainer {
+ GDCLASS(EditorHelpBit, VBoxContainer);
- inline static HashMap<StringName, String> doc_class_cache;
- inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache;
- inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache;
- inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache;
- inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache;
+ struct DocType {
+ String type;
+ String enumeration;
+ bool is_bitfield = false;
+ };
+
+ struct ArgumentData {
+ String name;
+ DocType doc_type;
+ String default_value;
+ };
+
+ struct HelpData {
+ String description;
+ String deprecated_message;
+ String experimental_message;
+ DocType doc_type; // For method return type.
+ Vector<ArgumentData> arguments; // For methods and signals.
+ };
+
+ inline static HashMap<StringName, HelpData> doc_class_cache;
+ inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache;
+ inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache;
+ inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache;
+ inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache;
+
+ RichTextLabel *title = nullptr;
+ RichTextLabel *content = nullptr;
- RichTextLabel *rich_text = nullptr;
+ String symbol_class_name;
+ String symbol_type;
+ String symbol_visible_type;
+ String symbol_name;
+
+ HelpData help_data;
+
+ float content_min_height = 0.0;
+ float content_max_height = 0.0;
+
+ static HelpData _get_class_help_data(const StringName &p_class_name);
+ static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name);
+ static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name);
+ static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name);
+ static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name);
+
+ void _add_type_to_title(const DocType &p_doc_type);
+ void _update_labels();
void _go_to_help(const String &p_what);
void _meta_clicked(const String &p_select);
- String text;
-
protected:
- String doc_class_name;
- String custom_description;
-
static void _bind_methods();
void _notification(int p_what);
public:
- String get_class_description(const StringName &p_class_name) const;
- String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const;
- String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const;
- String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const;
- String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const;
+ void parse_symbol(const String &p_symbol);
+ void set_custom_text(const String &p_type, const String &p_name, const String &p_description);
+ void prepend_description(const String &p_text);
- RichTextLabel *get_rich_text() { return rich_text; }
- void set_text(const String &p_text);
+ void set_content_height_limits(float p_min, float p_max);
+ void update_content_height();
- EditorHelpBit();
+ EditorHelpBit(const String &p_symbol = String());
};
-class EditorHelpTooltip : public EditorHelpBit {
- GDCLASS(EditorHelpTooltip, EditorHelpBit);
+// Standard tooltips do not allow you to hover over them.
+// This class is intended as a temporary workaround.
+class EditorHelpBitTooltip : public PopupPanel {
+ GDCLASS(EditorHelpBitTooltip, PopupPanel);
- String tooltip_text;
+ Timer *timer = nullptr;
protected:
void _notification(int p_what);
+ virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
public:
- void parse_tooltip(const String &p_text);
+ static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target);
+
+ void popup_under_cursor();
- EditorHelpTooltip(const String &p_text = String(), const String &p_custom_description = String());
+ EditorHelpBitTooltip(Control *p_target);
};
#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index aed1462eb6..50cc89c618 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -44,6 +44,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
+#include "scene/gui/margin_container.h"
#include "scene/gui/spin_box.h"
#include "scene/gui/texture_rect.h"
#include "scene/property_utils.h"
@@ -916,34 +917,35 @@ void EditorProperty::_update_pin_flags() {
}
Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
- EditorHelpBit *tooltip = nullptr;
+ String custom_warning;
+ if (object->has_method("_get_property_warning")) {
+ custom_warning = object->call("_get_property_warning", property);
+ }
- if (has_doc_tooltip) {
- String custom_description;
+ if (has_doc_tooltip || !custom_warning.is_empty()) {
+ EditorHelpBit *help_bit = memnew(EditorHelpBit);
- const EditorInspector *inspector = get_parent_inspector();
- if (inspector) {
- custom_description = inspector->get_custom_property_description(p_text);
- }
- tooltip = memnew(EditorHelpTooltip(p_text, custom_description));
- }
+ if (has_doc_tooltip) {
+ help_bit->parse_symbol(p_text);
- if (object->has_method("_get_property_warning")) {
- String warn = object->call("_get_property_warning", property);
- if (!warn.is_empty()) {
- String prev_text;
- if (tooltip == nullptr) {
- tooltip = memnew(EditorHelpBit());
- tooltip->set_text(p_text);
- tooltip->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
- } else {
- prev_text = tooltip->get_rich_text()->get_text() + "\n";
+ const EditorInspector *inspector = get_parent_inspector();
+ if (inspector) {
+ const String custom_description = inspector->get_custom_property_description(p_text);
+ if (!custom_description.is_empty()) {
+ help_bit->prepend_description(custom_description);
+ }
}
- tooltip->set_text(prev_text + "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
}
+
+ if (!custom_warning.is_empty()) {
+ help_bit->prepend_description("[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]");
+ }
+
+ EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorProperty *>(this));
+ return memnew(Control); // Make the standard tooltip invisible.
}
- return tooltip;
+ return nullptr;
}
void EditorProperty::menu_option(int p_option) {
@@ -1178,7 +1180,14 @@ void EditorInspectorCategory::_notification(int p_what) {
}
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
- return doc_class_name.is_empty() ? nullptr : memnew(EditorHelpTooltip(p_text));
+ // If it's not a doc tooltip, fallback to the default one.
+ if (doc_class_name.is_empty()) {
+ return nullptr;
+ }
+
+ EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
+ EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorInspectorCategory *>(this));
+ return memnew(Control); // Make the standard tooltip invisible.
}
Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -2887,8 +2896,8 @@ void EditorInspector::update_tree() {
category->doc_class_name = doc_name;
if (use_doc_hints) {
- // `|` separator used in `EditorHelpTooltip` for formatting.
- category->set_tooltip_text("class|" + doc_name + "||");
+ // `|` separators used in `EditorHelpBit`.
+ category->set_tooltip_text("class|" + doc_name + "|");
}
}
@@ -3368,15 +3377,15 @@ void EditorInspector::update_tree() {
ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
if (use_doc_hints) {
- // `|` separator used in `EditorHelpTooltip` for formatting.
+ // `|` separators used in `EditorHelpBit`.
if (theme_item_name.is_empty()) {
if (p.usage & PROPERTY_USAGE_INTERNAL) {
- ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name + "|");
+ ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name);
} else {
- ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|");
+ ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name);
}
} else {
- ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|");
+ ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name);
}
ep->has_doc_tooltip = true;
}
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index eff4f9caa5..cf3bf89e09 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -41,6 +41,7 @@ class ConfirmationDialog;
class EditorInspector;
class EditorValidationPanel;
class LineEdit;
+class MarginContainer;
class OptionButton;
class PanelContainer;
class PopupMenu;
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index c59c221e02..da072744b8 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -40,6 +40,7 @@
#include "editor/inspector_dock.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
+#include "scene/gui/margin_container.h"
#include "scene/resources/packed_scene.h"
bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index dae0fc52a6..b00f85c93c 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -37,6 +37,7 @@
class Button;
class EditorSpinSlider;
+class MarginContainer;
class EditorPropertyArrayObject : public RefCounted {
GDCLASS(EditorPropertyArrayObject, RefCounted);
diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp
index 6fd1fd687b..f8df0f9fa8 100644
--- a/editor/editor_quick_open.cpp
+++ b/editor/editor_quick_open.cpp
@@ -91,17 +91,21 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
}
void EditorQuickOpen::_update_search() {
- const String search_text = search_box->get_text();
- const bool empty_search = search_text.is_empty();
+ const PackedStringArray search_tokens = search_box->get_text().to_lower().replace("/", " ").split(" ", false);
+ const bool empty_search = search_tokens.is_empty();
// Filter possible candidates.
Vector<Entry> entries;
for (int i = 0; i < files.size(); i++) {
- if (empty_search || search_text.is_subsequence_ofn(files[i])) {
- Entry r;
- r.path = files[i];
- r.score = empty_search ? 0 : _score_path(search_text, files[i].to_lower());
+ Entry r;
+ r.path = files[i];
+ if (empty_search) {
entries.push_back(r);
+ } else {
+ r.score = _score_search_result(search_tokens, r.path.to_lower());
+ if (r.score > 0) {
+ entries.push_back(r);
+ }
}
}
@@ -135,23 +139,42 @@ void EditorQuickOpen::_update_search() {
}
}
-float EditorQuickOpen::_score_path(const String &p_search, const String &p_path) {
- float score = 0.9f + .1f * (p_search.length() / (float)p_path.length());
+float EditorQuickOpen::_score_search_result(const PackedStringArray &p_search_tokens, const String &p_path) {
+ float score = 0.0f;
+ int prev_min_match_idx = -1;
- // Exact match.
- if (p_search == p_path) {
- return 1.2f;
- }
+ for (const String &s : p_search_tokens) {
+ int min_match_idx = p_path.find(s);
+
+ if (min_match_idx == -1) {
+ return 0.0f;
+ }
+
+ float token_score = s.length();
+
+ int max_match_idx = p_path.rfind(s);
+
+ // Prioritize the actual file name over folder.
+ if (max_match_idx > p_path.rfind("/")) {
+ token_score *= 2.0f;
+ }
+
+ // Prioritize matches at the front of the path token.
+ if (min_match_idx == 0 || p_path.find("/" + s) != -1) {
+ token_score += 1.0f;
+ }
+
+ score += token_score;
+
+ // Prioritize tokens which appear in order.
+ if (prev_min_match_idx != -1 && max_match_idx > prev_min_match_idx) {
+ score += 1.0f;
+ }
- // Positive bias for matches close to the beginning of the file name.
- String file = p_path.get_file();
- int pos = file.findn(p_search);
- if (pos != -1) {
- return score * (1.0f - 0.1f * (float(pos) / file.length()));
+ prev_min_match_idx = min_match_idx;
}
- // Similarity
- return p_path.to_lower().similarity(p_search.to_lower());
+ return score;
}
void EditorQuickOpen::_confirmed() {
diff --git a/editor/editor_quick_open.h b/editor/editor_quick_open.h
index ec8ce0175e..bbc689040a 100644
--- a/editor/editor_quick_open.h
+++ b/editor/editor_quick_open.h
@@ -63,7 +63,7 @@ class EditorQuickOpen : public ConfirmationDialog {
void _update_search();
void _build_search_cache(EditorFileSystemDirectory *p_efsd);
- float _score_path(const String &p_search, const String &p_path);
+ float _score_search_result(const PackedStringArray &p_search_tokens, const String &p_path);
void _confirmed();
virtual void cancel_pressed() override;
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 452715a577..ca6be130f9 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -690,6 +690,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
+ EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/aabb", Color(0.28, 0.8, 0.82), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
// If a line is a multiple of this, it uses the primary grid color.
// Use a power of 2 value by default as it's more common to use powers of 2 in level design.
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 2ed547a970..b7deb6afa6 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -59,8 +59,6 @@
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/progress_bar.h"
-#include "scene/gui/texture_rect.h"
-#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
#include "servers/display_server.h"
@@ -234,15 +232,15 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
Color custom_color = has_custom_color ? folder_colors[assigned_folder_colors[lpath]] : Color();
if (has_custom_color) {
- subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * 1.75);
- subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? 0.1 : 0.15));
+ subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * ITEM_COLOR_SCALE);
+ subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? ITEM_ALPHA_MIN : ITEM_ALPHA_MAX));
} else {
TreeItem *parent = subdirectory_item->get_parent();
if (parent) {
Color parent_bg_color = parent->get_custom_bg_color(0);
if (parent_bg_color != Color()) {
bool parent_has_custom_color = assigned_folder_colors.has(parent->get_metadata(0));
- subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(0.3) : parent_bg_color);
+ subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(ITEM_BG_DARK_SCALE) : parent_bg_color);
subdirectory_item->set_icon_modulate(0, parent->get_icon_modulate(0));
} else {
subdirectory_item->set_icon_modulate(0, get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")));
@@ -267,7 +265,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
} else {
subdirectory_item->set_collapsed(uncollapsed_paths.find(lpath) < 0);
}
- if (searched_string.length() > 0 && dname.to_lower().find(searched_string) >= 0) {
+ if (!searched_tokens.is_empty() && _matches_all_search_tokens(dname)) {
parent_should_expand = true;
}
@@ -293,8 +291,8 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
}
String file_name = p_dir->get_file(i);
- if (searched_string.length() > 0) {
- if (file_name.to_lower().find(searched_string) < 0) {
+ if (!searched_tokens.is_empty()) {
+ if (!_matches_all_search_tokens(file_name)) {
// The searched string is not in the file name, we skip it.
continue;
} else {
@@ -328,7 +326,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
file_item->set_icon_max_width(0, icon_size);
Color parent_bg_color = subdirectory_item->get_custom_bg_color(0);
if (has_custom_color) {
- file_item->set_custom_bg_color(0, parent_bg_color.darkened(0.3));
+ file_item->set_custom_bg_color(0, parent_bg_color.darkened(ITEM_BG_DARK_SCALE));
} else if (parent_bg_color != Color()) {
file_item->set_custom_bg_color(0, parent_bg_color);
}
@@ -352,7 +350,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
}
}
- if (searched_string.length() > 0) {
+ if (!searched_tokens.is_empty()) {
if (parent_should_expand) {
subdirectory_item->set_collapsed(false);
} else if (dname != "res://") {
@@ -460,7 +458,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo
color = Color(1, 1, 1);
}
- if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) {
+ if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) {
TreeItem *ti = tree->create_item(favorites_item);
ti->set_text(0, text);
ti->set_icon(0, icon);
@@ -857,7 +855,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
for (int i = 0; i < p_path->get_file_count(); i++) {
String file = p_path->get_file(i);
- if (file.to_lower().contains(searched_string)) {
+ if (_matches_all_search_tokens(file)) {
FileInfo fi;
fi.name = file;
fi.type = p_path->get_file_type(i);
@@ -984,14 +982,14 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
if (favorite == "res://") {
text = "/";
icon = folder_icon;
- if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) {
+ if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) {
files->add_item(text, icon, true);
files->set_item_metadata(-1, favorite);
}
} else if (favorite.ends_with("/")) {
text = favorite.substr(0, favorite.length() - 1).get_file();
icon = folder_icon;
- if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) {
+ if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) {
files->add_item(text, icon, true);
files->set_item_metadata(-1, favorite);
}
@@ -1013,7 +1011,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
fi.modified_time = 0;
}
- if (searched_string.length() == 0 || fi.name.to_lower().find(searched_string) >= 0) {
+ if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) {
file_list.push_back(fi);
}
}
@@ -1036,7 +1034,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
return;
}
- if (searched_string.length() > 0) {
+ if (!searched_tokens.is_empty()) {
// Display the search results.
// Limit the number of results displayed to avoid an infinite loop.
_search(EditorFileSystem::get_singleton()->get_filesystem(), &file_list, 10000);
@@ -1068,7 +1066,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
files->set_item_metadata(-1, bd);
files->set_item_selectable(-1, false);
- files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * 1.75);
+ files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE);
}
bool reversed = file_sort == FILE_SORT_NAME_REVERSE;
@@ -1082,7 +1080,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
files->add_item(dname, folder_icon, true);
files->set_item_metadata(-1, dpath);
Color this_folder_color = has_custom_color ? folder_colors[assigned_folder_colors[dpath]] : inherited_folder_color;
- files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * 1.75);
+ files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * ITEM_COLOR_SCALE);
if (previous_selection.has(dname)) {
files->select(files->get_item_count() - 1, false);
@@ -1272,7 +1270,7 @@ void FileSystemDock::_file_list_activate_file(int p_idx) {
}
void FileSystemDock::_preview_invalidated(const String &p_path) {
- if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) {
+ if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_tokens.is_empty() && file_list_vb->is_visible_in_tree()) {
for (int i = 0; i < files->get_item_count(); i++) {
if (files->get_item_metadata(i) == p_path) {
// Re-request preview.
@@ -1778,8 +1776,19 @@ void FileSystemDock::_folder_removed(const String &p_folder) {
current_path = current_path.get_base_dir();
}
- if (assigned_folder_colors.has(p_folder)) {
- assigned_folder_colors.erase(p_folder);
+ // Remove assigned folder color for all subfolders.
+ bool folder_colors_updated = false;
+ List<Variant> paths;
+ assigned_folder_colors.get_key_list(&paths);
+ for (const Variant &E : paths) {
+ const String &path = E;
+ // These folder paths are guaranteed to end with a "/".
+ if (path.begins_with(p_folder)) {
+ assigned_folder_colors.erase(path);
+ folder_colors_updated = true;
+ }
+ }
+ if (folder_colors_updated) {
_update_folder_colors_setting();
}
@@ -2603,12 +2612,13 @@ void FileSystemDock::_resource_created() {
}
void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) {
- if (searched_string.length() == 0) {
+ if (searched_tokens.is_empty()) {
// Register the uncollapsed paths before they change.
uncollapsed_paths_before_search = get_uncollapsed_paths();
}
- searched_string = p_text.to_lower();
+ const String searched_string = p_text.to_lower();
+ searched_tokens = searched_string.split(" ", false);
if (p_from == tree_search_box) {
file_list_search_box->set_text(searched_string);
@@ -2619,16 +2629,29 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from
bool unfold_path = (p_text.is_empty() && !current_path.is_empty());
switch (display_mode) {
case DISPLAY_MODE_TREE_ONLY: {
- _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
+ _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
case DISPLAY_MODE_HSPLIT:
case DISPLAY_MODE_VSPLIT: {
_update_file_list(false);
- _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
+ _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
}
}
+bool FileSystemDock::_matches_all_search_tokens(const String &p_text) {
+ if (searched_tokens.is_empty()) {
+ return false;
+ }
+ const String s = p_text.to_lower();
+ for (const String &t : searched_tokens) {
+ if (!s.contains(t)) {
+ return false;
+ }
+ }
+ return true;
+}
+
void FileSystemDock::_rescan() {
_set_scanning_mode();
EditorFileSystem::get_singleton()->scan();
@@ -3086,6 +3109,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) {
@@ -3354,7 +3379,7 @@ void FileSystemDock::_file_list_item_clicked(int p_item, const Vector2 &p_pos, M
// Popup.
if (!paths.is_empty()) {
file_list_popup->clear();
- _file_and_folders_fill_popup(file_list_popup, paths, searched_string.length() == 0);
+ _file_and_folders_fill_popup(file_list_popup, paths, searched_tokens.is_empty());
file_list_popup->set_position(files->get_screen_position() + p_pos);
file_list_popup->reset_size();
file_list_popup->popup();
@@ -3367,7 +3392,7 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton
}
// Right click on empty space for file list.
- if (searched_string.length() > 0) {
+ if (!searched_tokens.is_empty()) {
return;
}
@@ -3796,6 +3821,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"));
}
@@ -4113,7 +4139,6 @@ FileSystemDock::FileSystemDock() {
new_resource_dialog->set_base_type("Resource");
new_resource_dialog->connect("create", callable_mp(this, &FileSystemDock::_resource_created));
- searched_string = String();
uncollapsed_paths_before_search = Vector<String>();
tree_update_id = 0;
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 8830f31d2d..058886c91a 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -172,7 +172,7 @@ private:
LineEdit *file_list_search_box = nullptr;
MenuButton *file_list_button_sort = nullptr;
- String searched_string;
+ PackedStringArray searched_tokens;
Vector<String> uncollapsed_paths_before_search;
TextureRect *search_icon = nullptr;
@@ -311,6 +311,7 @@ private:
void _split_dragged(int p_offset);
void _search_changed(const String &p_text, const Control *p_from);
+ bool _matches_all_search_tokens(const String &p_text);
MenuButton *_create_file_menu_button();
void _file_sort_popup(int p_id);
@@ -381,6 +382,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/icons/PreviewRotate.svg b/editor/icons/PreviewRotate.svg
new file mode 100644
index 0000000000..9e0da46169
--- /dev/null
+++ b/editor/icons/PreviewRotate.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#000" stroke-width="2" opacity=".8" stroke-linejoin="round"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g><g fill="#f9f9f9"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g></svg>
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 620ebce44b..325525be1b 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -1117,6 +1117,20 @@ void SceneImportSettingsDialog::_cleanup() {
set_process(false);
}
+void SceneImportSettingsDialog::_on_light_1_switch_pressed() {
+ light1->set_visible(light_1_switch->is_pressed());
+}
+
+void SceneImportSettingsDialog::_on_light_2_switch_pressed() {
+ light2->set_visible(light_2_switch->is_pressed());
+}
+
+void SceneImportSettingsDialog::_on_light_rotate_switch_pressed() {
+ bool light_top_level = !light_rotate_switch->is_pressed();
+ light1->set_as_top_level_keep_local(light_top_level);
+ light2->set_as_top_level_keep_local(light_top_level);
+}
+
void SceneImportSettingsDialog::_viewport_input(const Ref<InputEvent> &p_input) {
float *rot_x = &cam_rot_x;
float *rot_y = &cam_rot_y;
@@ -1232,6 +1246,13 @@ void SceneImportSettingsDialog::_re_import() {
EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, editing_animation ? "animation_library" : "scene", main_settings);
}
+void SceneImportSettingsDialog::_update_theme_item_cache() {
+ ConfirmationDialog::_update_theme_item_cache();
+ theme_cache.light_1_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight1"));
+ theme_cache.light_2_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight2"));
+ theme_cache.rotate_icon = get_editor_theme_icon(SNAME("PreviewRotate"));
+}
+
void SceneImportSettingsDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
@@ -1251,6 +1272,10 @@ void SceneImportSettingsDialog::_notification(int p_what) {
animation_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay")));
}
animation_stop_button->set_icon(get_editor_theme_icon(SNAME("Stop")));
+
+ light_1_switch->set_icon(theme_cache.light_1_icon);
+ light_2_switch->set_icon(theme_cache.light_2_icon);
+ light_rotate_switch->set_icon(theme_cache.rotate_icon);
} break;
case NOTIFICATION_PROCESS: {
@@ -1644,6 +1669,40 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
base_viewport->set_use_own_world_3d(true);
+ HBoxContainer *viewport_hbox = memnew(HBoxContainer);
+ vp_container->add_child(viewport_hbox);
+ viewport_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2);
+
+ viewport_hbox->add_spacer();
+
+ VBoxContainer *vb_light = memnew(VBoxContainer);
+ vb_light->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ viewport_hbox->add_child(vb_light);
+
+ light_rotate_switch = memnew(Button);
+ light_rotate_switch->set_theme_type_variation("PreviewLightButton");
+ light_rotate_switch->set_toggle_mode(true);
+ light_rotate_switch->set_pressed(true);
+ light_rotate_switch->set_tooltip_text(TTR("Rotate Lights With Model"));
+ light_rotate_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_rotate_switch_pressed));
+ vb_light->add_child(light_rotate_switch);
+
+ light_1_switch = memnew(Button);
+ light_1_switch->set_theme_type_variation("PreviewLightButton");
+ light_1_switch->set_toggle_mode(true);
+ light_1_switch->set_pressed(true);
+ light_1_switch->set_tooltip_text(TTR("Primary Light"));
+ light_1_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_1_switch_pressed));
+ vb_light->add_child(light_1_switch);
+
+ light_2_switch = memnew(Button);
+ light_2_switch->set_theme_type_variation("PreviewLightButton");
+ light_2_switch->set_toggle_mode(true);
+ light_2_switch->set_pressed(true);
+ light_2_switch->set_tooltip_text(TTR("Secondary Light"));
+ light_2_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_2_switch_pressed));
+ vb_light->add_child(light_2_switch);
+
camera = memnew(Camera3D);
base_viewport->add_child(camera);
camera->make_current();
@@ -1675,10 +1734,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
environment->set_sky_custom_fov(50.0);
camera->set_environment(environment);
- light = memnew(DirectionalLight3D);
- light->set_transform(Transform3D().looking_at(Vector3(-1, -2, -0.6), Vector3(0, 1, 0)));
- base_viewport->add_child(light);
- light->set_shadow(true);
+ light1 = memnew(DirectionalLight3D);
+ light1->set_transform(Transform3D(Basis::looking_at(Vector3(-1, -1, -1))));
+ light1->set_shadow(true);
+ camera->add_child(light1);
+
+ light2 = memnew(DirectionalLight3D);
+ light2->set_transform(Transform3D(Basis::looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))));
+ light2->set_color(Color(0.5f, 0.5f, 0.5f));
+ camera->add_child(light2);
{
Ref<StandardMaterial3D> selection_mat;
diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h
index 17d6616fc0..c2a5151432 100644
--- a/editor/import/3d/scene_import_settings.h
+++ b/editor/import/3d/scene_import_settings.h
@@ -85,7 +85,18 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
bool first_aabb = false;
AABB contents_aabb;
- DirectionalLight3D *light = nullptr;
+ Button *light_1_switch = nullptr;
+ Button *light_2_switch = nullptr;
+ Button *light_rotate_switch = nullptr;
+
+ struct ThemeCache {
+ Ref<Texture2D> light_1_icon;
+ Ref<Texture2D> light_2_icon;
+ Ref<Texture2D> rotate_icon;
+ } theme_cache;
+
+ DirectionalLight3D *light1 = nullptr;
+ DirectionalLight3D *light2 = nullptr;
Ref<ArrayMesh> selection_mesh;
MeshInstance3D *node_selected = nullptr;
@@ -180,6 +191,9 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
void _mesh_tree_selected();
void _scene_tree_selected();
void _cleanup();
+ void _on_light_1_switch_pressed();
+ void _on_light_2_switch_pressed();
+ void _on_light_rotate_switch_pressed();
void _viewport_input(const Ref<InputEvent> &p_input);
@@ -222,6 +236,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
Timer *update_view_timer = nullptr;
protected:
+ virtual void _update_theme_item_cache() override;
void _notification(int p_what);
public:
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index e2aac6c75d..a667f94a62 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -587,7 +587,7 @@ void InputEventConfigurationDialog::_notification(int p_what) {
void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event, const String &p_current_action_name) {
if (p_event.is_valid()) {
- _set_event(p_event->duplicate(), p_event);
+ _set_event(p_event->duplicate(), p_event->duplicate());
} else {
// Clear Event
_set_event(Ref<InputEvent>(), Ref<InputEvent>());
diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp
new file mode 100644
index 0000000000..16be707d08
--- /dev/null
+++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp
@@ -0,0 +1,79 @@
+/**************************************************************************/
+/* geometry_instance_3d_gizmo_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "geometry_instance_3d_gizmo_plugin.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/visual_instance_3d.h"
+
+GeometryInstance3DGizmoPlugin::GeometryInstance3DGizmoPlugin() {
+}
+
+bool GeometryInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<GeometryInstance3D>(p_spatial) != nullptr;
+}
+
+String GeometryInstance3DGizmoPlugin::get_gizmo_name() const {
+ return "MeshInstance3DCustomAABB";
+}
+
+int GeometryInstance3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void GeometryInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ GeometryInstance3D *geometry = Object::cast_to<GeometryInstance3D>(p_gizmo->get_node_3d());
+
+ p_gizmo->clear();
+
+ if (p_gizmo->is_selected()) {
+ AABB aabb = geometry->get_custom_aabb();
+
+ Vector<Vector3> lines;
+ for (int i = 0; i < 12; i++) {
+ Vector3 a;
+ Vector3 b;
+ aabb.get_edge(i, a, b);
+
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ const Color selection_box_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/aabb");
+ mat->set_albedo(selection_box_color);
+ mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ p_gizmo->add_lines(lines, mat);
+ }
+}
diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h
new file mode 100644
index 0000000000..2482e277ea
--- /dev/null
+++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* geometry_instance_3d_gizmo_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H
+#define GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H
+
+#include "editor/plugins/node_3d_editor_gizmos.h"
+
+class GeometryInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(GeometryInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ virtual bool has_gizmo(Node3D *p_spatial) override;
+ virtual String get_gizmo_name() const override;
+ virtual int get_priority() const override;
+
+ virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ GeometryInstance3DGizmoPlugin();
+};
+
+#endif // GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index aea3ea9700..be14132185 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -53,6 +53,7 @@
#include "editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.h"
#include "editor/plugins/gizmos/decal_gizmo_plugin.h"
#include "editor/plugins/gizmos/fog_volume_gizmo_plugin.h"
+#include "editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h"
#include "editor/plugins/gizmos/gpu_particles_3d_gizmo_plugin.h"
#include "editor/plugins/gizmos/gpu_particles_collision_3d_gizmo_plugin.h"
#include "editor/plugins/gizmos/joint_3d_gizmo_plugin.h"
@@ -8096,6 +8097,7 @@ void Node3DEditor::_register_all_gizmos() {
add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin)));
add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin)));
add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin)));
+ add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin)));
add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin)));
add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin)));
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index e4e66b38e9..5e67cbc6ce 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -1476,6 +1476,10 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
_fetch_sprite_node(); // Fetch node after set frames.
}
+bool SpriteFramesEditor::is_editing() const {
+ return frames.is_valid();
+}
+
Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (read_only) {
return false;
@@ -2325,7 +2329,7 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const {
if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) {
return true;
}
- return p_object->is_class("SpriteFrames");
+ return !frames_editor->is_editing() && Object::cast_to<SpriteFrames>(p_object);
}
void SpriteFramesEditorPlugin::make_visible(bool p_visible) {
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index 2d0e43be1e..e9fbaf7dde 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -253,7 +253,6 @@ class SpriteFramesEditor : public HSplitContainer {
void _update_show_settings();
void _edit();
- void _regist_scene_undo(EditorUndoRedoManager *undo_redo);
void _fetch_sprite_node();
void _remove_sprite_node();
@@ -270,6 +269,8 @@ protected:
public:
void edit(Ref<SpriteFrames> p_frames);
+ bool is_editing() const;
+
SpriteFramesEditor();
};
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 0db752e771..166ed05748 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2267,7 +2267,9 @@ ThemeTypeDialog::ThemeTypeDialog() {
///////////////////////
Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const {
- return memnew(EditorHelpTooltip(p_text));
+ EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text));
+ EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ThemeItemLabel *>(this));
+ return memnew(Control); // Make the standard tooltip invisible.
}
VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) {
@@ -2436,8 +2438,8 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_
item_name->set_h_size_flags(SIZE_EXPAND_FILL);
item_name->set_clip_text(true);
item_name->set_text(p_item_name);
- // `|` separators used in `EditorHelpTooltip` for formatting.
- item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name + "|");
+ // `|` separators used in `EditorHelpBit`.
+ item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name);
item_name->set_mouse_filter(Control::MOUSE_FILTER_STOP);
item_name_container->add_child(item_name);
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index ba3446807e..0bc02789aa 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -321,7 +321,7 @@ public:
ThemeTypeDialog();
};
-// Custom `Label` needed to use `EditorHelpTooltip` to display theme item documentation.
+// Custom `Label` needed to use `EditorHelpBit` to display theme item documentation.
class ThemeItemLabel : public Label {
virtual Control *make_custom_tooltip(const String &p_text) const;
};
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index a16767d916..70cef0e345 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1571,6 +1571,8 @@ ProjectManager::ProjectManager() {
// Initialize project list.
{
+ project_list->load_project_list();
+
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
@@ -1581,13 +1583,10 @@ ProjectManager::ProjectManager() {
}
}
- bool scanned_for_projects = false; // Scanning will update the list automatically.
-
String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
if (!autoscan_path.is_empty()) {
if (dir_access->dir_exists(autoscan_path)) {
project_list->find_projects(autoscan_path);
- scanned_for_projects = true;
} else {
Error error = dir_access->make_dir_recursive(autoscan_path);
if (error != OK) {
@@ -1595,10 +1594,8 @@ ProjectManager::ProjectManager() {
}
}
}
-
- if (!scanned_for_projects) {
- project_list->update_project_list();
- }
+ project_list->update_project_list();
+ initialized = true;
}
// Extend menu bar to window title.
diff --git a/editor/project_manager.h b/editor/project_manager.h
index d472666d1a..669b5d8b6c 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -141,6 +141,7 @@ class ProjectManager : public Control {
void _update_list_placeholder();
ProjectList *project_list = nullptr;
+ bool initialized = false;
LineEdit *search_box = nullptr;
Label *loading_label = nullptr;
@@ -239,6 +240,7 @@ public:
// Project list.
+ bool is_initialized() const { return initialized; }
LineEdit *get_search_box();
// Project tag management.
diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp
index aa93d9414b..d125754dd7 100644
--- a/editor/project_manager/project_list.cpp
+++ b/editor/project_manager/project_list.cpp
@@ -469,23 +469,19 @@ void ProjectList::update_project_list() {
// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.
// FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper.
- // Clear whole list
- for (int i = 0; i < _projects.size(); ++i) {
- Item &project = _projects.write[i];
- CRASH_COND(project.control == nullptr);
- memdelete(project.control); // Why not queue_free()?
- }
- _projects.clear();
- _last_clicked = "";
- _selected_project_paths.clear();
+ if (ProjectManager::get_singleton()->is_initialized()) {
+ // Clear whole list
+ for (int i = 0; i < _projects.size(); ++i) {
+ Item &project = _projects.write[i];
+ CRASH_COND(project.control == nullptr);
+ memdelete(project.control); // Why not queue_free()?
+ }
- List<String> sections;
- _config.load(_config_path);
- _config.get_sections(&sections);
+ _projects.clear();
+ _last_clicked = "";
+ _selected_project_paths.clear();
- for (const String &path : sections) {
- bool favorite = _config.get_value(path, "favorite", false);
- _projects.push_back(load_project_data(path, favorite));
+ load_project_list();
}
// Create controls
@@ -590,7 +586,21 @@ void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) {
}
save_config();
- update_project_list();
+
+ if (ProjectManager::get_singleton()->is_initialized()) {
+ update_project_list();
+ }
+}
+
+void ProjectList::load_project_list() {
+ List<String> sections;
+ _config.load(_config_path);
+ _config.get_sections(&sections);
+
+ for (const String &path : sections) {
+ bool favorite = _config.get_value(path, "favorite", false);
+ _projects.push_back(load_project_data(path, favorite));
+ }
}
void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) {
diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h
index 86f1f13bd8..981df0f3a0 100644
--- a/editor/project_manager/project_list.h
+++ b/editor/project_manager/project_list.h
@@ -220,6 +220,7 @@ public:
// Project list updates.
+ void load_project_list();
void update_project_list();
void sort_projects();
int get_project_count() const;
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index ac175d01a6..d7af751f95 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -87,7 +87,7 @@ void PropertySelector::_update_search() {
}
search_options->clear();
- help_bit->set_text("");
+ help_bit->set_custom_text(String(), String(), String());
TreeItem *root = search_options->create_item();
@@ -353,7 +353,7 @@ void PropertySelector::_confirmed() {
}
void PropertySelector::_item_selected() {
- help_bit->set_text("");
+ help_bit->set_custom_text(String(), String(), String());
TreeItem *item = search_options->get_selected();
if (!item) {
@@ -372,25 +372,21 @@ void PropertySelector::_item_selected() {
String text;
while (!class_type.is_empty()) {
- text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name);
- if (!text.is_empty()) {
- break;
+ if (properties) {
+ if (ClassDB::has_property(class_type, name, true)) {
+ help_bit->parse_symbol("property|" + class_type + "|" + name);
+ break;
+ }
+ } else {
+ if (ClassDB::has_method(class_type, name, true)) {
+ help_bit->parse_symbol("method|" + class_type + "|" + name);
+ break;
+ }
}
// It may be from a parent class, keep looking.
class_type = ClassDB::get_parent_class(class_type);
}
-
- if (!text.is_empty()) {
- // Display both property name and description, since the help bit may be displayed
- // far away from the location (especially if the dialog was resized to be taller).
- help_bit->set_text(vformat("[b]%s[/b]: %s", name, text));
- help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- } else {
- // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
- help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", name)));
- help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
- }
}
void PropertySelector::_hide_requested() {
@@ -569,8 +565,7 @@ PropertySelector::PropertySelector() {
search_options->set_hide_folding(true);
help_bit = memnew(EditorHelpBit);
- vbc->add_margin_child(TTR("Description:"), help_bit);
- help_bit->get_rich_text()->set_fit_content(false);
- help_bit->get_rich_text()->set_custom_minimum_size(Size2(help_bit->get_rich_text()->get_minimum_size().x, 135 * EDSCALE));
+ help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
help_bit->connect("request_hide", callable_mp(this, &PropertySelector::_hide_requested));
+ vbc->add_margin_child(TTR("Description:"), help_bit);
}
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 734fa415a3..6151cb4b74 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)));
@@ -2158,6 +2166,28 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_constant("text_highlight_v_padding", "EditorHelp", 2 * EDSCALE);
}
+ // EditorHelpBitTitle.
+ {
+ Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate();
+ style->set_bg_color(p_config.dark_theme ? style->get_bg_color().lightened(0.04) : style->get_bg_color().darkened(0.04));
+ style->set_border_color(p_config.dark_theme ? style->get_border_color().lightened(0.04) : style->get_border_color().darkened(0.04));
+ style->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
+ style->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
+
+ p_theme->set_type_variation("EditorHelpBitTitle", "RichTextLabel");
+ p_theme->set_stylebox("normal", "EditorHelpBitTitle", style);
+ }
+
+ // EditorHelpBitContent.
+ {
+ Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate();
+ style->set_corner_radius(CORNER_TOP_LEFT, 0);
+ style->set_corner_radius(CORNER_TOP_RIGHT, 0);
+
+ p_theme->set_type_variation("EditorHelpBitContent", "RichTextLabel");
+ p_theme->set_stylebox("normal", "EditorHelpBitContent", style);
+ }
+
// Asset Library.
p_theme->set_stylebox("bg", "AssetLib", p_config.base_empty_style);
p_theme->set_stylebox("panel", "AssetLib", p_config.content_panel_style);
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 6471c5a142..43349ae6ef 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -256,17 +256,14 @@ Compatibility methods registered.
GH-88014
--------
-Validate extension JSON: API was removed: classes/VisualShaderNodeComment
-
-Removed VisualShaderNodeComment, which is replaced by VisualShaderNodeFrame.
+Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/get_title
+Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/set_title
+Validate extension JSON: API was removed: classes/VisualShaderNodeComment/properties/title
GH-87888
--------
Validate extension JSON: API was removed: classes/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/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index cd19887d82..ec75663e97 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1997,7 +1997,7 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant
#ifdef DEBUG_ENABLED
if (p_is_local) {
- if (p_constant->usages == 0) {
+ if (p_constant->usages == 0 && !String(p_constant->identifier->name).begins_with("_")) {
parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name);
}
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d706c1b101..c526d9c0a4 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -250,7 +250,7 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc
return true;
}
-GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) {
+GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) {
if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) {
return codegen.add_constant(p_expression->reduced_value);
}
@@ -781,9 +781,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
bool named = subscript->is_attribute;
StringName name;
GDScriptCodeGenerator::Address index;
- if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) {
- index = p_index_addr;
- } else if (subscript->is_attribute) {
+ if (subscript->is_attribute) {
if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) {
GDScriptParser::IdentifierNode *identifier = subscript->attribute;
HashMap<StringName, GDScript::MemberInfo>::Iterator MI = codegen.script->member_indices.find(identifier->name);
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 0adbe1ed8e..637d61ca3b 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -149,13 +149,9 @@ class GDScriptCompiler {
void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
- Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
-
GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true);
- GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
- GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address());
+ GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false);
GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses);
diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out
index 7ec33470d3..d73c5eb7cd 100644
--- a/modules/gdscript/tests/scripts/parser/features/constants.out
+++ b/modules/gdscript/tests/scripts/parser/features/constants.out
@@ -1,33 +1 @@
GDTEST_OK
->> WARNING
->> Line: 2
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "__TEST".
->> WARNING
->> Line: 3
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_STRING" is declared but never used in the block. If this is intended, prefix it with an underscore: "__STRING".
->> WARNING
->> Line: 4
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_VECTOR" is declared but never used in the block. If this is intended, prefix it with an underscore: "__VECTOR".
->> WARNING
->> Line: 5
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_ARRAY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__ARRAY".
->> WARNING
->> Line: 6
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_DICTIONARY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__DICTIONARY".
->> WARNING
->> Line: 9
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_HELLO" is declared but never used in the block. If this is intended, prefix it with an underscore: "__HELLO".
->> WARNING
->> Line: 10
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INFINITY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INFINITY".
->> WARNING
->> Line: 11
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_NOT_A_NUMBER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__NOT_A_NUMBER".
diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out
index 40a8f97416..d73c5eb7cd 100644
--- a/modules/gdscript/tests/scripts/parser/features/static_typing.out
+++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out
@@ -1,21 +1 @@
GDTEST_OK
->> WARNING
->> Line: 11
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INTEGER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER".
->> WARNING
->> Line: 12
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INTEGER_REDUNDANT_TYPED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED".
->> WARNING
->> Line: 13
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INTEGER_REDUNDANT_TYPED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED2".
->> WARNING
->> Line: 14
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INTEGER_REDUNDANT_INFERRED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED".
->> WARNING
->> Line: 15
->> UNUSED_LOCAL_CONSTANT
->> The local constant "_INTEGER_REDUNDANT_INFERRED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED2".
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd
new file mode 100644
index 0000000000..3d355197e1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd
@@ -0,0 +1,4 @@
+func test():
+ const UNUSED = "not used"
+
+ const _UNUSED = "not used, but no warning since the constant name starts with an underscore"
diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out
new file mode 100644
index 0000000000..99ced48433
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> UNUSED_LOCAL_CONSTANT
+>> The local constant "UNUSED" is declared but never used in the block. If this is intended, prefix it with an underscore: "_UNUSED".
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/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index b6f5d6ce57..835fb3e59d 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -1908,7 +1908,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
seams_push_constant.slice = uint32_t(i * subslices + k);
seams_push_constant.debug = debug;
- RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1);
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/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..3b788b2fd0 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);
}
@@ -753,6 +753,15 @@ void Node3D::set_as_top_level(bool p_enabled) {
data.top_level = p_enabled;
}
+void Node3D::set_as_top_level_keep_local(bool p_enabled) {
+ ERR_THREAD_GUARD;
+ if (data.top_level == p_enabled) {
+ return;
+ }
+ data.top_level = p_enabled;
+ _propagate_transform_changed(this);
+}
+
bool Node3D::is_set_as_top_level() const {
ERR_READ_THREAD_GUARD_V(false);
return data.top_level;
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index 7f8c3e6e68..c1667221df 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -227,6 +227,7 @@ public:
void clear_gizmos();
void set_as_top_level(bool p_enabled);
+ void set_as_top_level_keep_local(bool p_enabled);
bool is_set_as_top_level() const;
void set_disable_scale(bool p_enabled);
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/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 503c39ae3e..89f7ab2391 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -377,6 +377,7 @@ void GeometryInstance3D::set_custom_aabb(AABB p_aabb) {
}
custom_aabb = p_aabb;
RS::get_singleton()->instance_set_custom_aabb(get_instance(), custom_aabb);
+ update_gizmos();
}
AABB GeometryInstance3D::get_custom_aabb() const {
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index b1870eea55..ad3f607661 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -358,7 +358,7 @@ void Button::_notification(int p_what) {
if (!xl_text.is_empty()) {
text_buf->set_alignment(align_rtl_checked);
- float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf.
+ float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf.
text_buf->set_width(text_buf_width);
Point2 text_ofs;
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index c13a2e281a..e481b78715 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -202,7 +202,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) {
}
void AcceptDialog::_update_child_rects() {
- Size2 dlg_size = get_size();
+ Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor();
float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT);
float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM);
@@ -210,6 +210,15 @@ void AcceptDialog::_update_child_rects() {
bg_panel->set_position(Point2());
bg_panel->set_size(dlg_size);
+ for (int i = 0; i < buttons_hbox->get_child_count(); i++) {
+ Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i));
+ if (!b) {
+ continue;
+ }
+
+ b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height));
+ }
+
// Place the buttons from the bottom edge to their minimum required size.
Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y);
@@ -389,6 +398,8 @@ void AcceptDialog::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height);
}
bool AcceptDialog::swap_cancel_ok = false;
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 6f9f450778..12b48c903a 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -57,6 +57,8 @@ class AcceptDialog : public Window {
struct ThemeCache {
Ref<StyleBox> panel_style;
int buttons_separation = 0;
+ int buttons_min_width = 0;
+ int buttons_min_height = 0;
} theme_cache;
void _custom_action(const String &p_action);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 12b2364ddf..c3a586a1ee 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -1255,52 +1255,6 @@ int FileDialog::get_option_count() const {
return options.size();
}
-bool FileDialog::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) {
- int item_index = components[0].trim_prefix("option_").to_int();
- String property = components[1];
- if (property == "name") {
- set_option_name(item_index, p_value);
- return true;
- } else if (property == "values") {
- set_option_values(item_index, p_value);
- return true;
- } else if (property == "default") {
- set_option_default(item_index, p_value);
- return true;
- }
- }
- return false;
-}
-
-bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) {
- int item_index = components[0].trim_prefix("option_").to_int();
- String property = components[1];
- if (property == "name") {
- r_ret = get_option_name(item_index);
- return true;
- } else if (property == "values") {
- r_ret = get_option_values(item_index);
- return true;
- } else if (property == "default") {
- r_ret = get_option_default(item_index);
- return true;
- }
- }
- return false;
-}
-
-void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < options.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i)));
- p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i)));
- p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i)));
- }
-}
-
void FileDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed);
@@ -1386,6 +1340,13 @@ void FileDialog::_bind_methods() {
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button");
+
+ Option defaults;
+
+ base_property_helper.set_prefix("option_");
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name);
+ base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default);
}
void FileDialog::set_show_hidden_files(bool p_show) {
@@ -1563,6 +1524,8 @@ FileDialog::FileDialog() {
if (register_func) {
register_func(this);
}
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
FileDialog::~FileDialog() {
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 7caae7e216..4236f0a56b 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -37,6 +37,7 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/tree.h"
+#include "scene/property_list_helper.h"
class GridContainer;
@@ -137,6 +138,10 @@ private:
Vector<String> values;
int default_idx = 0;
};
+
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
Vector<Option> options;
Dictionary selected_options;
bool options_dirty = false;
@@ -187,9 +192,11 @@ private:
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 89c627a7a8..e83d9c7c1b 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) {
}
bool MenuButton::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname)) {
bool valid;
- popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid);
+ popup->set(sname.trim_prefix("popup/"), p_value, &valid);
return valid;
}
return false;
}
bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname)) {
bool valid;
- r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid);
+ r_ret = popup->get(sname.trim_prefix("popup/"), &valid);
return valid;
}
return false;
}
-void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < popup->get_item_count(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button");
- pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i));
- pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
- pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i));
- pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void MenuButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
@@ -215,6 +186,17 @@ void MenuButton::_bind_methods() {
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
ADD_SIGNAL(MethodInfo("about_to_popup"));
+
+ PopupMenu::Item defaults(true);
+
+ base_property_helper.set_prefix("popup/item_");
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator);
}
void MenuButton::set_disable_shortcuts(bool p_disabled) {
@@ -235,6 +217,8 @@ MenuButton::MenuButton(const String &p_text) :
add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true));
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false));
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
MenuButton::~MenuButton() {
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index eea6b8e877..2bd577ddd0 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -33,6 +33,7 @@
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
+#include "scene/property_list_helper.h"
class MenuButton : public Button {
GDCLASS(MenuButton, Button);
@@ -42,13 +43,18 @@ class MenuButton : public Button {
bool disable_shortcuts = false;
PopupMenu *popup = nullptr;
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
void _popup_visibility_changed(bool p_visible);
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 0e10652f07..509c6aca99 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) {
}
bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
- const String &property = components[2];
- if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") {
- return false;
- }
+ int index;
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname, &index)) {
bool valid;
- popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid);
+ popup->set(sname.trim_prefix("popup/"), p_value, &valid);
- int idx = components[1].get_slice("_", 1).to_int();
- if (idx == current) {
+ if (index == current) {
// Force refreshing currently displayed item.
current = NONE_SELECTED;
- _select(idx, false);
+ _select(index, false);
}
+ const String property = sname.get_slice("/", 2);
if (property == "text" || property == "icon") {
_queue_update_size_cache();
}
@@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
-bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
- const String &property = components[2];
- if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") {
- return false;
- }
-
- bool valid;
- r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid);
- return valid;
- }
- return false;
-}
-
-void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < popup->get_item_count(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
- pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i));
- pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void OptionButton::_focused(int p_which) {
emit_signal(SNAME("item_focused"), p_which);
}
@@ -606,6 +567,15 @@ void OptionButton::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow);
+
+ PopupMenu::Item defaults(true);
+
+ base_property_helper.set_prefix("popup/item_");
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator);
}
void OptionButton::set_disable_shortcuts(bool p_disabled) {
@@ -625,6 +595,8 @@ OptionButton::OptionButton(const String &p_text) :
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false));
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
OptionButton::~OptionButton() {
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 9c15b295a9..4b5164161a 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -33,6 +33,7 @@
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
+#include "scene/property_list_helper.h"
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
@@ -64,11 +65,15 @@ class OptionButton : public Button {
int modulate_arrow = 0;
} theme_cache;
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
void _focused(int p_which);
void _selected(int p_which);
void _select(int p_which, bool p_emit = false);
void _select_int(int p_which);
void _refresh_size_cache();
+ void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it).
virtual void pressed() override;
@@ -78,8 +83,10 @@ protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index d90d5da2e9..87383283fd 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -230,7 +230,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const {
void PopupPanel::_update_child_rects() {
Vector2 cpos(theme_cache.panel_style->get_offset());
- Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size());
+ Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor();
+ Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -244,7 +245,7 @@ void PopupPanel::_update_child_rects() {
if (c == panel) {
c->set_position(Vector2());
- c->set_size(get_size());
+ c->set_size(panel_size);
} else {
c->set_position(cpos);
c->set_size(csize);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 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..9a2ba23ce8 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2459,7 +2459,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (rtl) {
button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width();
}
- p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size);
button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color);
item_width_with_buttons -= button_size.width + theme_cache.button_margin;
}
@@ -3995,12 +3994,14 @@ bool Tree::edit_selected(bool p_force_edit) {
return false;
}
+ float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor();
Rect2 rect;
if (select_mode == SELECT_ROW) {
rect = s->get_meta("__focus_col_" + itos(selected_col));
} else {
rect = s->get_meta("__focus_rect");
}
+ rect.position *= popup_scale;
popup_edited_item = s;
popup_edited_item_col = col;
@@ -4043,7 +4044,7 @@ bool Tree::edit_selected(bool p_force_edit) {
popup_rect.size = rect.size;
// Account for icon.
- Size2 icon_size = _get_cell_icon_size(c);
+ Size2 icon_size = _get_cell_icon_size(c) * popup_scale;
popup_rect.position.x += icon_size.x;
popup_rect.size.x -= icon_size.x;
@@ -4070,7 +4071,10 @@ bool Tree::edit_selected(bool p_force_edit) {
}
popup_editor->set_position(popup_rect.position);
- popup_editor->set_size(popup_rect.size);
+ popup_editor->set_size(popup_rect.size * popup_scale);
+ if (!popup_editor->is_embedded()) {
+ popup_editor->set_content_scale_factor(popup_scale);
+ }
popup_editor->popup();
popup_editor->child_controls_changed();
@@ -4086,7 +4090,10 @@ bool Tree::edit_selected(bool p_force_edit) {
text_editor->show();
popup_editor->set_position(get_screen_position() + rect.position);
- popup_editor->set_size(rect.size);
+ popup_editor->set_size(rect.size * popup_scale);
+ if (!popup_editor->is_embedded()) {
+ popup_editor->set_content_scale_factor(popup_scale);
+ }
popup_editor->popup();
popup_editor->child_controls_changed();
@@ -5400,7 +5407,6 @@ String Tree::get_tooltip(const Point2 &p_pos) const {
return Control::get_tooltip(p_pos);
}
- Point2 button_pos = pos;
if (h_scroll->is_visible_in_tree()) {
pos.x += h_scroll->get_value();
}
@@ -5413,13 +5419,22 @@ String Tree::get_tooltip(const Point2 &p_pos) const {
if (it) {
const TreeItem::Cell &c = it->cells[col];
+ int col_width = get_column_width(col);
+
+ for (int i = 0; i < col; i++) {
+ pos.x -= get_column_width(i);
+ }
+
for (int j = c.buttons.size() - 1; j >= 0; j--) {
- if (c.buttons[j].rect.has_point(button_pos)) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (pos.x > col_width - size.width) {
String tooltip = c.buttons[j].tooltip;
if (!tooltip.is_empty()) {
return tooltip;
}
}
+ col_width -= size.width;
}
String ret;
if (it->get_tooltip_text(col) == "") {
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 21696d8216..c7c266a2e7 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -108,7 +108,6 @@ private:
Ref<Texture2D> texture;
Color color = Color(1, 1, 1, 1);
String tooltip;
- Rect2i rect;
};
Vector<Button> buttons;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 17d7fec230..2d30ea345d 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1461,6 +1461,8 @@ void Viewport::_gui_show_tooltip() {
panel->set_flag(Window::FLAG_NO_FOCUS, true);
panel->set_flag(Window::FLAG_POPUP, false);
panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true);
+ // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport.
+ panel->set_flag(Window::FLAG_TRANSPARENT, true);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
panel->gui_parent = this;
@@ -1469,17 +1471,25 @@ void Viewport::_gui_show_tooltip() {
tooltip_owner->add_child(gui.tooltip_popup);
+ Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder());
+ if (!window) { // Not embedded.
+ window = gui.tooltip_popup->get_parent_visible_window();
+ }
+ float win_scale = window->content_scale_factor;
Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset");
+ if (!gui.tooltip_popup->is_embedded()) {
+ tooltip_offset *= win_scale;
+ }
Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size());
- r.size = r.size.min(panel->get_max_size());
-
- Window *window = gui.tooltip_popup->get_parent_visible_window();
Rect2i vr;
if (gui.tooltip_popup->is_embedded()) {
vr = gui.tooltip_popup->get_embedder()->get_visible_rect();
} else {
+ panel->content_scale_factor = win_scale;
+ r.size *= win_scale;
vr = window->get_usable_parent_rect();
}
+ r.size = r.size.min(panel->get_max_size());
if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
// Place it in the opposite direction. If it fails, just hug the border.
diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp
index d9a80011b0..b666e4c52d 100644
--- a/scene/property_list_helper.cpp
+++ b/scene/property_list_helper.cpp
@@ -31,7 +31,7 @@
#include "property_list_helper.h"
const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const {
- const Vector<String> components = p_property.split("/", true, 2);
+ const Vector<String> components = p_property.rsplit("/", true, 1);
if (components.size() < 2 || !components[0].begins_with(prefix)) {
return nullptr;
}
@@ -48,36 +48,73 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri
}
void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const {
+ DEV_ASSERT(p_setter);
Variant args[] = { p_index, p_value };
const Variant *argptrs[] = { &args[0], &args[1] };
Callable::CallError ce;
p_setter->call(object, argptrs, 2, ce);
}
-Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const {
+Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const {
+ if (!p_property->getter) {
+ return object->get(prefix + itos(p_index) + "/" + p_property->info.name);
+ }
+
Callable::CallError ce;
Variant args[] = { p_index };
const Variant *argptrs[] = { &args[0] };
- return p_getter->call(object, argptrs, 1, ce);
+ return p_property->getter->call(object, argptrs, 1, ce);
}
void PropertyListHelper::set_prefix(const String &p_prefix) {
prefix = p_prefix;
}
+void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) {
+ Property property;
+ property.info = p_info;
+ property.default_value = p_default;
+
+ property_list[p_info.name] = property;
+}
+
+bool PropertyListHelper::is_initialized() const {
+ return !property_list.is_empty();
+}
+
void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) {
prefix = p_base.prefix;
property_list = p_base.property_list;
object = p_object;
}
+bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const {
+ const Vector<String> components = p_property.rsplit("/", true, 1);
+ if (components.size() < 2 || !components[0].begins_with(prefix)) {
+ return false;
+ }
+
+ {
+ const String index_string = components[0].trim_prefix(prefix);
+ if (!index_string.is_valid_int()) {
+ return false;
+ }
+
+ if (r_index) {
+ *r_index = index_string.to_int();
+ }
+ }
+
+ return property_list.has(components[1]);
+}
+
void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const {
for (int i = 0; i < p_count; i++) {
for (const KeyValue<String, Property> &E : property_list) {
const Property &property = E.value;
PropertyInfo info = property.info;
- if (_call_getter(property.getter, i) == property.default_value) {
+ if (_call_getter(&property, i) == property.default_value) {
info.usage &= (~PROPERTY_USAGE_STORAGE);
}
@@ -92,7 +129,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r
const Property *property = _get_property(p_property, &index);
if (property) {
- r_ret = _call_getter(property->getter, index);
+ r_ret = _call_getter(property, index);
return true;
}
return false;
@@ -110,8 +147,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari
}
bool PropertyListHelper::property_can_revert(const String &p_property) const {
- int index;
- return _get_property(p_property, &index) != nullptr;
+ return is_property_valid(p_property);
}
bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const {
@@ -129,8 +165,10 @@ PropertyListHelper::~PropertyListHelper() {
// No object = it's the main helper. Do a cleanup.
if (!object) {
for (const KeyValue<String, Property> &E : property_list) {
- memdelete(E.value.setter);
- memdelete(E.value.getter);
+ if (E.value.setter) {
+ memdelete(E.value.setter);
+ memdelete(E.value.getter);
+ }
}
}
}
diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h
index 6c1ad21a05..eac6b03d47 100644
--- a/scene/property_list_helper.h
+++ b/scene/property_list_helper.h
@@ -48,10 +48,12 @@ class PropertyListHelper {
const Property *_get_property(const String &p_property, int *r_index) const;
void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const;
- Variant _call_getter(const MethodBind *p_getter, int p_index) const;
+ Variant _call_getter(const Property *p_property, int p_index) const;
public:
void set_prefix(const String &p_prefix);
+ // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic.
+ void register_property(const PropertyInfo &p_info, const Variant &p_default);
template <typename S, typename G>
void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) {
@@ -64,7 +66,9 @@ public:
property_list[p_info.name] = property;
}
+ bool is_initialized() const;
void setup_for_instance(const PropertyListHelper &p_base, Object *p_object);
+ bool is_property_valid(const String &p_property, int *r_index = nullptr) const;
void get_property_list(List<PropertyInfo> *p_list, int p_count) const;
bool property_get_value(const String &p_property, Variant &r_ret) const;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 1c8833494d..d13f338444 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -657,6 +657,9 @@ void register_scene_types() {
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant);
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase);
GDREGISTER_CLASS(VisualShaderNodeFrame);
+#ifndef DISABLE_DEPRECATED
+ GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility.
+#endif
GDREGISTER_CLASS(VisualShaderNodeFloatConstant);
GDREGISTER_CLASS(VisualShaderNodeIntConstant);
GDREGISTER_CLASS(VisualShaderNodeUIntConstant);
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 9ac899ad78..7e80d0be3c 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -4206,7 +4206,7 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() {
set_allow_v_resize(true);
}
-////////////// Comment
+////////////// Frame
String VisualShaderNodeFrame::get_caption() const {
return title;
@@ -4323,6 +4323,25 @@ void VisualShaderNodeFrame::_bind_methods() {
VisualShaderNodeFrame::VisualShaderNodeFrame() {
}
+////////////// Comment (Deprecated)
+
+#ifndef DISABLE_DEPRECATED
+void VisualShaderNodeComment::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description);
+ ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description");
+}
+
+void VisualShaderNodeComment::set_description(const String &p_description) {
+ description = p_description;
+}
+
+String VisualShaderNodeComment::get_description() const {
+ return description;
+}
+#endif
+
////////////// GroupBase
void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) {
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 3ef6dcd4f9..d7270f3ac6 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -767,6 +767,28 @@ public:
VisualShaderNodeFrame();
};
+#ifndef DISABLE_DEPRECATED
+// Deprecated, for compatibility only.
+class VisualShaderNodeComment : public VisualShaderNodeFrame {
+ GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame);
+
+ String description;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const override { return "Comment(Deprecated)"; }
+
+ virtual Category get_category() const override { return CATEGORY_NONE; }
+
+ void set_description(const String &p_description);
+ String get_description() const;
+
+ VisualShaderNodeComment() {}
+};
+#endif
+
class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase {
GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase);
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 3e3a7d2381..b5333d91c6 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -676,63 +676,6 @@ bool AudioStreamRandomizer::is_monophonic() const {
return false;
}
-bool AudioStreamRandomizer::_get(const StringName &p_name, Variant &r_ret) const {
- if (AudioStream::_get(p_name, r_ret)) {
- return true;
- }
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) {
- int index = components[0].trim_prefix("stream_").to_int();
- if (index < 0 || index >= (int)audio_stream_pool.size()) {
- return false;
- }
-
- if (components[1] == "stream") {
- r_ret = get_stream(index);
- return true;
- } else if (components[1] == "weight") {
- r_ret = get_stream_probability_weight(index);
- return true;
- } else {
- return false;
- }
- }
- return false;
-}
-
-bool AudioStreamRandomizer::_set(const StringName &p_name, const Variant &p_value) {
- if (AudioStream::_set(p_name, p_value)) {
- return true;
- }
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) {
- int index = components[0].trim_prefix("stream_").to_int();
- if (index < 0 || index >= (int)audio_stream_pool.size()) {
- return false;
- }
-
- if (components[1] == "stream") {
- set_stream(index, p_value);
- return true;
- } else if (components[1] == "weight") {
- set_stream_probability_weight(index, p_value);
- return true;
- } else {
- return false;
- }
- }
- return false;
-}
-
-void AudioStreamRandomizer::_get_property_list(List<PropertyInfo> *p_list) const {
- AudioStream::_get_property_list(p_list); // Define the trivial scalar properties.
- p_list->push_back(PropertyInfo(Variant::NIL, "Streams", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
- for (int i = 0; i < audio_stream_pool.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("stream_%d/stream", i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"));
- p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("stream_%d/weight", i), PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"));
- }
-}
-
void AudioStreamRandomizer::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_stream", "index", "stream", "weight"), &AudioStreamRandomizer::add_stream, DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("move_stream", "index_from", "index_to"), &AudioStreamRandomizer::move_stream);
@@ -764,9 +707,17 @@ void AudioStreamRandomizer::_bind_methods() {
BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS);
BIND_ENUM_CONSTANT(PLAYBACK_RANDOM);
BIND_ENUM_CONSTANT(PLAYBACK_SEQUENTIAL);
+
+ PoolEntry defaults;
+
+ base_property_helper.set_prefix("stream_");
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream);
+ base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight);
}
-AudioStreamRandomizer::AudioStreamRandomizer() {}
+AudioStreamRandomizer::AudioStreamRandomizer() {
+ property_helper.setup_for_instance(base_property_helper, this);
+}
void AudioStreamPlaybackRandomizer::start(double p_from_pos) {
playing = playback;
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index f8123fbe15..01a4a09942 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -33,6 +33,7 @@
#include "core/io/image.h"
#include "core/io/resource.h"
+#include "scene/property_list_helper.h"
#include "servers/audio/audio_filter_sw.h"
#include "servers/audio_server.h"
@@ -236,9 +237,12 @@ private:
struct PoolEntry {
Ref<AudioStream> stream;
- float weight;
+ float weight = 1.0;
};
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
HashSet<AudioStreamPlaybackRandomizer *> playbacks;
Vector<PoolEntry> audio_stream_pool;
float random_pitch_scale = 1.0f;
@@ -254,9 +258,11 @@ private:
protected:
static void _bind_methods();
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, audio_stream_pool.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
public:
void add_stream(int p_index, Ref<AudioStream> p_stream, float p_weight = 1.0);
diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp
index 27c07f23fa..b5d31f5414 100644
--- a/servers/rendering/renderer_rd/environment/sky.cpp
+++ b/servers/rendering/renderer_rd/environment/sky.cpp
@@ -1472,7 +1472,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p
Vector<Color> clear_colors;
clear_colors.push_back(Color(0.0, 0.0, 0.0));
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0);
_render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier);
RD::get_singleton()->draw_list_end();
}
@@ -1491,7 +1491,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p
Vector<Color> clear_colors;
clear_colors.push_back(Color(0.0, 0.0, 0.0));
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0);
_render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier);
RD::get_singleton()->draw_list_end();
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 0e69ad99b8..c7ab7ea462 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -1413,7 +1413,7 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo
if (p_render_data->directional_shadows.size()) {
//open the pass for directional shadows
light_storage->update_directional_shadow_atlas();
- RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE);
+ RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0);
RD::get_singleton()->draw_list_end();
}
}
@@ -1930,7 +1930,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
}
if (needs_pre_resolve) {
//pre clear the depth framebuffer, as AMD (and maybe others?) use compute for it, and barrier other compute shaders.
- RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear);
+ RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear, 0.0);
RD::get_singleton()->draw_list_end();
//start compute processes here, so they run at the same time as depth pre-pass
_post_prepass_render(p_render_data, using_sdfgi || using_voxelgi);
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 48c9cda253..5715d94d95 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -614,7 +614,7 @@ void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) {
if (p_render_data->directional_shadows.size()) {
//open the pass for directional shadows
light_storage->update_directional_shadow_atlas();
- RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE);
+ RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0);
RD::get_singleton()->draw_list_end();
}
}
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index c8277024cf..9a898a2fca 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1103,7 +1103,7 @@ private:
public:
DrawListID draw_list_begin_for_screen(DisplayServer::WindowID p_screen = 0, const Color &p_clear_color = Color());
- DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 0.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2());
+ DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2());
void draw_list_set_blend_constants(DrawListID p_list, const Color &p_color);
void draw_list_bind_render_pipeline(DrawListID p_list, RID p_render_pipeline);
diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h
index 551b20fe74..fba0fcb280 100644
--- a/tests/core/math/test_transform_3d.h
+++ b/tests/core/math/test_transform_3d.h
@@ -107,6 +107,35 @@ TEST_CASE("[Transform3D] Finite number checks") {
"Transform3D with two components infinite should not be finite.");
}
+TEST_CASE("[Transform3D] Rotate around global origin") {
+ // Start with the default orientation, but not centered on the origin.
+ // Rotating should rotate both our basis and the origin.
+ Transform3D transform = Transform3D();
+ transform.origin = Vector3(0, 0, 1);
+
+ Transform3D expected = Transform3D();
+ expected.origin = Vector3(0, 0, -1);
+ expected.basis[0] = Vector3(-1, 0, 0);
+ expected.basis[2] = Vector3(0, 0, -1);
+
+ const Transform3D rotated_transform = transform.rotated(Vector3(0, 1, 0), Math_PI);
+ CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation and basis.");
+}
+
+TEST_CASE("[Transform3D] Rotate in-place (local rotation)") {
+ // Start with the default orientation.
+ // Local rotation should not change the origin, only the basis.
+ Transform3D transform = Transform3D();
+ transform.origin = Vector3(1, 2, 3);
+
+ Transform3D expected = Transform3D();
+ expected.origin = Vector3(1, 2, 3);
+ expected.basis[0] = Vector3(-1, 0, 0);
+ expected.basis[2] = Vector3(0, 0, -1);
+
+ const Transform3D rotated_transform = Transform3D(transform.rotated_local(Vector3(0, 1, 0), Math_PI));
+ CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation but still be based on the same origin.");
+}
} // namespace TestTransform3D
#endif // TEST_TRANSFORM_3D_H