summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/string/ustring.cpp6
-rw-r--r--doc/classes/DisplayServer.xml2
-rw-r--r--doc/classes/EditorPlugin.xml7
-rw-r--r--doc/classes/FileAccess.xml2
-rw-r--r--doc/classes/TreeItem.xml6
-rwxr-xr-xdoc/tools/make_rst.py8
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp4
-rw-r--r--editor/editor_audio_buses.cpp3
-rw-r--r--editor/editor_dock_manager.cpp14
-rw-r--r--editor/editor_dock_manager.h2
-rw-r--r--editor/editor_inspector.cpp20
-rw-r--r--editor/editor_node.cpp7
-rw-r--r--editor/editor_plugin.compat.inc46
-rw-r--r--editor/editor_plugin.cpp13
-rw-r--r--editor/editor_plugin.h77
-rw-r--r--editor/editor_properties.cpp16
-rw-r--r--editor/gui/editor_bottom_panel.cpp16
-rw-r--r--editor/gui/editor_bottom_panel.h4
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp3
-rw-r--r--editor/plugins/animation_tree_editor_plugin.cpp3
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp2
-rw-r--r--editor/plugins/debugger_editor_plugin.cpp3
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp21
-rw-r--r--editor/plugins/resource_preloader_editor_plugin.cpp3
-rw-r--r--editor/plugins/script_editor_plugin.cpp2
-rw-r--r--editor/plugins/shader_editor_plugin.cpp2
-rw-r--r--editor/plugins/shader_file_editor_plugin.cpp4
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp30
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp3
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp5
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp3
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp65
-rw-r--r--editor/shader_globals_editor.cpp3
-rw-r--r--main/main.cpp10
-rw-r--r--misc/extension_api_validation/4.2-stable.expected9
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml47
-rw-r--r--modules/gdscript/gdscript_parser.cpp143
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.gd86
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.out137
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd23
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.out32
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd37
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.cpp3
-rw-r--r--modules/openxr/editor/openxr_editor_plugin.cpp3
-rw-r--r--platform/android/export/export_plugin.cpp15
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp4
-rw-r--r--platform/linuxbsd/detect.py18
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp17
-rw-r--r--platform/windows/display_server_windows.cpp15
-rw-r--r--scene/gui/tree.cpp45
-rw-r--r--scene/gui/tree.h4
-rw-r--r--servers/rendering/rendering_device.cpp6
-rw-r--r--tests/core/string/test_string.h63
59 files changed, 874 insertions, 264 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index f4b00255a1..1d27933016 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -3329,10 +3329,14 @@ bool String::begins_with(const String &p_string) const {
bool String::begins_with(const char *p_string) const {
int l = length();
- if (l == 0 || !p_string) {
+ if (!p_string) {
return false;
}
+ if (l == 0) {
+ return *p_string == 0;
+ }
+
const char32_t *str = &operator[](0);
int i = 0;
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index d3c76737db..5e8f5b540a 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1148,6 +1148,7 @@
<param index="0" name="image" type="Image" />
<description>
Sets the window icon (usually displayed in the top-left corner) with an [Image]. To use icons in the operating system's native format, use [method set_native_icon] instead.
+ [b]Note:[/b] Requires support for [constant FEATURE_ICON].
</description>
</method>
<method name="set_native_icon">
@@ -1155,6 +1156,7 @@
<param index="0" name="filename" type="String" />
<description>
Sets the window icon (usually displayed in the top-left corner) in the operating system's [i]native[/i] format. The file at [param filename] must be in [code].ico[/code] format on Windows or [code].icns[/code] on macOS. By using specially crafted [code].ico[/code] or [code].icns[/code] icons, [method set_native_icon] allows specifying different icons depending on the size the icon is displayed at. This size is determined by the operating system and user preferences (including the display scale factor). To use icons in other formats, use [method set_icon] instead.
+ [b]Note:[/b] Requires support for [constant FEATURE_NATIVE_ICON].
</description>
</method>
<method name="set_system_theme_change_callback">
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index 0092c445c1..e874b44cfc 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -409,9 +409,10 @@
<return type="Button" />
<param index="0" name="control" type="Control" />
<param index="1" name="title" type="String" />
+ <param index="2" name="shortcut" type="Shortcut" default="null" />
<description>
- Adds a control to the bottom panel (together with [b]Output[/b], [b]Debug[/b], [b]Animation[/b], etc.). Returns the button added to the tab bar. It's up to you to manage the button's visibility as needed.
- When your plugin is deactivated, make sure to remove your custom control with [method remove_control_from_bottom_panel] and free it with [method Node.queue_free].
+ Adds a control to the bottom panel (together with Output, Debug, Animation, etc). Returns a reference to the button added. It's up to you to hide/show the button when needed. When your plugin is deactivated, make sure to remove your custom control with [method remove_control_from_bottom_panel] and free it with [method Node.queue_free].
+ Optionally, you can specify a shortcut parameter. When pressed, this shortcut will toggle the bottom panel's visibility. See the default editor bottom panel shortcuts in the Editor Settings for inspiration. Per convention, they all use [kbd]Alt[/kbd] modifier.
</description>
</method>
<method name="add_control_to_container">
@@ -428,10 +429,12 @@
<return type="void" />
<param index="0" name="slot" type="int" enum="EditorPlugin.DockSlot" />
<param index="1" name="control" type="Control" />
+ <param index="2" name="shortcut" type="Shortcut" default="null" />
<description>
Adds the control to a specific dock slot (see [enum DockSlot] for options).
If the dock is repositioned and as long as the plugin is active, the editor will save the dock position on further sessions.
When your plugin is deactivated, make sure to remove your custom control with [method remove_control_from_docks] and free it with [method Node.queue_free].
+ Optionally, you can specify a shortcut parameter. When pressed, this shortcut will toggle the dock's visibility once it's moved to the bottom panel (this shortcut does not affect the dock otherwise). See the default editor bottom panel shortcuts in the Editor Settings for inspiration. Per convention, they all use [kbd]Alt[/kbd] modifier.
</description>
</method>
<method name="add_custom_type">
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index df8ebc12fa..2abd9974ca 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -517,12 +517,14 @@
</constant>
<constant name="WRITE" value="2" enum="ModeFlags">
Opens the file for write operations. The file is created if it does not exist, and truncated if it does.
+ [b]Note:[/b] When creating a file it must be in an already existing directory. To recursively create directories for a file path, see [method DirAccess.make_dir_recursive]).
</constant>
<constant name="READ_WRITE" value="3" enum="ModeFlags">
Opens the file for read and write operations. Does not truncate the file. The cursor is positioned at the beginning of the file.
</constant>
<constant name="WRITE_READ" value="7" enum="ModeFlags">
Opens the file for read and write operations. The file is created if it does not exist, and truncated if it does. The cursor is positioned at the beginning of the file.
+ [b]Note:[/b] When creating a file it must be in an already existing directory. To recursively create directories for a file path, see [method DirAccess.make_dir_recursive]).
</constant>
<constant name="COMPRESSION_FASTLZ" value="0" enum="CompressionMode">
Uses the [url=https://fastlz.org/]FastLZ[/url] compression method.
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index a1173d4628..c679838ec5 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -438,6 +438,12 @@
Returns [code]true[/code] if the given [param column] is selected.
</description>
</method>
+ <method name="is_visible_in_tree" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if [member visible] is [code]true[/code] and all its ancestors are also visible.
+ </description>
+ </method>
<method name="move_after">
<return type="void" />
<param index="0" name="item" type="TreeItem" />
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 4e735039f7..f711038fdf 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -1737,7 +1737,7 @@ def make_rst_index(grouped_classes: Dict[str, List[str]], dry_run: bool, output_
# Formatting helpers.
-RESERVED_FORMATTING_TAGS = ["i", "b", "u", "code", "kbd", "center", "url", "br"]
+RESERVED_FORMATTING_TAGS = ["i", "b", "u", "lb", "rb", "code", "kbd", "center", "url", "br"]
RESERVED_LAYOUT_TAGS = ["codeblocks"]
RESERVED_CODEBLOCK_TAGS = ["codeblock", "gdscript", "csharp"]
RESERVED_CROSSLINK_TAGS = [
@@ -2311,6 +2311,12 @@ def format_text_block(
escape_pre = True
tag_text = ""
+ elif tag_state.name == "lb":
+ tag_text = "\\["
+
+ elif tag_state.name == "rb":
+ tag_text = "\\]"
+
elif tag_state.name == "kbd":
tag_text = "`"
if tag_state.closing:
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 21cf54b4be..d8404ac8e4 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -3828,7 +3828,9 @@ bool RenderingDeviceDriverVulkan::pipeline_cache_create(const Vector<uint8_t> &p
// Parse.
{
- if (p_data.size() <= (int)sizeof(PipelineCacheHeader)) {
+ if (p_data.is_empty()) {
+ // No pre-existing cache, just create it.
+ } else if (p_data.size() <= (int)sizeof(PipelineCacheHeader)) {
WARN_PRINT("Invalid/corrupt pipelines cache.");
} else {
const PipelineCacheHeader *loaded_header = reinterpret_cast<const PipelineCacheHeader *>(p_data.ptr());
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index ee8b66cddf..dd5dc14136 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/io/resource_saver.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -1041,7 +1042,7 @@ void EditorAudioBuses::_rebuild_buses() {
EditorAudioBuses *EditorAudioBuses::register_editor() {
EditorAudioBuses *audio_buses = memnew(EditorAudioBuses);
- EditorNode::get_bottom_panel()->add_item(TTR("Audio"), audio_buses);
+ EditorNode::get_bottom_panel()->add_item(TTR("Audio"), audio_buses, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_audio_bottom_panel", TTR("Toggle Audio Bottom Panel"), KeyModifierMask::ALT | Key::A));
return audio_buses;
}
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index a6b16a245d..08719d6bf0 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -38,6 +38,7 @@
#include "scene/gui/tab_container.h"
#include "scene/main/window.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -48,6 +49,8 @@
EditorDockManager *EditorDockManager::singleton = nullptr;
+static const char *META_TOGGLE_SHORTCUT = "_toggle_shortcut";
+
void DockSplitContainer::_update_visibility() {
if (is_updating) {
return;
@@ -392,7 +395,10 @@ void EditorDockManager::_dock_move_selected_to_bottom() {
dock->call("_set_dock_horizontal", true);
bottom_docks.push_back(dock);
- EditorNode::get_bottom_panel()->add_item(dock->get_name(), dock, true);
+
+ // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
+ EditorNode::get_bottom_panel()->add_item(dock->get_name(), dock, dock->get_meta(META_TOGGLE_SHORTCUT), true);
+
dock_select_popup->hide();
update_dock_slots_visibility(true);
_edit_current();
@@ -663,7 +669,8 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
node->call("_set_dock_horizontal", true);
bottom_docks.push_back(node);
- EditorNode::get_bottom_panel()->add_item(node->get_name(), node, true);
+ // Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
+ EditorNode::get_bottom_panel()->add_item(node->get_name(), node, node->get_meta(META_TOGGLE_SHORTCUT), true);
}
}
@@ -730,8 +737,9 @@ void EditorDockManager::close_all_floating_docks() {
}
}
-void EditorDockManager::add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name) {
+void EditorDockManager::add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name, const Ref<Shortcut> &p_shortcut) {
ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
+ p_control->set_meta(META_TOGGLE_SHORTCUT, p_shortcut);
dock_slot[p_slot]->add_child(p_control);
if (!p_name.is_empty()) {
dock_slot[p_slot]->set_tab_title(dock_slot[p_slot]->get_tab_idx_from_control(p_control), p_name);
diff --git a/editor/editor_dock_manager.h b/editor/editor_dock_manager.h
index cc1a6634bc..193ccd6541 100644
--- a/editor/editor_dock_manager.h
+++ b/editor/editor_dock_manager.h
@@ -133,7 +133,7 @@ public:
void set_docks_visible(bool p_show);
bool are_docks_visible() const;
- void add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name = "");
+ void add_control_to_dock(DockSlot p_slot, Control *p_control, const String &p_name = "", const Ref<Shortcut> &p_shortcut = nullptr);
void remove_control_from_dock(Control *p_control);
EditorDockManager();
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 02c6925f14..83a7e115c9 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -68,7 +68,7 @@ Size2 EditorProperty::get_minimum_size() const {
Size2 ms;
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
- ms.height = font->get_height(font_size) + 4 * EDSCALE;
+ ms.height = label.is_empty() ? 0 : font->get_height(font_size) + 4 * EDSCALE;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -106,7 +106,7 @@ Size2 EditorProperty::get_minimum_size() const {
}
if (bottom_editor != nullptr && bottom_editor->is_visible()) {
- ms.height += get_theme_constant(SNAME("v_separation"));
+ ms.height += label.is_empty() ? 0 : get_theme_constant(SNAME("v_separation"));
Size2 bems = bottom_editor->get_combined_minimum_size();
//bems.width += get_constant("item_margin", "Tree");
ms.height += bems.height;
@@ -138,7 +138,7 @@ void EditorProperty::_notification(int p_what) {
int child_room = size.width * (1.0 - split_ratio);
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
- int height = font->get_height(font_size) + 4 * EDSCALE;
+ int height = label.is_empty() ? 0 : font->get_height(font_size) + 4 * EDSCALE;
bool no_children = true;
//compute room needed
@@ -176,9 +176,8 @@ void EditorProperty::_notification(int p_what) {
}
if (bottom_editor) {
- int m = 0; //get_constant("item_margin", "Tree");
-
- bottom_rect = Rect2(m, rect.size.height + get_theme_constant(SNAME("v_separation")), size.width - m, bottom_editor->get_combined_minimum_size().height);
+ int v_offset = label.is_empty() ? 0 : get_theme_constant(SNAME("v_separation"));
+ bottom_rect = Rect2(0, rect.size.height + v_offset, size.width, bottom_editor->get_combined_minimum_size().height);
}
if (keying) {
@@ -254,8 +253,13 @@ void EditorProperty::_notification(int p_what) {
size.height = label_reference->get_size().height;
}
- Ref<StyleBox> sb = get_theme_stylebox(selected ? SNAME("bg_selected") : SNAME("bg"));
- draw_style_box(sb, Rect2(Vector2(), size));
+ // Only draw the label if it's not empty.
+ if (label.is_empty()) {
+ size.height = 0;
+ } else {
+ Ref<StyleBox> sb = get_theme_stylebox(selected ? SNAME("bg_selected") : SNAME("bg"));
+ draw_style_box(sb, Rect2(Vector2(), size));
+ }
Ref<StyleBox> bg_stylebox = get_theme_stylebox(SNAME("child_bg"));
if (draw_top_bg && right_child_rect != Rect2()) {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 7de3f58997..041784d7da 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -338,6 +338,8 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
_editor_select_prev();
} else if (ED_IS_SHORTCUT("editor/command_palette", p_event)) {
_open_command_palette();
+ } else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) {
+ bottom_panel->toggle_last_opened_bottom_panel();
} else {
}
@@ -6582,6 +6584,7 @@ EditorNode::EditorNode() {
distraction_free->set_theme_type_variation("FlatMenuButton");
ED_SHORTCUT_AND_COMMAND("editor/distraction_free_mode", TTR("Distraction Free Mode"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F11);
ED_SHORTCUT_OVERRIDE("editor/distraction_free_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::D);
+ ED_SHORTCUT_AND_COMMAND("editor/toggle_last_opened_bottom_panel", TTR("Toggle Last Opened Bottom Panel"), KeyModifierMask::CMD_OR_CTRL | Key::J);
distraction_free->set_shortcut(ED_GET_SHORTCUT("editor/distraction_free_mode"));
distraction_free->set_tooltip_text(TTR("Toggle distraction-free mode."));
distraction_free->set_toggle_mode(true);
@@ -6993,7 +6996,7 @@ EditorNode::EditorNode() {
editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_UR, ImportDock::get_singleton(), TTR("Import"));
// FileSystem: Bottom left.
- editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_BR, FileSystemDock::get_singleton(), TTR("FileSystem"));
+ editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_LEFT_BR, FileSystemDock::get_singleton(), TTR("FileSystem"), ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_filesystem_bottom_panel", TTR("Toggle FileSystem Bottom Panel"), KeyModifierMask::ALT | Key::F));
// Inspector: Full height right.
editor_dock_manager->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, InspectorDock::get_singleton(), TTR("Inspector"));
@@ -7035,7 +7038,7 @@ EditorNode::EditorNode() {
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
log = memnew(EditorLog);
- Button *output_button = bottom_panel->add_item(TTR("Output"), log);
+ Button *output_button = bottom_panel->add_item(TTR("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTR("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O));
log->set_tool_button(output_button);
center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized));
diff --git a/editor/editor_plugin.compat.inc b/editor/editor_plugin.compat.inc
new file mode 100644
index 0000000000..7edf938604
--- /dev/null
+++ b/editor/editor_plugin.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* editor_plugin.compat.inc */
+/**************************************************************************/
+/* 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 DISABLE_DEPRECATED
+
+Button *EditorPlugin::_add_control_to_bottom_panel_compat_88081(Control *p_control, const String &p_title) {
+ return add_control_to_bottom_panel(p_control, p_title, nullptr);
+}
+
+void EditorPlugin::_add_control_to_dock_compat_88081(DockSlot p_slot, Control *p_control) {
+ return add_control_to_dock(p_slot, p_control, nullptr);
+}
+
+void EditorPlugin::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("add_control_to_bottom_panel", "control", "title"), &EditorPlugin::_add_control_to_bottom_panel_compat_88081);
+ ClassDB::bind_compatibility_method(D_METHOD("add_control_to_dock", "slot", "control"), &EditorPlugin::_add_control_to_dock_compat_88081);
+}
+
+#endif
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 7b5b084f09..67fe0c2e28 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "editor_plugin.h"
+#include "editor_plugin.compat.inc"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_dock_manager.h"
@@ -79,14 +80,14 @@ void EditorPlugin::remove_autoload_singleton(const String &p_name) {
EditorNode::get_singleton()->get_project_settings()->get_autoload_settings()->autoload_remove(p_name);
}
-Button *EditorPlugin::add_control_to_bottom_panel(Control *p_control, const String &p_title) {
+Button *EditorPlugin::add_control_to_bottom_panel(Control *p_control, const String &p_title, const Ref<Shortcut> &p_shortcut) {
ERR_FAIL_NULL_V(p_control, nullptr);
- return EditorNode::get_bottom_panel()->add_item(p_title, p_control);
+ return EditorNode::get_bottom_panel()->add_item(p_title, p_control, p_shortcut);
}
-void EditorPlugin::add_control_to_dock(DockSlot p_slot, Control *p_control) {
+void EditorPlugin::add_control_to_dock(DockSlot p_slot, Control *p_control, const Ref<Shortcut> &p_shortcut) {
ERR_FAIL_NULL(p_control);
- EditorDockManager::get_singleton()->add_control_to_dock(EditorDockManager::DockSlot(p_slot), p_control);
+ EditorDockManager::get_singleton()->add_control_to_dock(EditorDockManager::DockSlot(p_slot), p_control, String(), p_shortcut);
}
void EditorPlugin::remove_control_from_docks(Control *p_control) {
@@ -559,8 +560,8 @@ void EditorPlugin::_notification(int p_what) {
void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_control_to_container", "container", "control"), &EditorPlugin::add_control_to_container);
- ClassDB::bind_method(D_METHOD("add_control_to_bottom_panel", "control", "title"), &EditorPlugin::add_control_to_bottom_panel);
- ClassDB::bind_method(D_METHOD("add_control_to_dock", "slot", "control"), &EditorPlugin::add_control_to_dock);
+ ClassDB::bind_method(D_METHOD("add_control_to_bottom_panel", "control", "title", "shortcut"), &EditorPlugin::add_control_to_bottom_panel, DEFVAL(Ref<Shortcut>()));
+ ClassDB::bind_method(D_METHOD("add_control_to_dock", "slot", "control", "shortcut"), &EditorPlugin::add_control_to_dock, DEFVAL(Ref<Shortcut>()));
ClassDB::bind_method(D_METHOD("remove_control_from_docks", "control"), &EditorPlugin::remove_control_from_docks);
ClassDB::bind_method(D_METHOD("remove_control_from_bottom_panel", "control"), &EditorPlugin::remove_control_from_bottom_panel);
ClassDB::bind_method(D_METHOD("remove_control_from_container", "container", "control"), &EditorPlugin::remove_control_from_container);
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index a93955046a..7c28818b2f 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -67,6 +67,40 @@ class EditorPlugin : public Node {
void _editor_project_settings_changed();
#endif
+public:
+ enum CustomControlContainer {
+ CONTAINER_TOOLBAR,
+ CONTAINER_SPATIAL_EDITOR_MENU,
+ CONTAINER_SPATIAL_EDITOR_SIDE_LEFT,
+ CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT,
+ CONTAINER_SPATIAL_EDITOR_BOTTOM,
+ CONTAINER_CANVAS_EDITOR_MENU,
+ CONTAINER_CANVAS_EDITOR_SIDE_LEFT,
+ CONTAINER_CANVAS_EDITOR_SIDE_RIGHT,
+ CONTAINER_CANVAS_EDITOR_BOTTOM,
+ CONTAINER_INSPECTOR_BOTTOM,
+ CONTAINER_PROJECT_SETTING_TAB_LEFT,
+ CONTAINER_PROJECT_SETTING_TAB_RIGHT,
+ };
+
+ enum DockSlot {
+ DOCK_SLOT_LEFT_UL,
+ DOCK_SLOT_LEFT_BL,
+ DOCK_SLOT_LEFT_UR,
+ DOCK_SLOT_LEFT_BR,
+ DOCK_SLOT_RIGHT_UL,
+ DOCK_SLOT_RIGHT_BL,
+ DOCK_SLOT_RIGHT_UR,
+ DOCK_SLOT_RIGHT_BR,
+ DOCK_SLOT_MAX
+ };
+
+ enum AfterGUIInput {
+ AFTER_GUI_INPUT_PASS,
+ AFTER_GUI_INPUT_STOP,
+ AFTER_GUI_INPUT_CUSTOM,
+ };
+
protected:
void _notification(int p_what);
@@ -101,46 +135,19 @@ protected:
GDVIRTUAL0(_enable_plugin)
GDVIRTUAL0(_disable_plugin)
-public:
- enum CustomControlContainer {
- CONTAINER_TOOLBAR,
- CONTAINER_SPATIAL_EDITOR_MENU,
- CONTAINER_SPATIAL_EDITOR_SIDE_LEFT,
- CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT,
- CONTAINER_SPATIAL_EDITOR_BOTTOM,
- CONTAINER_CANVAS_EDITOR_MENU,
- CONTAINER_CANVAS_EDITOR_SIDE_LEFT,
- CONTAINER_CANVAS_EDITOR_SIDE_RIGHT,
- CONTAINER_CANVAS_EDITOR_BOTTOM,
- CONTAINER_INSPECTOR_BOTTOM,
- CONTAINER_PROJECT_SETTING_TAB_LEFT,
- CONTAINER_PROJECT_SETTING_TAB_RIGHT,
- };
-
- enum DockSlot {
- DOCK_SLOT_LEFT_UL,
- DOCK_SLOT_LEFT_BL,
- DOCK_SLOT_LEFT_UR,
- DOCK_SLOT_LEFT_BR,
- DOCK_SLOT_RIGHT_UL,
- DOCK_SLOT_RIGHT_BL,
- DOCK_SLOT_RIGHT_UR,
- DOCK_SLOT_RIGHT_BR,
- DOCK_SLOT_MAX
- };
-
- enum AfterGUIInput {
- AFTER_GUI_INPUT_PASS,
- AFTER_GUI_INPUT_STOP,
- AFTER_GUI_INPUT_CUSTOM
- };
+#ifndef DISABLE_DEPRECATED
+ Button *_add_control_to_bottom_panel_compat_88081(Control *p_control, const String &p_title);
+ void _add_control_to_dock_compat_88081(DockSlot p_slot, Control *p_control);
+ static void _bind_compatibility_methods();
+#endif
+public:
//TODO: send a resource for editing to the editor node?
void add_control_to_container(CustomControlContainer p_location, Control *p_control);
void remove_control_from_container(CustomControlContainer p_location, Control *p_control);
- Button *add_control_to_bottom_panel(Control *p_control, const String &p_title);
- void add_control_to_dock(DockSlot p_slot, Control *p_control);
+ Button *add_control_to_bottom_panel(Control *p_control, const String &p_title, const Ref<Shortcut> &p_shortcut = nullptr);
+ void add_control_to_dock(DockSlot p_slot, Control *p_control, const Ref<Shortcut> &p_shortcut = nullptr);
void remove_control_from_docks(Control *p_control);
void remove_control_from_bottom_panel(Control *p_control);
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 73a0768a72..01455fec17 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3869,27 +3869,27 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
} break;
case Variant::PACKED_BYTE_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_BYTE_ARRAY);
+ editor->setup(Variant::PACKED_BYTE_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_INT32_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_INT32_ARRAY);
+ editor->setup(Variant::PACKED_INT32_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_INT64_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_INT64_ARRAY);
+ editor->setup(Variant::PACKED_INT64_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_FLOAT32_ARRAY);
+ editor->setup(Variant::PACKED_FLOAT32_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_FLOAT64_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_FLOAT64_ARRAY);
+ editor->setup(Variant::PACKED_FLOAT64_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_STRING_ARRAY: {
@@ -3899,17 +3899,17 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
} break;
case Variant::PACKED_VECTOR2_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_VECTOR2_ARRAY);
+ editor->setup(Variant::PACKED_VECTOR2_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_VECTOR3_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_VECTOR3_ARRAY);
+ editor->setup(Variant::PACKED_VECTOR3_ARRAY, p_hint_text);
return editor;
} break;
case Variant::PACKED_COLOR_ARRAY: {
EditorPropertyArray *editor = memnew(EditorPropertyArray);
- editor->setup(Variant::PACKED_COLOR_ARRAY);
+ editor->setup(Variant::PACKED_COLOR_ARRAY, p_hint_text);
return editor;
} break;
default: {
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index ab7e05b915..6a59d7a3a7 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -105,6 +105,8 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
EditorNode::get_top_split()->show();
}
}
+
+ last_opened_control = items[p_idx].control;
}
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
@@ -156,12 +158,13 @@ void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, c
}
}
-Button *EditorBottomPanel::add_item(String p_text, Control *p_item, bool p_at_front) {
+Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
tb->set_theme_type_variation("FlatMenuButton");
tb->connect("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
+ tb->set_shortcut(p_shortcut);
tb->set_toggle_mode(true);
tb->set_focus_mode(Control::FOCUS_NONE);
item_vbox->add_child(p_item);
@@ -221,6 +224,17 @@ void EditorBottomPanel::hide_bottom_panel() {
}
}
+void EditorBottomPanel::toggle_last_opened_bottom_panel() {
+ // Select by control instead of index, so that the last bottom panel is opened correctly
+ // if it's been reordered since.
+ if (last_opened_control) {
+ _switch_by_control(!last_opened_control->is_visible(), last_opened_control);
+ } else {
+ // Open the first panel in the list if no panel was opened this session.
+ _switch_to_item(true, 0);
+ }
+}
+
EditorBottomPanel::EditorBottomPanel() {
item_vbox = memnew(VBoxContainer);
add_child(item_vbox);
diff --git a/editor/gui/editor_bottom_panel.h b/editor/gui/editor_bottom_panel.h
index 54b3a1319d..95c767dae5 100644
--- a/editor/gui/editor_bottom_panel.h
+++ b/editor/gui/editor_bottom_panel.h
@@ -57,6 +57,7 @@ class EditorBottomPanel : public PanelContainer {
EditorToaster *editor_toaster = nullptr;
LinkButton *version_btn = nullptr;
Button *expand_button = nullptr;
+ Control *last_opened_control = nullptr;
void _switch_by_control(bool p_visible, Control *p_control);
void _switch_to_item(bool p_visible, int p_idx);
@@ -72,11 +73,12 @@ public:
void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
- Button *add_item(String p_text, Control *p_item, bool p_at_front = false);
+ Button *add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut = nullptr, bool p_at_front = false);
void remove_item(Control *p_item);
void make_item_visible(Control *p_item, bool p_visible = true);
void move_item_to_end(Control *p_item);
void hide_bottom_panel();
+ void toggle_last_opened_bottom_panel();
EditorBottomPanel();
};
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 7ee155b478..c03dc7efde 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -35,6 +35,7 @@
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
@@ -2275,7 +2276,7 @@ void AnimationPlayerEditorPlugin::make_visible(bool p_visible) {
AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
anim_editor = memnew(AnimationPlayerEditor(this));
- EditorNode::get_bottom_panel()->add_item(TTR("Animation"), anim_editor);
+ EditorNode::get_bottom_panel()->add_item(TTR("Animation"), anim_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_bottom_panel", TTR("Toggle Animation Bottom Panel"), KeyModifierMask::ALT | Key::N));
}
AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index 3e9eebdeab..074fea49bd 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -39,6 +39,7 @@
#include "core/io/resource_loader.h"
#include "core/math/delaunay_2d.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
@@ -316,7 +317,7 @@ AnimationTreeEditorPlugin::AnimationTreeEditorPlugin() {
anim_tree_editor = memnew(AnimationTreeEditor);
anim_tree_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
- button = EditorNode::get_bottom_panel()->add_item(TTR("AnimationTree"), anim_tree_editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("AnimationTree"), anim_tree_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_tree_bottom_panel", TTR("Toggle AnimationTree Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index 459c5e8b31..13bdc366bf 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -680,7 +680,7 @@ void EditorAssetLibrary::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("asset_library") &&
- !EditorSettings::get_singleton()->check_changed_settings_in_group("netweork")) {
+ !EditorSettings::get_singleton()->check_changed_settings_in_group("network")) {
break;
}
diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp
index da7b256572..2dc43098f7 100644
--- a/editor/plugins/debugger_editor_plugin.cpp
+++ b/editor/plugins/debugger_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/editor_debugger_server.h"
#include "editor/debugger/editor_file_server.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/gui/editor_bottom_panel.h"
@@ -55,7 +56,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
file_server = memnew(EditorFileServer);
EditorDebuggerNode *debugger = memnew(EditorDebuggerNode);
- Button *db = EditorNode::get_bottom_panel()->add_item(TTR("Debugger"), debugger);
+ Button *db = EditorNode::get_bottom_panel()->add_item(TTR("Debugger"), debugger, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_debugger_bottom_panel", TTR("Toggle Debugger Bottom Panel"), KeyModifierMask::ALT | Key::D));
debugger->set_tool_button(db);
// Main editor debug menu.
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index aa0069979c..0d345a196b 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -4481,23 +4481,14 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
if (d.has("type") && (String(d["type"]) == "files")) {
Vector<String> files = d["files"];
- List<String> scene_extensions;
- ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions);
- List<String> mesh_extensions;
- ResourceLoader::get_recognized_extensions_for_type("Mesh", &mesh_extensions);
- List<String> material_extensions;
- ResourceLoader::get_recognized_extensions_for_type("Material", &material_extensions);
- List<String> texture_extensions;
- ResourceLoader::get_recognized_extensions_for_type("Texture", &texture_extensions);
-
+ // Check if at least one of the dragged files is a mesh, material, texture or scene.
for (int i = 0; i < files.size(); i++) {
- String extension = files[i].get_extension().to_lower();
+ bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene");
+ bool is_mesh = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Mesh");
+ bool is_material = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Material");
+ bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture");
- // Check if dragged files with mesh or scene extension can be created at least once.
- if (mesh_extensions.find(extension) ||
- scene_extensions.find(extension) ||
- material_extensions.find(extension) ||
- texture_extensions.find(extension)) {
+ if (is_mesh || is_scene || is_material || is_texture) {
Ref<Resource> res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp
index 9aac87da35..a624c47e3e 100644
--- a/editor/plugins/resource_preloader_editor_plugin.cpp
+++ b/editor/plugins/resource_preloader_editor_plugin.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -423,7 +424,7 @@ ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin() {
preloader_editor = memnew(ResourcePreloaderEditor);
preloader_editor->set_custom_minimum_size(Size2(0, 250) * EDSCALE);
- button = EditorNode::get_bottom_panel()->add_item("ResourcePreloader", preloader_editor);
+ button = EditorNode::get_bottom_panel()->add_item("ResourcePreloader", preloader_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_resource_preloader_bottom_panel", TTR("Toggle ResourcePreloader Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index e986510895..edec4af094 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -4197,7 +4197,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true));
add_child(find_in_files_dialog);
find_in_files = memnew(FindInFilesPanel);
- find_in_files_button = EditorNode::get_bottom_panel()->add_item(TTR("Search Results"), find_in_files);
+ find_in_files_button = EditorNode::get_bottom_panel()->add_item(TTR("Search Results"), find_in_files, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_search_results_bottom_panel", TTR("Toggle Search Results Bottom Panel")));
find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected));
find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files));
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 213a332bab..222d010a7a 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -728,7 +728,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
empty.instantiate();
shader_tabs->add_theme_style_override("panel", empty);
- button = EditorNode::get_bottom_panel()->add_item(TTR("Shader Editor"), window_wrapper);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("Shader Editor"), window_wrapper, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTR("Toggle Shader Editor Bottom Panel"), KeyModifierMask::ALT | Key::S));
shader_create_dialog = memnew(ShaderCreateDialog);
vb->add_child(shader_create_dialog);
diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp
index e127007d0c..3e025f8ba0 100644
--- a/editor/plugins/shader_file_editor_plugin.cpp
+++ b/editor/plugins/shader_file_editor_plugin.cpp
@@ -34,8 +34,8 @@
#include "core/io/resource_saver.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/themes/editor_scale.h"
@@ -323,7 +323,7 @@ ShaderFileEditorPlugin::ShaderFileEditorPlugin() {
shader_editor = memnew(ShaderFileEditor);
shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
- button = EditorNode::get_bottom_panel()->add_item(TTR("ShaderFile"), shader_editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("ShaderFile"), shader_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_file_bottom_panel", TTR("Toggle ShaderFile Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 9c1170492b..3055e178ba 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -525,18 +526,21 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
split_sheet_offset_x->set_max(size.x);
split_sheet_offset_y->set_max(size.y);
- // Different texture, reset to 4x4.
- dominant_param = PARAM_FRAME_COUNT;
- updating_split_settings = true;
- split_sheet_h->set_value(4);
- split_sheet_v->set_value(4);
- split_sheet_size_x->set_value(size.x / 4);
- split_sheet_size_y->set_value(size.y / 4);
- split_sheet_sep_x->set_value(0);
- split_sheet_sep_y->set_value(0);
- split_sheet_offset_x->set_value(0);
- split_sheet_offset_y->set_value(0);
- updating_split_settings = false;
+ if (size != previous_texture_size) {
+ // Different texture, reset to 4x4.
+ dominant_param = PARAM_FRAME_COUNT;
+ updating_split_settings = true;
+ split_sheet_h->set_value(4);
+ split_sheet_v->set_value(4);
+ split_sheet_size_x->set_value(size.x / 4);
+ split_sheet_size_y->set_value(size.y / 4);
+ split_sheet_sep_x->set_value(0);
+ split_sheet_sep_y->set_value(0);
+ split_sheet_offset_x->set_value(0);
+ split_sheet_offset_y->set_value(0);
+ updating_split_settings = false;
+ }
+ previous_texture_size = size;
// Reset zoom.
_sheet_zoom_reset();
@@ -2337,7 +2341,7 @@ void SpriteFramesEditorPlugin::make_visible(bool p_visible) {
SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {
frames_editor = memnew(SpriteFramesEditor);
frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
- button = EditorNode::get_bottom_panel()->add_item(TTR("SpriteFrames"), frames_editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("SpriteFrames"), frames_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_sprite_frames_bottom_panel", TTR("Toggle SpriteFrames Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index 730dddade1..2d0e43be1e 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -167,6 +167,8 @@ class SpriteFramesEditor : public HSplitContainer {
bool frames_need_sort = false;
int last_frame_selected = 0;
+ Size2i previous_texture_size;
+
float scale_ratio;
int thumbnail_default_size;
float thumbnail_zoom;
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 92f107f369..8ed00cf542 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -31,6 +31,7 @@
#include "theme_editor_plugin.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_help.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_picker.h"
@@ -3844,6 +3845,6 @@ ThemeEditorPlugin::ThemeEditorPlugin() {
theme_editor->plugin = this;
theme_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
- button = EditorNode::get_bottom_panel()->add_item(TTR("Theme"), theme_editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("Theme"), theme_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_theme_bottom_panel", TTR("Toggle Theme Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
index fb31ace2e0..ed21a29487 100644
--- a/editor/plugins/tiles/tiles_editor_plugin.cpp
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/os/mutex.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -511,7 +512,7 @@ TileMapEditorPlugin::TileMapEditorPlugin() {
editor->connect("change_selected_layer_request", callable_mp(this, &TileMapEditorPlugin::_select_layer));
editor->hide();
- button = EditorNode::get_bottom_panel()->add_item(TTR("TileMap"), editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("TileMap"), editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_tile_map_bottom_panel", TTR("Toggle TileMap Bottom Panel")));
button->hide();
}
@@ -562,7 +563,7 @@ TileSetEditorPlugin::TileSetEditorPlugin() {
editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
editor->hide();
- button = EditorNode::get_bottom_panel()->add_item(TTR("TileSet"), editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("TileSet"), editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_tile_set_bottom_panel", TTR("Toggle TileSet Bottom Panel")));
button->hide();
}
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 86c7e31740..a54ffd4ee8 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "core/os/time.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_dock_manager.h"
#include "editor/editor_file_system.h"
#include "editor/editor_interface.h"
@@ -913,7 +914,7 @@ void VersionControlEditorPlugin::fetch_available_vcs_plugin_names() {
void VersionControlEditorPlugin::register_editor() {
EditorDockManager::get_singleton()->add_control_to_dock(EditorDockManager::DOCK_SLOT_RIGHT_UL, version_commit_dock);
- version_control_dock_button = EditorNode::get_bottom_panel()->add_item(TTR("Version Control"), version_control_dock);
+ version_control_dock_button = EditorNode::get_bottom_panel()->add_item(TTR("Version Control"), version_control_dock, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_version_control_bottom_panel", TTR("Toggle Version Control Bottom Panel")));
_set_vcs_ui_state(true);
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 7617359823..02e2a9f487 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -818,6 +818,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
}
hb->add_theme_constant_override("separation", 7 * EDSCALE);
+ // Default value button/property editor.
Variant default_value;
if (valid_left && !port_left_used) {
@@ -2744,6 +2745,8 @@ void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node,
Variant value = vs_node->get_input_port_default_value(p_port);
edited_property_holder->set_edited_property(value);
+ editing_node = p_node;
+ editing_port = p_port;
if (property_editor) {
property_editor->disconnect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited));
@@ -2751,29 +2754,49 @@ void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node,
}
// TODO: Define these properties with actual PropertyInfo and feed it to the property editor widget.
- property_editor = EditorInspector::instantiate_property_editor(edited_property_holder.ptr(), value.get_type(), "edited_property", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
- if (property_editor) {
- property_editor->set_object_and_property(edited_property_holder.ptr(), "edited_property");
- property_editor->update_property();
- property_editor->set_name_split_ratio(0);
- property_editor_popup->add_child(property_editor);
+ property_editor = EditorInspector::instantiate_property_editor(edited_property_holder.ptr(), value.get_type(), "edited_property", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE, true);
+ ERR_FAIL_NULL_MSG(property_editor, "Failed to create property editor for type: " + Variant::get_type_name(value.get_type()));
+
+ // Determine the best size for the popup based on the property type.
+ // This is done here, since the property editors are also used in the inspector where they have different layout requirements, so we can't just change their default minimum size.
+ Size2 popup_pref_size;
+ switch (value.get_type()) {
+ case Variant::VECTOR3:
+ case Variant::BASIS:
+ popup_pref_size.width = 320;
+ break;
+ case Variant::VECTOR4:
+ case Variant::QUATERNION:
+ case Variant::PLANE:
+ case Variant::TRANSFORM2D:
+ case Variant::TRANSFORM3D:
+ case Variant::PROJECTION:
+ popup_pref_size.width = 480;
+ break;
+ default:
+ popup_pref_size.width = 180;
+ break;
+ }
+ property_editor_popup->set_min_size(popup_pref_size);
- property_editor->connect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited));
+ property_editor->set_object_and_property(edited_property_holder.ptr(), "edited_property");
+ property_editor->update_property();
+ property_editor->set_name_split_ratio(0);
+ property_editor_popup->add_child(property_editor);
- Button *button = Object::cast_to<Button>(p_button);
- if (button) {
- property_editor_popup->set_position(button->get_screen_position() + Vector2(0, button->get_size().height) * graph->get_zoom());
- }
- property_editor_popup->reset_size();
- if (button) {
- property_editor_popup->popup();
- } else {
- property_editor_popup->popup_centered_ratio();
- }
- }
+ property_editor->connect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited));
- editing_node = p_node;
- editing_port = p_port;
+ Button *button = Object::cast_to<Button>(p_button);
+ if (button) {
+ property_editor_popup->set_position(button->get_screen_position() + Vector2(0, button->get_size().height) * graph->get_zoom());
+ }
+ property_editor_popup->reset_size();
+ if (button) {
+ property_editor_popup->popup();
+ } else {
+ property_editor_popup->popup_centered_ratio();
+ }
+ property_editor->select(0); // Focus the first focusable control.
}
void VisualShaderEditor::_set_custom_node_option(int p_index, int p_node, int p_op) {
@@ -6515,7 +6538,7 @@ VisualShaderEditor::VisualShaderEditor() {
graph_plugin->set_editor(this);
property_editor_popup = memnew(PopupPanel);
- property_editor_popup->set_min_size(Size2(180, 0) * EDSCALE);
+ property_editor_popup->set_min_size(Size2(360, 0) * EDSCALE);
add_child(property_editor_popup);
edited_property_holder.instantiate();
diff --git a/editor/shader_globals_editor.cpp b/editor/shader_globals_editor.cpp
index 97f6ce2215..86a78d813e 100644
--- a/editor/shader_globals_editor.cpp
+++ b/editor/shader_globals_editor.cpp
@@ -398,6 +398,8 @@ void ShaderGlobalsEditor::_variable_added() {
undo_redo->add_do_method(this, "_changed");
undo_redo->add_undo_method(this, "_changed");
undo_redo->commit_action();
+
+ variable_name->clear();
}
void ShaderGlobalsEditor::_variable_deleted(const String &p_variable) {
@@ -455,6 +457,7 @@ ShaderGlobalsEditor::ShaderGlobalsEditor() {
variable_name->set_h_size_flags(SIZE_EXPAND_FILL);
variable_name->set_clear_button_enabled(true);
variable_name->connect("text_changed", callable_mp(this, &ShaderGlobalsEditor::_variable_name_text_changed));
+ variable_name->connect("text_submitted", callable_mp(this, &ShaderGlobalsEditor::_variable_added).unbind(1));
add_menu_hb->add_child(variable_name);
diff --git a/main/main.cpp b/main/main.cpp
index 91ccbe6766..ec66d47901 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2885,7 +2885,7 @@ Error Main::setup2() {
}
#if defined(TOOLS_ENABLED) && defined(MACOS_ENABLED)
- if (OS::get_singleton()->get_bundle_icon_path().is_empty()) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_ICON) && OS::get_singleton()->get_bundle_icon_path().is_empty()) {
Ref<Image> icon = memnew(Image(app_icon_png));
DisplayServer::get_singleton()->set_icon(icon);
}
@@ -3802,7 +3802,7 @@ bool Main::start() {
#ifdef MACOS_ENABLED
String mac_icon_path = GLOBAL_GET("application/config/macos_native_icon");
- if (!mac_icon_path.is_empty()) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_ICON) && !mac_icon_path.is_empty()) {
DisplayServer::get_singleton()->set_native_icon(mac_icon_path);
has_icon = true;
}
@@ -3810,14 +3810,14 @@ bool Main::start() {
#ifdef WINDOWS_ENABLED
String win_icon_path = GLOBAL_GET("application/config/windows_native_icon");
- if (!win_icon_path.is_empty()) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_ICON) && !win_icon_path.is_empty()) {
DisplayServer::get_singleton()->set_native_icon(win_icon_path);
has_icon = true;
}
#endif
String icon_path = GLOBAL_GET("application/config/icon");
- if ((!icon_path.is_empty()) && (!has_icon)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_ICON) && !icon_path.is_empty() && !has_icon) {
Ref<Image> icon;
icon.instantiate();
if (ImageLoader::load_image(icon_path, icon) == OK) {
@@ -3850,7 +3850,7 @@ bool Main::start() {
#endif
}
- if (!has_icon && OS::get_singleton()->get_bundle_icon_path().is_empty()) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_ICON) && !has_icon && OS::get_singleton()->get_bundle_icon_path().is_empty()) {
Ref<Image> icon = memnew(Image(app_icon_png));
DisplayServer::get_singleton()->set_icon(icon);
}
diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected
index 1c36c47246..49e871e203 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -231,3 +231,12 @@ GH-89024
Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/push_meta/arguments': size changed value in new API, from 1 to 2.
Added optional argument. Compatibility method registered.
+
+
+GH-88081
+--------
+Validate extension JSON: Error: Field 'classes/EditorPlugin/methods/add_control_to_bottom_panel/arguments': size changed value in new API, from 2 to 3.
+Validate extension JSON: Error: Field 'classes/EditorPlugin/methods/add_control_to_dock/arguments': size changed value in new API, from 2 to 3.
+
+Added optional argument to add_control_to_bottom_panel and add_control_to_dock to specify a shortcut that toggles the bottom panel/dock's visibility.
+Compatibility method registered.
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 7ececce613..ddf506216e 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -333,20 +333,22 @@
<annotation name="@export_color_no_alpha">
<return type="void" />
<description>
- Export a [Color] property without allowing its transparency ([member Color.a]) to be edited.
+ Export a [Color], [Array][lb][Color][rb], or [PackedColorArray] property without allowing its transparency ([member Color.a]) to be edited.
See also [constant PROPERTY_HINT_COLOR_NO_ALPHA].
[codeblock]
@export_color_no_alpha var dye_color: Color
+ @export_color_no_alpha var dye_colors: Array[Color]
[/codeblock]
</description>
</annotation>
<annotation name="@export_dir">
<return type="void" />
<description>
- Export a [String] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem.
+ Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem.
See also [constant PROPERTY_HINT_DIR].
[codeblock]
@export_dir var sprite_folder_path: String
+ @export_dir var sprite_folder_paths: Array[String]
[/codeblock]
</description>
</annotation>
@@ -354,12 +356,15 @@
<return type="void" />
<param index="0" name="names" type="String" />
<description>
- Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored.
+ Export an [int], [String], [Array][lb][int][rb], [Array][lb][String][rb], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], or [PackedStringArray] property as an enumerated list of options (or an array of options). If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored.
See also [constant PROPERTY_HINT_ENUM].
[codeblock]
@export_enum("Warrior", "Magician", "Thief") var character_class: int
@export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int
@export_enum("Rebecca", "Mary", "Leah") var character_name: String
+
+ @export_enum("Sword", "Spear", "Mace") var character_items: Array[int]
+ @export_enum("double_jump", "climb", "dash") var character_skills: Array[String]
[/codeblock]
If you want to set an initial value, you must specify it explicitly:
[codeblock]
@@ -369,6 +374,9 @@
[codeblock]
enum CharacterName {REBECCA, MARY, LEAH}
@export var character_name: CharacterName
+
+ enum CharacterItem {SWORD, SPEAR, MACE}
+ @export var character_items: Array[CharacterItem]
[/codeblock]
</description>
</annotation>
@@ -382,6 +390,7 @@
@export_exp_easing var transition_speed
@export_exp_easing("attenuation") var fading_attenuation
@export_exp_easing("positive_only") var effect_power
+ @export_exp_easing var speeds: Array[float]
[/codeblock]
</description>
</annotation>
@@ -389,12 +398,13 @@
<return type="void" />
<param index="0" name="filter" type="String" default="&quot;&quot;" />
<description>
- Export a [String] property as a path to a file. The path will be limited to the project folder and its subfolders. See [annotation @export_global_file] to allow picking from the entire filesystem.
+ Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as a path to a file. The path will be limited to the project folder and its subfolders. See [annotation @export_global_file] to allow picking from the entire filesystem.
If [param filter] is provided, only matching files will be available for picking.
See also [constant PROPERTY_HINT_FILE].
[codeblock]
@export_file var sound_effect_path: String
@export_file("*.txt") var notes_path: String
+ @export_file var level_paths: Array[String]
[/codeblock]
</description>
</annotation>
@@ -421,6 +431,10 @@
[codeblock]
@export_flags("A:16", "B", "C") var x
[/codeblock]
+ You can also use the annotation on [Array][lb][int][rb], [PackedByteArray], [PackedInt32Array], and [PackedInt64Array]
+ [codeblock]
+ @export_flags("Fire", "Water", "Earth", "Wind") var phase_elements: Array[int]
+ [/codeblock]
</description>
</annotation>
<annotation name="@export_flags_2d_navigation">
@@ -430,6 +444,7 @@
See also [constant PROPERTY_HINT_LAYERS_2D_NAVIGATION].
[codeblock]
@export_flags_2d_navigation var navigation_layers: int
+ @export_flags_2d_navigation var navigation_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -440,6 +455,7 @@
See also [constant PROPERTY_HINT_LAYERS_2D_PHYSICS].
[codeblock]
@export_flags_2d_physics var physics_layers: int
+ @export_flags_2d_physics var physics_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -450,6 +466,7 @@
See also [constant PROPERTY_HINT_LAYERS_2D_RENDER].
[codeblock]
@export_flags_2d_render var render_layers: int
+ @export_flags_2d_render var render_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -460,6 +477,7 @@
See also [constant PROPERTY_HINT_LAYERS_3D_NAVIGATION].
[codeblock]
@export_flags_3d_navigation var navigation_layers: int
+ @export_flags_3d_navigation var navigation_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -470,6 +488,7 @@
See also [constant PROPERTY_HINT_LAYERS_3D_PHYSICS].
[codeblock]
@export_flags_3d_physics var physics_layers: int
+ @export_flags_3d_physics var physics_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -480,6 +499,7 @@
See also [constant PROPERTY_HINT_LAYERS_3D_RENDER].
[codeblock]
@export_flags_3d_render var render_layers: int
+ @export_flags_3d_render var render_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
@@ -490,16 +510,18 @@
See also [constant PROPERTY_HINT_LAYERS_AVOIDANCE].
[codeblock]
@export_flags_avoidance var avoidance_layers: int
+ @export_flags_avoidance var avoidance_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_global_dir">
<return type="void" />
<description>
- Export a [String] property as an absolute path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders.
+ Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as an absolute path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders.
See also [constant PROPERTY_HINT_GLOBAL_DIR].
[codeblock]
@export_global_dir var sprite_folder_path: String
+ @export_global_dir var sprite_folder_paths: Array[String]
[/codeblock]
</description>
</annotation>
@@ -507,12 +529,13 @@
<return type="void" />
<param index="0" name="filter" type="String" default="&quot;&quot;" />
<description>
- Export a [String] property as an absolute path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders.
+ Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as an absolute path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders.
If [param filter] is provided, only matching files will be available for picking.
See also [constant PROPERTY_HINT_GLOBAL_FILE].
[codeblock]
@export_global_file var sound_effect_path: String
@export_global_file("*.txt") var notes_path: String
+ @export_global_file var multiple_paths: Array[String]
[/codeblock]
</description>
</annotation>
@@ -542,10 +565,11 @@
<annotation name="@export_multiline">
<return type="void" />
<description>
- Export a [String] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property.
+ Export a [String], [Array][lb][String][rb], [PackedStringArray], [Dictionary] or [Array][lb][Dictionary][rb] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property.
See also [constant PROPERTY_HINT_MULTILINE_TEXT].
[codeblock]
@export_multiline var character_biography
+ @export_multiline var npc_dialogs: Array[String]
[/codeblock]
</description>
</annotation>
@@ -553,10 +577,11 @@
<return type="void" />
<param index="0" name="type" type="String" default="&quot;&quot;" />
<description>
- Export a [NodePath] property with a filter for allowed node types.
+ Export a [NodePath] or [Array][lb][NodePath][rb] property with a filter for allowed node types.
See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
[codeblock]
@export_node_path("Button", "TouchScreenButton") var some_button
+ @export_node_path("Button", "TouchScreenButton") var many_buttons: Array[NodePath]
[/codeblock]
[b]Note:[/b] The type must be a native class or a globally registered script (using the [code]class_name[/code] keyword) that inherits [Node].
</description>
@@ -565,10 +590,11 @@
<return type="void" />
<param index="0" name="placeholder" type="String" />
<description>
- Export a [String] property with a placeholder text displayed in the editor widget when no value is present.
+ Export a [String], [Array][lb][String][rb], or [PackedStringArray] property with a placeholder text displayed in the editor widget when no value is present.
See also [constant PROPERTY_HINT_PLACEHOLDER_TEXT].
[codeblock]
@export_placeholder("Name in lowercase") var character_id: String
+ @export_placeholder("Name in lowercase") var friend_ids: Array[String]
[/codeblock]
</description>
</annotation>
@@ -579,7 +605,7 @@
<param index="2" name="step" type="float" default="1.0" />
<param index="3" name="extra_hints" type="String" default="&quot;&quot;" />
<description>
- Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [member EditorSettings.interface/inspector/default_float_step] setting.
+ Export an [int], [float], [Array][lb][int][rb], [Array][lb][float][rb], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], [PackedFloat32Array], or [PackedFloat64Array] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [member EditorSettings.interface/inspector/default_float_step] setting.
If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget.
Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
See also [constant PROPERTY_HINT_RANGE].
@@ -587,6 +613,7 @@
@export_range(0, 20) var number
@export_range(-10, 20) var number
@export_range(-10, 20, 0.2) var number: float
+ @export_range(0, 20) var numbers: Array[float]
@export_range(0, 100, 1, "or_greater") var power_percent
@export_range(0, 100, 1, "or_greater", "or_less") var health_delta
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 4d4eadf0fa..e27ff05721 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -4006,6 +4006,55 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
return true;
}
+static String _get_annotation_error_string(const StringName &p_annotation_name, const Vector<Variant::Type> &p_expected_types, const GDScriptParser::DataType &p_provided_type) {
+ Vector<String> types;
+ for (int i = 0; i < p_expected_types.size(); i++) {
+ const Variant::Type &type = p_expected_types[i];
+ types.push_back(Variant::get_type_name(type));
+ types.push_back("Array[" + Variant::get_type_name(type) + "]");
+ switch (type) {
+ case Variant::INT:
+ types.push_back("PackedByteArray");
+ types.push_back("PackedInt32Array");
+ types.push_back("PackedInt64Array");
+ break;
+ case Variant::FLOAT:
+ types.push_back("PackedFloat32Array");
+ types.push_back("PackedFloat64Array");
+ break;
+ case Variant::STRING:
+ types.push_back("PackedStringArray");
+ break;
+ case Variant::VECTOR2:
+ types.push_back("PackedVector2Array");
+ break;
+ case Variant::VECTOR3:
+ types.push_back("PackedVector3Array");
+ break;
+ case Variant::COLOR:
+ types.push_back("PackedColorArray");
+ break;
+ default:
+ break;
+ }
+ }
+
+ String string;
+ if (types.size() == 1) {
+ string = types[0].quote();
+ } else if (types.size() == 2) {
+ string = types[0].quote() + " or " + types[1].quote();
+ } else if (types.size() >= 3) {
+ string = types[0].quote();
+ for (int i = 1; i < types.size() - 1; i++) {
+ string += ", " + types[i].quote();
+ }
+ string += ", or " + types[types.size() - 1].quote();
+ }
+
+ return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string());
+}
+
template <PropertyHint t_hint, Variant::Type t_type>
bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
@@ -4090,6 +4139,26 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
// This is called after the analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
+
+ // Use initializer type if specified type is `Variant`.
+ if (export_type.is_variant() && variable->initializer != nullptr && variable->initializer->datatype.is_set()) {
+ export_type = variable->initializer->get_datatype();
+ export_type.type_source = DataType::INFERRED;
+ }
+
+ const Variant::Type original_export_type_builtin = export_type.builtin_type;
+
+ // Process array and packed array annotations on the element type.
+ bool is_array = false;
+ if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
+ is_array = true;
+ export_type = export_type.get_container_element_type(0);
+ } else if (export_type.is_typed_container_type()) {
+ is_array = true;
+ export_type = export_type.get_typed_container_type();
+ export_type.type_source = variable->datatype.type_source;
+ }
+
bool use_default_variable_type_check = true;
if (p_annotation->name == SNAME("@export_range")) {
@@ -4097,30 +4166,16 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.type = Variant::INT;
}
} else if (p_annotation->name == SNAME("@export_multiline")) {
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
- DataType inner_type = export_type.get_container_element_type(0);
- if (inner_type.builtin_type != Variant::STRING) {
- push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)", p_annotation->name.operator String(), inner_type.to_string()), variable);
- return false;
- }
+ use_default_variable_type_check = false;
- String hint_prefix = itos(inner_type.builtin_type) + "/" + itos(variable->export_info.hint);
- variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
- variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
- variable->export_info.type = Variant::ARRAY;
+ if (export_type.builtin_type != Variant::STRING && export_type.builtin_type != Variant::DICTIONARY) {
+ Vector<Variant::Type> expected_types = { Variant::STRING, Variant::DICTIONARY };
+ push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
+ return false;
+ }
- return true;
- } else if (export_type.builtin_type == Variant::DICTIONARY) {
+ if (export_type.builtin_type == Variant::DICTIONARY) {
variable->export_info.type = Variant::DICTIONARY;
-
- return true;
- } else if (export_type.builtin_type == Variant::PACKED_STRING_ARRAY) {
- String hint_prefix = itos(Variant::STRING) + "/" + itos(variable->export_info.hint);
- variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
- variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
- variable->export_info.type = Variant::PACKED_STRING_ARRAY;
-
- return true;
}
} else if (p_annotation->name == SNAME("@export")) {
use_default_variable_type_check = false;
@@ -4130,13 +4185,6 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
return false;
}
- bool is_array = false;
-
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
- export_type = export_type.get_container_element_type(0); // Use inner type for.
- is_array = true;
- }
-
if (export_type.is_variant() || export_type.has_no_type()) {
push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
return false;
@@ -4158,7 +4206,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.native_type;
} else {
- push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
break;
@@ -4172,7 +4220,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.to_string();
} else {
- push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
@@ -4223,24 +4271,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
} break;
default:
- push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
- push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), variable);
+ push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
-
- if (is_array) {
- String hint_prefix = itos(variable->export_info.type);
- if (variable->export_info.hint) {
- hint_prefix += "/" + itos(variable->export_info.hint);
- }
- variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
- variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
- variable->export_info.type = Variant::ARRAY;
- }
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;
@@ -4248,17 +4286,13 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) {
enum_type = Variant::STRING;
- } else if (export_type.is_variant() && variable->initializer != nullptr) {
- DataType initializer_type = variable->initializer->get_datatype();
- if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) {
- enum_type = Variant::STRING;
- }
}
variable->export_info.type = enum_type;
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) {
- push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable);
+ Vector<Variant::Type> expected_types = { Variant::INT, Variant::STRING };
+ push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
return false;
}
} else if (p_annotation->name == SNAME("@export_storage")) {
@@ -4274,12 +4308,23 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) {
// Allow float/int conversion.
if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) {
- push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)", p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable);
+ Vector<Variant::Type> expected_types = { t_type };
+ push_error(_get_annotation_error_string(p_annotation->name, expected_types, variable->get_datatype()), p_annotation);
return false;
}
}
}
+ if (is_array) {
+ String hint_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ hint_prefix += "/" + itos(variable->export_info.hint);
+ }
+ variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
+ variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
+ variable->export_info.type = original_export_type_builtin;
+ }
+
return true;
}
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.gd b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.gd
new file mode 100644
index 0000000000..9f1a52856d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.gd
@@ -0,0 +1,4 @@
+@export_enum("A", "B", "C") var x: Array[Color]
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.out b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.out
new file mode 100644
index 0000000000..1e0455b96d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_array_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+"@export_enum" annotation requires a variable of type "int", "Array[int]", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "String", "Array[String]", or "PackedStringArray", but type "Array[Color]" was given instead.
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.gd b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.gd
new file mode 100644
index 0000000000..5fe66e7cce
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.gd
@@ -0,0 +1,4 @@
+@export_enum("A", "B", "C") var x: Color
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.out b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.out
new file mode 100644
index 0000000000..43a08acc0a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_enum_wrong_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+"@export_enum" annotation requires a variable of type "int", "Array[int]", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "String", "Array[String]", or "PackedStringArray", but type "Color" was given instead.
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
new file mode 100644
index 0000000000..ddfb186aa4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
@@ -0,0 +1,86 @@
+const Utils = preload("../../utils.notest.gd")
+
+@export_dir var test_dir: Array[String]
+@export_dir var test_dir_packed: PackedStringArray
+@export_file var test_file: Array[String]
+@export_file var test_file_packed: PackedStringArray
+@export_global_dir var test_global_dir: Array[String]
+@export_global_dir var test_global_dir_packed: PackedStringArray
+@export_global_file var test_global_file: Array[String]
+@export_global_file var test_global_file_packed: PackedStringArray
+@export_flags("A", "B", "C") var test_bit_flag: Array[int]
+@export_flags("A", "B", "C") var test_bit_flag_packed_byte: PackedByteArray
+@export_flags("A", "B", "C") var test_bit_flag_packed32: PackedInt32Array
+@export_flags("A", "B", "C") var test_bit_flag_packed64: PackedInt64Array
+@export_flags_2d_navigation var test_bit_flag_2d_nav: Array[int]
+@export_flags_2d_navigation var test_bit_flag_2d_nav_packed_byte: PackedByteArray
+@export_flags_2d_navigation var test_bit_flag_2d_nav_packed32: PackedInt32Array
+@export_flags_2d_navigation var test_bit_flag_2d_nav_packed64: PackedInt64Array
+@export_flags_2d_physics var test_bit_flag_2d_phys: Array[int]
+@export_flags_2d_physics var test_bit_flag_2d_phys_packed_byte: PackedByteArray
+@export_flags_2d_physics var test_bit_flag_2d_phys_packed32: PackedInt32Array
+@export_flags_2d_physics var test_bit_flag_2d_phys_packed64: PackedInt64Array
+@export_flags_2d_render var test_bit_flag_2d_render: Array[int]
+@export_flags_2d_render var test_bit_flag_2d_render_packed_byte: PackedByteArray
+@export_flags_2d_render var test_bit_flag_2d_render_packed32: PackedInt32Array
+@export_flags_2d_render var test_bit_flag_2d_render_packed64: PackedInt64Array
+@export_flags_3d_navigation var test_bit_flag_3d_nav: Array[int]
+@export_flags_3d_navigation var test_bit_flag_3d_nav_packed_byte: PackedByteArray
+@export_flags_3d_navigation var test_bit_flag_3d_nav_packed32: PackedInt32Array
+@export_flags_3d_navigation var test_bit_flag_3d_nav_packed64: PackedInt64Array
+@export_flags_3d_physics var test_bit_flag_3d_phys: Array[int]
+@export_flags_3d_physics var test_bit_flag_3d_phys_packed_byte: PackedByteArray
+@export_flags_3d_physics var test_bit_flag_3d_phys_packed32: PackedInt32Array
+@export_flags_3d_physics var test_bit_flag_3d_phys_packed64: PackedInt64Array
+@export_flags_3d_render var test_bit_flag_3d_render: Array[int]
+@export_flags_3d_render var test_bit_flag_3d_render_packed_byte: PackedByteArray
+@export_flags_3d_render var test_bit_flag_3d_render_packed32: PackedInt32Array
+@export_flags_3d_render var test_bit_flag_3d_render_packed64: PackedInt64Array
+@export_multiline var test_multiline: Array[String]
+@export_multiline var test_multiline_packed: PackedStringArray
+@export_placeholder("Placeholder") var test_placeholder: Array[String]
+@export_placeholder("Placeholder") var test_placeholder_packed: PackedStringArray
+@export_range(1, 10) var test_range_int: Array[int]
+@export_range(1, 10) var test_range_int_packed_byte: PackedByteArray
+@export_range(1, 10) var test_range_int_packed32: PackedInt32Array
+@export_range(1, 10) var test_range_int_packed64: PackedInt64Array
+@export_range(1, 10, 0.01) var test_range_int_float_step: Array[int]
+@export_range(1.0, 10.0) var test_range_float: Array[float]
+@export_range(1.0, 10.0) var test_range_float_packed32: PackedFloat32Array
+@export_range(1.0, 10.0) var test_range_float_packed64: PackedFloat64Array
+@export_exp_easing var test_exp_easing: Array[float]
+@export_exp_easing var test_exp_easing_packed32: PackedFloat32Array
+@export_exp_easing var test_exp_easing_packed64: PackedFloat64Array
+@export_node_path var test_node_path: Array[NodePath]
+@export_color_no_alpha var test_color: Array[Color]
+@export_color_no_alpha var test_color_packed: PackedColorArray
+
+var temp_packed_byte_array: PackedByteArray
+var temp_packed_int32_array: PackedInt32Array
+var temp_packed_int64_array: PackedInt64Array
+var temp_packed_float32_array: PackedFloat32Array
+var temp_packed_float64_array: PackedFloat64Array
+var temp_packed_color_array: PackedColorArray
+var temp_packed_vector2_array: PackedVector2Array
+var temp_packed_vector3_array: PackedVector3Array
+
+@export var test_weak_packed_byte_array = temp_packed_byte_array
+@export var test_weak_packed_int32_array = temp_packed_int32_array
+@export var test_weak_packed_int64_array = temp_packed_int64_array
+@export var test_weak_packed_float32_array = temp_packed_float32_array
+@export var test_weak_packed_float64_array = temp_packed_float64_array
+@export var test_weak_packed_color_array = temp_packed_color_array
+@export var test_weak_packed_vector2_array = temp_packed_vector2_array
+@export var test_weak_packed_vector3_array = temp_packed_vector3_array
+
+@export_range(1, 10) var test_range_weak_packed_byte_array = temp_packed_byte_array
+@export_range(1, 10) var test_range_weak_packed_int32_array = temp_packed_int32_array
+@export_range(1, 10) var test_range_weak_packed_int64_array = temp_packed_int64_array
+@export_range(1, 10) var test_range_weak_packed_float32_array = temp_packed_float32_array
+@export_range(1, 10) var test_range_weak_packed_float64_array = temp_packed_float64_array
+@export_color_no_alpha var test_noalpha_weak_packed_color_array = temp_packed_color_array
+
+func test():
+ for property in get_property_list():
+ if str(property.name).begins_with("test_"):
+ Utils.print_property_extended_info(property)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
new file mode 100644
index 0000000000..00e75fcc43
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
@@ -0,0 +1,137 @@
+GDTEST_OK
+var test_dir: Array
+ hint=TYPE_STRING hint_string="String/DIR:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_dir_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/DIR:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_file: Array
+ hint=TYPE_STRING hint_string="String/FILE:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_file_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/FILE:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_global_dir: Array
+ hint=TYPE_STRING hint_string="String/GLOBAL_DIR:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_global_dir_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/GLOBAL_DIR:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_global_file: Array
+ hint=TYPE_STRING hint_string="String/GLOBAL_FILE:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_global_file_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/GLOBAL_FILE:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag: Array
+ hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/FLAGS:A,B,C" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_nav: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_nav_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_nav_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_nav_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_phys: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_phys_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_phys_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_phys_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_render: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_render_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_render_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_2d_render_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_2D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_nav: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_nav_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_nav_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_nav_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_NAVIGATION:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_phys: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_phys_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_phys_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_phys_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_PHYSICS:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_render: Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_render_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_render_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_bit_flag_3d_render_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/LAYERS_3D_RENDER:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_multiline: Array
+ hint=TYPE_STRING hint_string="String/MULTILINE_TEXT:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_multiline_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/MULTILINE_TEXT:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_placeholder: Array
+ hint=TYPE_STRING hint_string="String/PLACEHOLDER_TEXT:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE
+var test_placeholder_packed: PackedStringArray
+ hint=TYPE_STRING hint_string="String/PLACEHOLDER_TEXT:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_int: Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_int_packed_byte: PackedByteArray
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_int_packed32: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_int_packed64: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_int_float_step: Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_float: Array
+ hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_float_packed32: PackedFloat32Array
+ hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_float_packed64: PackedFloat64Array
+ hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_exp_easing: Array
+ hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_exp_easing_packed32: PackedFloat32Array
+ hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_exp_easing_packed64: PackedFloat64Array
+ hint=TYPE_STRING hint_string="float/EXP_EASING:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_node_path: Array
+ hint=TYPE_STRING hint_string="NodePath/NODE_PATH_VALID_TYPES:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_color: Array
+ hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_color_packed: PackedColorArray
+ hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_byte_array: PackedByteArray
+ hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_int32_array: PackedInt32Array
+ hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_int64_array: PackedInt64Array
+ hint=TYPE_STRING hint_string="int:int" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_float32_array: PackedFloat32Array
+ hint=TYPE_STRING hint_string="float:float" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_float64_array: PackedFloat64Array
+ hint=TYPE_STRING hint_string="float:float" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_color_array: PackedColorArray
+ hint=TYPE_STRING hint_string="Color:Color" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_vector2_array: PackedVector2Array
+ hint=TYPE_STRING hint_string="Vector2:Vector2" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_vector3_array: PackedVector3Array
+ hint=TYPE_STRING hint_string="Vector3:Vector3" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_weak_packed_byte_array: PackedByteArray
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_weak_packed_int32_array: PackedInt32Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_weak_packed_int64_array: PackedInt64Array
+ hint=TYPE_STRING hint_string="int/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_weak_packed_float32_array: PackedFloat32Array
+ hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_range_weak_packed_float64_array: PackedFloat64Array
+ hint=TYPE_STRING hint_string="float/RANGE:1,10" usage=DEFAULT|SCRIPT_VARIABLE
+var test_noalpha_weak_packed_color_array: PackedColorArray
+ hint=TYPE_STRING hint_string="Color/COLOR_NO_ALPHA:" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
index 4f2a43f4fe..7f0737f4db 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -1,14 +1,35 @@
const Utils = preload("../../utils.notest.gd")
@export_enum("Red", "Green", "Blue") var test_untyped
+@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
+
+var temp_array_int: Array[int]
+var temp_array_string: Array[String]
+var temp_packed_byte_array: PackedByteArray
+var temp_packed_int32_array: PackedInt32Array
+var temp_packed_int64_array: PackedInt64Array
+var temp_packed_string_array: PackedStringArray
+@export_enum("Red", "Green", "Blue") var test_weak_variant
@export_enum("Red", "Green", "Blue") var test_weak_int = 0
@export_enum("Red", "Green", "Blue") var test_weak_string = ""
+@export_enum("Red", "Green", "Blue") var test_weak_array_int = temp_array_int
+@export_enum("Red", "Green", "Blue") var test_weak_array_string = temp_array_string
+@export_enum("Red", "Green", "Blue") var test_weak_packed_byte_array = temp_packed_byte_array
+@export_enum("Red", "Green", "Blue") var test_weak_packed_int32_array = temp_packed_int32_array
+@export_enum("Red", "Green", "Blue") var test_weak_packed_int64_array = temp_packed_int64_array
+@export_enum("Red", "Green", "Blue") var test_weak_packed_string_array = temp_packed_string_array
+@export_enum("Red", "Green", "Blue") var test_hard_variant: Variant
@export_enum("Red", "Green", "Blue") var test_hard_int: int
@export_enum("Red", "Green", "Blue") var test_hard_string: String
+@export_enum("Red", "Green", "Blue") var test_hard_array_int: Array[int]
+@export_enum("Red", "Green", "Blue") var test_hard_array_string: Array[String]
-@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
+@export_enum("Red", "Green", "Blue") var test_variant_array_int: Variant = temp_array_int
+@export_enum("Red", "Green", "Blue") var test_variant_packed_int32_array: Variant = temp_packed_int32_array
+@export_enum("Red", "Green", "Blue") var test_variant_array_string: Variant = temp_array_string
+@export_enum("Red", "Green", "Blue") var test_variant_packed_string_array: Variant = temp_packed_string_array
func test():
for property in get_property_list():
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out
index 43f5e197ad..c87f9b17f0 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out
@@ -1,13 +1,41 @@
GDTEST_OK
var test_untyped: int = null
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_with_values: int = null
+ hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_variant: int = null
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_weak_int: int = 0
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_weak_string: String = ""
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_array_int: Array = Array[int]([])
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_array_string: Array = Array[String]([])
+ hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_byte_array: PackedByteArray = PackedByteArray()
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_int32_array: PackedInt32Array = PackedInt32Array()
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_int64_array: PackedInt64Array = PackedInt64Array()
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_weak_packed_string_array: PackedStringArray = PackedStringArray()
+ hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_variant: int = null
+ hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_hard_int: int = 0
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
var test_hard_string: String = ""
hint=ENUM hint_string="Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
-var test_with_values: int = null
- hint=ENUM hint_string="Red:10,Green:20,Blue:30" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_array_int: Array = Array[int]([])
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_hard_array_string: Array = Array[String]([])
+ hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_variant_array_int: Array = Array[int]([])
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_variant_packed_int32_array: PackedInt32Array = PackedInt32Array()
+ hint=TYPE_STRING hint_string="int/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_variant_array_string: Array = Array[String]([])
+ hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
+var test_variant_packed_string_array: PackedStringArray = PackedStringArray()
+ hint=TYPE_STRING hint_string="String/ENUM:Red,Green,Blue" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index baadcd4ee8..b3f9d0ca9c 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -24,4 +24,4 @@ var test_node_path: NodePath = NodePath("hello")
var test_node: Node = null
hint=NODE_TYPE hint_string="Node" usage=DEFAULT|SCRIPT_VARIABLE
var test_node_array: Array = Array[Node]([])
- hint=TYPE_STRING hint_string="24/34:Node" usage=DEFAULT|SCRIPT_VARIABLE
+ hint=TYPE_STRING hint_string="Object/NODE_TYPE:Node" usage=DEFAULT|SCRIPT_VARIABLE
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 31818c9d01..1cf46c179e 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -39,11 +39,46 @@ static func get_property_signature(property: Dictionary, base: Object = null, is
return result
+static func get_human_readable_hint_string(property: Dictionary) -> String:
+ if property.type >= TYPE_ARRAY and property.hint == PROPERTY_HINT_TYPE_STRING:
+ var type_hint_prefixes: String = ""
+ var hint_string: String = property.hint_string
+
+ while true:
+ if not hint_string.contains(":"):
+ push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ var elem_type_hint: String = hint_string.get_slice(":", 0)
+ hint_string = hint_string.substr(elem_type_hint.length() + 1)
+
+ var elem_type: int
+ var elem_hint: int
+
+ if elem_type_hint.is_valid_int():
+ elem_type = elem_type_hint.to_int()
+ type_hint_prefixes += type_string(elem_type) + ":"
+ else:
+ if elem_type_hint.count("/") != 1:
+ push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ elem_type = elem_type_hint.get_slice("/", 0).to_int()
+ elem_hint = elem_type_hint.get_slice("/", 1).to_int()
+ type_hint_prefixes += "%s/%s:" % [
+ type_string(elem_type),
+ get_property_hint_name(elem_hint).trim_prefix("PROPERTY_HINT_"),
+ ]
+
+ if elem_type < TYPE_ARRAY:
+ break
+
+ return type_hint_prefixes + hint_string
+
+ return property.hint_string
+
+
static func print_property_extended_info(property: Dictionary, base: Object = null, is_static: bool = false) -> void:
print(get_property_signature(property, base, is_static))
print(' hint=%s hint_string="%s" usage=%s' % [
get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
- str(property.hint_string).c_escape(),
+ get_human_readable_hint_string(property),
get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
])
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
index e8dfc3379c..599926ec99 100644
--- a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor_network_profiler.h"
#include "replication_editor.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_bottom_panel.h"
@@ -113,7 +114,7 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) {
MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
repl_editor = memnew(ReplicationEditor);
- button = EditorNode::get_bottom_panel()->add_item(TTR("Replication"), repl_editor);
+ button = EditorNode::get_bottom_panel()->add_item(TTR("Replication"), repl_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_replication_bottom_panel", TTR("Toggle Replication Bottom Panel")));
button->hide();
repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned));
debugger.instantiate();
diff --git a/modules/openxr/editor/openxr_editor_plugin.cpp b/modules/openxr/editor/openxr_editor_plugin.cpp
index f545e31073..f6b7f2dd0c 100644
--- a/modules/openxr/editor/openxr_editor_plugin.cpp
+++ b/modules/openxr/editor/openxr_editor_plugin.cpp
@@ -32,6 +32,7 @@
#include "../action_map/openxr_action_map.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_bottom_panel.h"
@@ -53,7 +54,7 @@ void OpenXREditorPlugin::make_visible(bool p_visible) {
OpenXREditorPlugin::OpenXREditorPlugin() {
action_map_editor = memnew(OpenXRActionMapEditor);
- EditorNode::get_bottom_panel()->add_item(TTR("OpenXR Action Map"), action_map_editor);
+ EditorNode::get_bottom_panel()->add_item(TTR("OpenXR Action Map"), action_map_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_openxr_action_map_bottom_panel", TTR("Toggle OpenXR Action Map Bottom Panel")));
#ifndef ANDROID_ENABLED
select_runtime = memnew(OpenXRSelectRuntime);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index db0352db01..74d22bc44f 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2408,9 +2408,22 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += template_err;
}
} else {
+ // Validate the custom gradle android source template.
+ bool android_source_template_valid = false;
+ const String android_source_template = p_preset->get("gradle_build/android_source_template");
+ if (!android_source_template.is_empty()) {
+ android_source_template_valid = FileAccess::exists(android_source_template);
+ if (!android_source_template_valid) {
+ err += TTR("Custom Android source template not found.") + "\n";
+ }
+ }
+
+ // Validate the installed build template.
bool installed_android_build_template = FileAccess::exists(ExportTemplateManager::get_android_build_directory(p_preset).path_join("build.gradle"));
if (!installed_android_build_template) {
- r_missing_templates = !exists_export_template("android_source.zip", &err);
+ if (!android_source_template_valid) {
+ r_missing_templates = !exists_export_template("android_source.zip", &err);
+ }
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
} else {
r_missing_templates = false;
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index fd4bcf92be..446fe5c7a1 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -36,8 +36,8 @@
#include "core/version.h"
#include "main/main.h"
-#ifdef DEBUG_ENABLED
-#define CRASH_HANDLER_ENABLED 1
+#ifndef DEBUG_ENABLED
+#undef CRASH_HANDLER_ENABLED
#endif
#ifdef CRASH_HANDLER_ENABLED
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 4856076436..27dec73b65 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -50,7 +50,7 @@ def get_opts():
BoolVariable("wayland", "Enable Wayland display", True),
BoolVariable("libdecor", "Enable libdecor support", True),
BoolVariable("touch", "Enable touch events", True),
- BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
+ BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", None),
]
@@ -488,14 +488,20 @@ def configure(env: "SConsEnvironment"):
if platform.system() == "Linux":
env.Append(LIBS=["dl"])
- if not env["execinfo"] and platform.libc_ver()[0] != "glibc":
+ if platform.libc_ver()[0] != "glibc":
# The default crash handler depends on glibc, so if the host uses
# a different libc (BSD libc, musl), fall back to libexecinfo.
- print("Note: Using `execinfo=yes` for the crash handler as required on platforms where glibc is missing.")
- env["execinfo"] = True
+ if not "execinfo" in env:
+ print("Note: Using `execinfo=yes` for the crash handler as required on platforms where glibc is missing.")
+ env["execinfo"] = True
- if env["execinfo"]:
- env.Append(LIBS=["execinfo"])
+ if env["execinfo"]:
+ env.Append(LIBS=["execinfo"])
+ env.Append(CPPDEFINES=["CRASH_HANDLER_ENABLED"])
+ else:
+ print("Note: Using `execinfo=no` disables the crash handler on platforms where glibc is missing.")
+ else:
+ env.Append(CPPDEFINES=["CRASH_HANDLER_ENABLED"])
if platform.system() == "FreeBSD":
env.Append(LINKFLAGS=["-lkvm"])
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 528c688a9c..d552dd7b8a 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -193,18 +193,29 @@ void DisplayServerWayland::_show_window() {
bool DisplayServerWayland::has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_MOUSE:
+ case FEATURE_MOUSE_WARP:
case FEATURE_CLIPBOARD:
case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_HIDPI:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
- case FEATURE_CLIPBOARD_PRIMARY:
+ case FEATURE_CLIPBOARD_PRIMARY: {
+ return true;
+ } break;
+
#ifdef DBUS_ENABLED
- case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG: {
+ return true;
+ } break;
#endif
- case FEATURE_HIDPI: {
+
+#ifdef SPEECHD_ENABLED
+ case FEATURE_TEXT_TO_SPEECH: {
return true;
} break;
+#endif
default: {
return false;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index ada9202207..99b18759f3 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -2531,17 +2531,15 @@ Error DisplayServerWindows::dialog_show(String p_title, String p_description, Ve
config.pButtons = tbuttons;
config.pfCallback = win32_task_dialog_callback;
+ Error result = FAILED;
HMODULE comctl = LoadLibraryW(L"comctl32.dll");
if (comctl) {
typedef HRESULT(WINAPI * TaskDialogIndirectPtr)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
TaskDialogIndirectPtr task_dialog_indirect = (TaskDialogIndirectPtr)GetProcAddress(comctl, "TaskDialogIndirect");
- if (task_dialog_indirect) {
- int button_pressed;
- if (FAILED(task_dialog_indirect(&config, &button_pressed, nullptr, nullptr))) {
- return FAILED;
- }
+ int button_pressed;
+ if (task_dialog_indirect && SUCCEEDED(task_dialog_indirect(&config, &button_pressed, nullptr, nullptr))) {
if (!p_callback.is_null()) {
Variant button = button_pressed;
const Variant *args[1] = { &button };
@@ -2553,13 +2551,14 @@ Error DisplayServerWindows::dialog_show(String p_title, String p_description, Ve
}
}
- return OK;
+ result = OK;
}
FreeLibrary(comctl);
+ } else {
+ ERR_PRINT("Unable to create native dialog.");
}
- ERR_PRINT("Unable to create native dialog.");
- return FAILED;
+ return result;
}
struct Win32InputTextDialogInit {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 30a5433e45..7842fc5fc0 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -691,7 +691,7 @@ bool TreeItem::_is_any_collapsed(bool p_only_visible) {
}
bool TreeItem::is_any_collapsed(bool p_only_visible) {
- if (p_only_visible && !is_visible()) {
+ if (p_only_visible && !is_visible_in_tree()) {
return false;
}
@@ -712,12 +712,31 @@ void TreeItem::set_visible(bool p_visible) {
tree->queue_redraw();
_changed_notify();
}
+
+ _handle_visibility_changed(p_visible);
}
bool TreeItem::is_visible() {
return visible;
}
+bool TreeItem::is_visible_in_tree() const {
+ return visible && parent_visible_in_tree;
+}
+
+void TreeItem::_handle_visibility_changed(bool p_visible) {
+ TreeItem *child = get_first_child();
+ while (child) {
+ child->_propagate_visibility_changed(p_visible);
+ child = child->get_next();
+ }
+}
+
+void TreeItem::_propagate_visibility_changed(bool p_parent_visible_in_tree) {
+ parent_visible_in_tree = p_parent_visible_in_tree;
+ _handle_visibility_changed(p_parent_visible_in_tree);
+}
+
void TreeItem::uncollapse_tree() {
TreeItem *t = this;
while (t) {
@@ -788,6 +807,7 @@ TreeItem *TreeItem::create_child(int p_index) {
}
ti->parent = this;
+ ti->parent_visible_in_tree = is_visible_in_tree();
return ti;
}
@@ -799,6 +819,8 @@ void TreeItem::add_child(TreeItem *p_item) {
p_item->_change_tree(tree);
p_item->parent = this;
+ p_item->parent_visible_in_tree = is_visible_in_tree();
+ p_item->_handle_visibility_changed(p_item->parent_visible_in_tree);
TreeItem *item_prev = first_child;
while (item_prev && item_prev->next) {
@@ -906,7 +928,7 @@ TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) {
TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
TreeItem *loop = this;
TreeItem *prev_item = _get_prev_in_tree(p_wrap);
- while (prev_item && !prev_item->is_visible()) {
+ while (prev_item && !prev_item->is_visible_in_tree()) {
prev_item = prev_item->_get_prev_in_tree(p_wrap);
if (prev_item == loop) {
// Check that we haven't looped all the way around to the start.
@@ -947,7 +969,7 @@ TreeItem *TreeItem::_get_next_in_tree(bool p_wrap, bool p_include_invisible) {
TreeItem *TreeItem::get_next_visible(bool p_wrap) {
TreeItem *loop = this;
TreeItem *next_item = _get_next_in_tree(p_wrap);
- while (next_item && !next_item->is_visible()) {
+ while (next_item && !next_item->is_visible_in_tree()) {
next_item = next_item->_get_next_in_tree(p_wrap);
if (next_item == loop) {
// Check that we haven't looped all the way around to the start.
@@ -1641,6 +1663,7 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible);
+ ClassDB::bind_method(D_METHOD("is_visible_in_tree"), &TreeItem::is_visible_in_tree);
ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree);
@@ -1790,7 +1813,7 @@ Size2 Tree::_get_cell_icon_size(const TreeItem::Cell &p_cell) const {
}
int Tree::compute_item_height(TreeItem *p_item) const {
- if ((p_item == root && hide_root) || !p_item->is_visible()) {
+ if ((p_item == root && hide_root) || !p_item->is_visible_in_tree()) {
return 0;
}
@@ -1848,7 +1871,7 @@ int Tree::compute_item_height(TreeItem *p_item) const {
}
int Tree::get_item_height(TreeItem *p_item) const {
- if (!p_item->is_visible()) {
+ if (!p_item->is_visible_in_tree()) {
return 0;
}
int height = compute_item_height(p_item);
@@ -2050,7 +2073,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
return -1; //draw no more!
}
- if (!p_item->is_visible()) {
+ if (!p_item->is_visible_in_tree()) {
return 0;
}
@@ -2507,7 +2530,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
// Draw relationship lines.
- if (theme_cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) {
+ if (theme_cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible_in_tree()) {
int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int parent_ofs = p_pos.x + theme_cache.item_margin;
Point2i root_pos = Point2i(root_ofs, children_pos.y + child_self_height / 2) - theme_cache.offset + p_draw_ofs;
@@ -2788,7 +2811,7 @@ void Tree::_range_click_timeout() {
}
int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) {
- if (p_item && !p_item->is_visible()) {
+ if (p_item && !p_item->is_visible_in_tree()) {
// Skip any processing of invisible items.
return 0;
}
@@ -4797,7 +4820,7 @@ int Tree::get_item_offset(TreeItem *p_item) const {
return ofs;
}
- if ((it != root || !hide_root) && it->is_visible()) {
+ if ((it != root || !hide_root) && it->is_visible_in_tree()) {
ofs += compute_item_height(it);
ofs += theme_cache.v_separation;
}
@@ -5171,7 +5194,7 @@ void Tree::_do_incr_search(const String &p_add) {
TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const {
Point2 pos = p_pos;
- if ((root != p_item || !hide_root) && p_item->is_visible()) {
+ if ((root != p_item || !hide_root) && p_item->is_visible_in_tree()) {
h = compute_item_height(p_item) + theme_cache.v_separation;
if (pos.y < h) {
if (drop_mode_flags == DROP_MODE_ON_ITEM) {
@@ -5204,7 +5227,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
h = 0;
}
- if (p_item->is_collapsed() || !p_item->is_visible()) {
+ if (p_item->is_collapsed() || !p_item->is_visible_in_tree()) {
return nullptr; // do not try children, it's collapsed
}
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 194de1e994..21696d8216 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -129,6 +129,7 @@ private:
bool collapsed = false; // won't show children
bool visible = true;
+ bool parent_visible_in_tree = true;
bool disable_folding = false;
int custom_min_height = 0;
@@ -147,6 +148,8 @@ private:
void _changed_notify();
void _cell_selected(int p_cell);
void _cell_deselected(int p_cell);
+ void _handle_visibility_changed(bool p_visible);
+ void _propagate_visibility_changed(bool p_parent_visible_in_tree);
void _change_tree(Tree *p_tree);
@@ -300,6 +303,7 @@ public:
void set_visible(bool p_visible);
bool is_visible();
+ bool is_visible_in_tree() const;
void uncollapse_tree();
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index e1709fdb00..dc48ddbd56 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -5091,12 +5091,12 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
if (main_instance) {
// Only the instance that is not a local device and is also the singleton is allowed to manage a pipeline cache.
- pipeline_cache_file_path = "user://vulkan/pipelines";
- pipeline_cache_file_path += "." + device.name.validate_filename().replace(" ", "_").to_lower();
+ pipeline_cache_file_path = vformat("user://vulkan/pipelines.%s.%s",
+ OS::get_singleton()->get_current_rendering_method(),
+ device.name.validate_filename().replace(" ", "_").to_lower());
if (Engine::get_singleton()->is_editor_hint()) {
pipeline_cache_file_path += ".editor";
}
-
pipeline_cache_file_path += ".cache";
Vector<uint8_t> cache_data = _load_pipeline_cache();
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 8d2607b874..da742d0183 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -642,48 +642,59 @@ struct test_27_data {
TEST_CASE("[String] Begins with") {
test_27_data tc[] = {
+ // Test cases for true:
{ "res://foobar", "res://", true },
+ { "abc", "abc", true },
+ { "abc", "", true },
+ { "", "", true },
+ // Test cases for false:
{ "res", "res://", false },
- { "abc", "abc", true }
+ { "abcdef", "foo", false },
+ { "abc", "ax", false },
+ { "", "abc", false }
};
size_t count = sizeof(tc) / sizeof(tc[0]);
bool state = true;
- for (size_t i = 0; state && i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
String s = tc[i].data;
state = s.begins_with(tc[i].part) == tc[i].expected;
- if (state) {
- String sb = tc[i].part;
- state = s.begins_with(sb) == tc[i].expected;
- }
- CHECK(state);
- if (!state) {
- break;
- }
- };
- CHECK(state);
+ CHECK_MESSAGE(state, "first check failed at: ", i);
+
+ String sb = tc[i].part;
+ state = s.begins_with(sb) == tc[i].expected;
+ CHECK_MESSAGE(state, "second check failed at: ", i);
+ }
+
+ // Test "const char *" version also with nullptr.
+ String s("foo");
+ state = s.begins_with(nullptr) == false;
+ CHECK_MESSAGE(state, "nullptr check failed");
+
+ String empty("");
+ state = empty.begins_with(nullptr) == false;
+ CHECK_MESSAGE(state, "nullptr check with empty string failed");
}
TEST_CASE("[String] Ends with") {
test_27_data tc[] = {
+ // test cases for true:
{ "res://foobar", "foobar", true },
+ { "abc", "abc", true },
+ { "abc", "", true },
+ { "", "", true },
+ // test cases for false:
{ "res", "res://", false },
- { "abc", "abc", true }
+ { "", "abc", false },
+ { "abcdef", "foo", false },
+ { "abc", "xc", false }
};
size_t count = sizeof(tc) / sizeof(tc[0]);
- bool state = true;
- for (size_t i = 0; state && i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
String s = tc[i].data;
- state = s.ends_with(tc[i].part) == tc[i].expected;
- if (state) {
- String sb = tc[i].part;
- state = s.ends_with(sb) == tc[i].expected;
- }
- CHECK(state);
- if (!state) {
- break;
- }
- };
- CHECK(state);
+ String sb = tc[i].part;
+ bool state = s.ends_with(sb) == tc[i].expected;
+ CHECK_MESSAGE(state, "check failed at: ", i);
+ }
}
TEST_CASE("[String] format") {