summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/io/resource_format_binary.cpp1
-rw-r--r--doc/classes/DisplayServer.xml7
-rw-r--r--editor/filesystem_dock.cpp16
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/icons/RandomNumberGenerator.svg1
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp30
-rw-r--r--editor/plugins/tiles/tile_map_editor.h2
-rw-r--r--platform/android/display_server_android.cpp6
-rw-r--r--platform/android/display_server_android.h2
-rw-r--r--platform/android/export/export_plugin.cpp17
-rw-r--r--platform/android/export/gradle_export_util.cpp2
-rw-r--r--platform/android/java/app/AndroidManifest.xml7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java21
-rw-r--r--platform/android/java_godot_io_wrapper.cpp24
-rw-r--r--platform/android/java_godot_io_wrapper.h3
-rw-r--r--scene/resources/resource_format_text.cpp32
-rw-r--r--scene/resources/theme.cpp65
-rw-r--r--scene/resources/theme.h3
-rw-r--r--servers/display_server.cpp2
-rw-r--r--servers/display_server.h2
-rw-r--r--tests/scene/test_theme.h257
-rw-r--r--tests/test_main.cpp1
22 files changed, 468 insertions, 34 deletions
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index b6988109c5..bc7e524892 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -1287,6 +1287,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
fw->store_8(b);
b = f->get_8();
}
+ f.unref();
bool all_ok = fw->get_error() == OK;
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index ede3a1e199..83b39bcb60 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -104,6 +104,13 @@
<description>
</description>
</method>
+ <method name="get_display_cutouts" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns an [Array] of [Rect2], each of which is the bounding rectangle for a display cutout or notch. These are non-functional areas on edge-to-edge screens used by cameras and sensors. Returns an empty array if the device does not have cutouts. See also [method screen_get_usable_rect].
+ [b]Note:[/b] Currently only implemented on Android. Other platforms will return an empty array even if they do have display cutouts or notches.
+ </description>
+ </method>
<method name="get_name" qualifiers="const">
<return type="String" />
<description>
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 33c6ce9622..e08e7343ba 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2025,6 +2025,16 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
}
} break;
+ case FILE_COPY_UID: {
+ if (!p_selected.is_empty()) {
+ ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_selected[0]);
+ if (uid != ResourceUID::INVALID_ID) {
+ String uid_string = ResourceUID::get_singleton()->id_to_text(uid);
+ DisplayServer::get_singleton()->clipboard_set(uid_string);
+ }
+ }
+ } break;
+
case FILE_NEW_RESOURCE: {
new_resource_dialog->popup_create(true);
} break;
@@ -2550,6 +2560,9 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str
if (p_paths.size() == 1) {
p_popup->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("filesystem_dock/copy_path"), FILE_COPY_PATH);
+ if (ResourceLoader::get_resource_uid(p_paths[0]) != ResourceUID::INVALID_ID) {
+ p_popup->add_icon_shortcut(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), ED_GET_SHORTCUT("filesystem_dock/copy_uid"), FILE_COPY_UID);
+ }
if (p_paths[0] != "res://") {
p_popup->add_icon_shortcut(get_theme_icon(SNAME("Rename"), SNAME("EditorIcons")), ED_GET_SHORTCUT("filesystem_dock/rename"), FILE_RENAME);
p_popup->add_icon_shortcut(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), ED_GET_SHORTCUT("filesystem_dock/duplicate"), FILE_DUPLICATE);
@@ -2771,6 +2784,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
_tree_rmb_option(FILE_DUPLICATE);
} else if (ED_IS_SHORTCUT("filesystem_dock/copy_path", p_event)) {
_tree_rmb_option(FILE_COPY_PATH);
+ } else if (ED_IS_SHORTCUT("filesystem_dock/copy_uid", p_event)) {
+ _tree_rmb_option(FILE_COPY_UID);
} else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) {
_tree_rmb_option(FILE_REMOVE);
} else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) {
@@ -2999,6 +3014,7 @@ FileSystemDock::FileSystemDock() {
// `KeyModifierMask::CMD | Key::C` conflicts with other editor shortcuts.
ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::C);
+ ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID"));
ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KeyModifierMask::CMD | Key::D);
ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), Key::KEY_DELETE);
ED_SHORTCUT("filesystem_dock/rename", TTR("Rename..."), Key::F2);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 15fade2d95..736651c17c 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -94,6 +94,7 @@ private:
FILE_NEW_SCENE,
FILE_SHOW_IN_EXPLORER,
FILE_COPY_PATH,
+ FILE_COPY_UID,
FILE_NEW_RESOURCE,
FILE_NEW_TEXTFILE,
FOLDER_EXPAND_ALL,
diff --git a/editor/icons/RandomNumberGenerator.svg b/editor/icons/RandomNumberGenerator.svg
new file mode 100644
index 0000000000..214a7452ed
--- /dev/null
+++ b/editor/icons/RandomNumberGenerator.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8.0000004 0c-7.63857653 0-8.0000004.36172395-8.0000004 8.0066027 0 7.6431663.32714821 7.9720723 7.9277171 7.9929163 3.4504629.00951 5.0807119-.12114 5.9272279-.475116 1.794714-.750503 2.145055-1.974798 2.145055-7.5178003 0-7.64487875-.361425-8.0066027-7.9999996-8.0066027zm-4.0341882 2.0158259c1.7899451 0 2.786201 2.0256492 1.6859585 3.4255356-.8444948 1.0744791-2.3331923 1.1725336-3.2879122.2170288-1.3527384-1.3538547-.3466043-3.6425644 1.6019537-3.6425644zm4.0869355 3.7383699c1.7899399 0 2.7862023 2.0236959 1.6859586 3.4235804-.8444989 1.0745018-2.3331926 1.1744878-3.2879123.218984-1.3527384-1.3538549-.3466055-3.6425644 1.6019537-3.6425644zm4.0615383 3.9690852c1.09547.036419 2.143101.848375 2.143101 2.113587 0 1.788519-2.569649 2.876024-3.713797 1.571993-.9034489-1.029694-.9772793-1.902694-.23834-2.842882.476145-.6058151 1.151756-.8645864 1.809036-.842698z" fill="#fff" stroke-width=".256504"/></svg>
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index c1a95c11f6..ba87eba9e0 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -76,7 +76,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
picker_button->show();
erase_button->show();
tools_settings_vsep_2->show();
- random_tile_checkbox->show();
+ random_tile_toggle->show();
scatter_label->show();
scatter_spinbox->show();
} else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
@@ -84,7 +84,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
picker_button->show();
erase_button->show();
tools_settings_vsep_2->show();
- random_tile_checkbox->show();
+ random_tile_toggle->show();
scatter_label->show();
scatter_spinbox->show();
} else if (tool_buttons_group->get_pressed_button() == rect_tool_button) {
@@ -92,7 +92,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
picker_button->show();
erase_button->show();
tools_settings_vsep_2->show();
- random_tile_checkbox->show();
+ random_tile_toggle->show();
scatter_label->show();
scatter_spinbox->show();
} else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) {
@@ -101,7 +101,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
erase_button->show();
tools_settings_vsep_2->show();
bucket_contiguous_checkbox->show();
- random_tile_checkbox->show();
+ random_tile_toggle->show();
scatter_label->show();
scatter_spinbox->show();
}
@@ -461,6 +461,7 @@ void TileMapEditorTilesPlugin::_update_theme() {
picker_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons")));
erase_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
+ random_tile_toggle->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("RandomNumberGenerator"), SNAME("EditorIcons")));
missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"));
}
@@ -870,7 +871,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
Transform2D tile_xform;
tile_xform.set_origin(tile_map->map_to_world(E.key));
tile_xform.set_scale(tile_set->get_tile_size());
- if (!(drag_erasing || erase_button->is_pressed()) && random_tile_checkbox->is_pressed()) {
+ if (!(drag_erasing || erase_button->is_pressed()) && random_tile_toggle->is_pressed()) {
tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true);
} else {
if (tile_set->has_source(E.value.source_id)) {
@@ -1001,7 +1002,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_
Map<Vector2i, TileMapCell> output;
if (!pattern->is_empty()) {
// Paint the tiles on the tile map.
- if (!p_erase && random_tile_checkbox->is_pressed()) {
+ if (!p_erase && random_tile_toggle->is_pressed()) {
// Paint a random tile.
Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos));
for (int i = 0; i < line.size(); i++) {
@@ -1061,7 +1062,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start
Map<Vector2i, TileMapCell> output;
if (!pattern->is_empty()) {
- if (!p_erase && random_tile_checkbox->is_pressed()) {
+ if (!p_erase && random_tile_toggle->is_pressed()) {
// Paint a random tile.
for (int x = 0; x < rect.size.x; x++) {
for (int y = 0; y < rect.size.y; y++) {
@@ -1134,7 +1135,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
source_cell.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) &&
source_cell.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) &&
(source_cell.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) {
- if (!p_erase && random_tile_checkbox->is_pressed()) {
+ if (!p_erase && random_tile_toggle->is_pressed()) {
// Paint a random tile.
output.insert(coords, _pick_random_tile(pattern));
} else {
@@ -1180,7 +1181,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
source_cell.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) &&
source_cell.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) &&
(source_cell.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) {
- if (!p_erase && random_tile_checkbox->is_pressed()) {
+ if (!p_erase && random_tile_toggle->is_pressed()) {
// Paint a random tile.
output.insert(coords, _pick_random_tile(pattern));
} else {
@@ -2103,11 +2104,12 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
tools_settings->add_child(bucket_contiguous_checkbox);
// Random tile checkbox.
- random_tile_checkbox = memnew(CheckBox);
- random_tile_checkbox->set_flat(true);
- random_tile_checkbox->set_text(TTR("Place Random Tile"));
- random_tile_checkbox->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled));
- tools_settings->add_child(random_tile_checkbox);
+ random_tile_toggle = memnew(Button);
+ random_tile_toggle->set_flat(true);
+ random_tile_toggle->set_toggle_mode(true);
+ random_tile_toggle->set_tooltip(TTR("Place Random Tile"));
+ random_tile_toggle->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled));
+ tools_settings->add_child(random_tile_toggle);
// Random tile scattering.
scatter_label = memnew(Label);
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 3a0293f48f..ec32c83d10 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -92,7 +92,7 @@ private:
VSeparator *tools_settings_vsep_2 = nullptr;
CheckBox *bucket_contiguous_checkbox = nullptr;
- CheckBox *random_tile_checkbox = nullptr;
+ Button *random_tile_toggle = nullptr;
float scattering = 0.0;
Label *scatter_label = nullptr;
SpinBox *scatter_spinbox = nullptr;
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 2eb7056a36..2c9b481f0c 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -136,6 +136,12 @@ bool DisplayServerAndroid::clipboard_has() const {
}
}
+Array DisplayServerAndroid::get_display_cutouts() const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_NULL_V(godot_io_java, Array());
+ return godot_io_java->get_display_cutouts();
+}
+
void DisplayServerAndroid::screen_set_keep_on(bool p_enable) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_COND(!godot_java);
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 2604214ac0..7b758dd3bf 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -104,6 +104,8 @@ public:
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
+ virtual Array get_display_cutouts() const override;
+
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 8cfa3a67b9..d357cd586e 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1000,16 +1000,23 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
}
- if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") {
- if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ // Hand tracking related configurations
+ if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") {
string_table.write[attr_value] = "com.oculus.handtracking.frequency";
}
- }
- if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") {
- if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") {
string_table.write[attr_value] = (hand_tracking_frequency_index == XR_HAND_TRACKING_FREQUENCY_LOW ? "LOW" : "HIGH");
}
+
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_name") {
+ string_table.write[attr_value] = "com.oculus.handtracking.version";
+ }
+
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_value") {
+ string_table.write[attr_value] = "V2.0";
+ }
}
iofs += 20;
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index 173bb8bcb7..d9574a1a52 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -279,6 +279,7 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
" android:requestLegacyExternalStorage=\"%s\"\n"
" tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
" tools:ignore=\"GoogleAppIndexingWarning\">\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_version_name\" />\n"
" <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_metadata_name\" />\n",
bool_to_string(p_preset->get("user_data_backup/allow")),
bool_to_string(p_preset->get("package/classify_as_game")),
@@ -293,6 +294,7 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
manifest_application_text += vformat(
" <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.frequency\" android:value=\"%s\" />\n",
hand_tracking_frequency);
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n";
}
} else {
manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n";
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 4c4501729d..c98e8f1d55 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -40,6 +40,13 @@
android:name="xr_hand_tracking_metadata_name"
android:value="xr_hand_tracking_metadata_value"/>
+ <!-- XR hand tracking version -->
+ <!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. -->
+ <!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. -->
+ <meta-data
+ android:name="xr_hand_tracking_version_name"
+ android:value="xr_hand_tracking_version_value"/>
+
<!-- Supported Meta devices -->
<!-- This is removed by the exporter if the xr mode is not VR. -->
<meta-data
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index e8e292df5d..af1b54dbac 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -38,6 +38,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Point;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@@ -51,6 +52,7 @@ import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.io.IOException;
+import java.util.List;
import java.util.Locale;
// Wrapper for native library
@@ -260,6 +262,25 @@ public class GodotIO {
return result;
}
+ public int[] getDisplayCutouts() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
+ return new int[0];
+ DisplayCutout cutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
+ if (cutout == null)
+ return new int[0];
+ List<Rect> rects = cutout.getBoundingRects();
+ int cutouts = rects.size();
+ int[] result = new int[cutouts * 4];
+ int index = 0;
+ for (Rect rect : rects) {
+ result[index++] = rect.left;
+ result[index++] = rect.top;
+ result[index++] = rect.width();
+ result[index++] = rect.height();
+ }
+ return result;
+ }
+
public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 5b21e696c3..b98e15a54e 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -31,6 +31,8 @@
#include "java_godot_io_wrapper.h"
#include "core/error/error_list.h"
+#include "core/math/rect2.h"
+#include "core/variant/variant.h"
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment
// we can't cache it.
@@ -51,6 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I");
_get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");
_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;");
+ _get_display_cutouts = p_env->GetMethodID(cls, "getDisplayCutouts", "()[I"),
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
@@ -176,6 +179,27 @@ void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
}
}
+Array GodotIOJavaWrapper::get_display_cutouts() {
+ Array result;
+ ERR_FAIL_NULL_V(_get_display_cutouts, result);
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, result);
+ jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _get_display_cutouts);
+ jint arrayLength = env->GetArrayLength(returnArray);
+ jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
+ int cutouts = arrayLength / 4;
+ for (int i = 0; i < cutouts; i++) {
+ int x = arrayBody[i * 4];
+ int y = arrayBody[i * 4 + 1];
+ int width = arrayBody[i * 4 + 2];
+ int height = arrayBody[i * 4 + 3];
+ Rect2 cutout(x, y, width, height);
+ result.append(cutout);
+ }
+ env->ReleaseIntArrayElements(returnArray, arrayBody, 0);
+ return result;
+}
+
String GodotIOJavaWrapper::get_unique_id() {
if (_get_unique_id) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 08e3092afd..7b87f3deee 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -37,6 +37,7 @@
#include <android/log.h>
#include <jni.h>
+#include "core/variant/array.h"
#include "string_android.h"
// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++
@@ -48,6 +49,7 @@ private:
jmethodID _open_URI = 0;
jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
+ jmethodID _get_display_cutouts = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
@@ -76,6 +78,7 @@ public:
float get_scaled_density();
float get_screen_refresh_rate(float fallback);
void screen_get_usable_rect(int (&p_rect_xywh)[4]);
+ Array get_display_cutouts();
String get_unique_id();
bool has_vk();
void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index b18456d464..a239bf1ab9 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -890,7 +890,6 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
fw->store_8(c);
c = f->get_8();
}
- f.unref();
bool all_ok = fw->get_error() == OK;
@@ -898,12 +897,6 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
return ERR_CANT_CREATE;
}
- fw.unref();
-
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- da->remove(p_path);
- da->rename(p_path + ".depren", p_path);
-
return OK;
}
@@ -1439,15 +1432,26 @@ void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<Strin
}
Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const Map<String, String> &p_map) {
- Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- if (f.is_null()) {
- ERR_FAIL_V(ERR_CANT_OPEN);
+ Error err = OK;
+ {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ if (f.is_null()) {
+ ERR_FAIL_V(ERR_CANT_OPEN);
+ }
+
+ ResourceLoaderText loader;
+ loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
+ loader.res_path = loader.local_path;
+ err = loader.rename_dependencies(f, p_path, p_map);
}
- ResourceLoaderText loader;
- loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
- loader.res_path = loader.local_path;
- return loader.rename_dependencies(f, p_path, p_map);
+ if (err == OK) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ da->remove(p_path);
+ da->rename(p_path + ".depren", p_path);
+ }
+
+ return err;
}
ResourceFormatLoaderText *ResourceFormatLoaderText::singleton = nullptr;
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index 373fbb94ea..be54c309c8 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -261,6 +261,27 @@ int Theme::get_fallback_font_size() {
return fallback_font_size;
}
+bool Theme::is_valid_type_name(const String &p_name) {
+ for (int i = 0; i < p_name.length(); i++) {
+ if (!is_ascii_identifier_char(p_name[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Theme::is_valid_item_name(const String &p_name) {
+ if (p_name.is_empty()) {
+ return false;
+ }
+ for (int i = 0; i < p_name.length(); i++) {
+ if (!is_ascii_identifier_char(p_name[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
// Fallback values for theme item types, configurable per theme.
void Theme::set_default_base_scale(float p_base_scale) {
if (default_base_scale == p_base_scale) {
@@ -326,6 +347,9 @@ bool Theme::has_default_font_size() const {
// Icons.
void Theme::set_icon(const StringName &p_name, const StringName &p_theme_type, const Ref<Texture2D> &p_icon) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = false;
if (icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) {
existing = true;
@@ -358,6 +382,8 @@ bool Theme::has_icon_nocheck(const StringName &p_name, const StringName &p_theme
}
void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!icon_map.has(p_theme_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(icon_map[p_theme_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_old_name), "Cannot rename the icon '" + String(p_old_name) + "' because it does not exist.");
@@ -396,6 +422,8 @@ void Theme::get_icon_list(StringName p_theme_type, List<StringName> *p_list) con
}
void Theme::add_icon_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (icon_map.has(p_theme_type)) {
return;
}
@@ -433,6 +461,9 @@ void Theme::get_icon_type_list(List<StringName> *p_list) const {
// Styleboxes.
void Theme::set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = false;
if (style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) {
existing = true;
@@ -465,6 +496,8 @@ bool Theme::has_stylebox_nocheck(const StringName &p_name, const StringName &p_t
}
void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!style_map.has(p_theme_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(style_map[p_theme_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_old_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because it does not exist.");
@@ -503,6 +536,8 @@ void Theme::get_stylebox_list(StringName p_theme_type, List<StringName> *p_list)
}
void Theme::add_stylebox_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (style_map.has(p_theme_type)) {
return;
}
@@ -540,6 +575,9 @@ void Theme::get_stylebox_type_list(List<StringName> *p_list) const {
// Fonts.
void Theme::set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = false;
if (font_map[p_theme_type][p_name].is_valid()) {
existing = true;
@@ -574,6 +612,8 @@ bool Theme::has_font_nocheck(const StringName &p_name, const StringName &p_theme
}
void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!font_map.has(p_theme_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(font_map[p_theme_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_old_name), "Cannot rename the font '" + String(p_old_name) + "' because it does not exist.");
@@ -612,6 +652,8 @@ void Theme::get_font_list(StringName p_theme_type, List<StringName> *p_list) con
}
void Theme::add_font_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (font_map.has(p_theme_type)) {
return;
}
@@ -649,6 +691,9 @@ void Theme::get_font_type_list(List<StringName> *p_list) const {
// Font sizes.
void Theme::set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = has_font_size_nocheck(p_name, p_theme_type);
font_size_map[p_theme_type][p_name] = p_font_size;
@@ -674,6 +719,8 @@ bool Theme::has_font_size_nocheck(const StringName &p_name, const StringName &p_
}
void Theme::rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!font_size_map.has(p_theme_type), "Cannot rename the font size '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(font_size_map[p_theme_type].has(p_name), "Cannot rename the font size '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!font_size_map[p_theme_type].has(p_old_name), "Cannot rename the font size '" + String(p_old_name) + "' because it does not exist.");
@@ -708,6 +755,8 @@ void Theme::get_font_size_list(StringName p_theme_type, List<StringName> *p_list
}
void Theme::add_font_size_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (font_size_map.has(p_theme_type)) {
return;
}
@@ -733,6 +782,9 @@ void Theme::get_font_size_type_list(List<StringName> *p_list) const {
// Colors.
void Theme::set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = has_color_nocheck(p_name, p_theme_type);
color_map[p_theme_type][p_name] = p_color;
@@ -756,6 +808,8 @@ bool Theme::has_color_nocheck(const StringName &p_name, const StringName &p_them
}
void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!color_map.has(p_theme_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(color_map[p_theme_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!color_map[p_theme_type].has(p_old_name), "Cannot rename the color '" + String(p_old_name) + "' because it does not exist.");
@@ -790,6 +844,8 @@ void Theme::get_color_list(StringName p_theme_type, List<StringName> *p_list) co
}
void Theme::add_color_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (color_map.has(p_theme_type)) {
return;
}
@@ -815,6 +871,9 @@ void Theme::get_color_type_list(List<StringName> *p_list) const {
// Theme constants.
void Theme::set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
bool existing = has_constant_nocheck(p_name, p_theme_type);
constant_map[p_theme_type][p_name] = p_constant;
@@ -838,6 +897,8 @@ bool Theme::has_constant_nocheck(const StringName &p_name, const StringName &p_t
}
void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_item_name(p_name), vformat("Invalid item name: '%s'", p_name));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
ERR_FAIL_COND_MSG(!constant_map.has(p_theme_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist.");
ERR_FAIL_COND_MSG(constant_map[p_theme_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists.");
ERR_FAIL_COND_MSG(!constant_map[p_theme_type].has(p_old_name), "Cannot rename the constant '" + String(p_old_name) + "' because it does not exist.");
@@ -872,6 +933,8 @@ void Theme::get_constant_list(StringName p_theme_type, List<StringName> *p_list)
}
void Theme::add_constant_type(const StringName &p_theme_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+
if (constant_map.has(p_theme_type)) {
return;
}
@@ -1154,6 +1217,8 @@ void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_l
// Theme type variations.
void Theme::set_type_variation(const StringName &p_theme_type, const StringName &p_base_type) {
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_theme_type), vformat("Invalid type name: '%s'", p_theme_type));
+ ERR_FAIL_COND_MSG(!is_valid_type_name(p_base_type), vformat("Invalid type name: '%s'", p_base_type));
ERR_FAIL_COND_MSG(p_theme_type == StringName(), "An empty theme type cannot be marked as a variation of another type.");
ERR_FAIL_COND_MSG(ClassDB::class_exists(p_theme_type), "A type associated with a built-in class cannot be marked as a variation of another type.");
ERR_FAIL_COND_MSG(p_base_type == StringName(), "An empty theme type cannot be the base type of a variation. Use clear_type_variation() instead if you want to unmark '" + String(p_theme_type) + "' as a variation.");
diff --git a/scene/resources/theme.h b/scene/resources/theme.h
index 9afe05007d..f8f1e95634 100644
--- a/scene/resources/theme.h
+++ b/scene/resources/theme.h
@@ -137,6 +137,9 @@ public:
static Ref<Font> get_fallback_font();
static int get_fallback_font_size();
+ static bool is_valid_type_name(const String &p_name);
+ static bool is_valid_item_name(const String &p_name);
+
void set_default_base_scale(float p_base_scale);
float get_default_base_scale() const;
bool has_default_base_scale() const;
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 8d97cd2543..e68fcc184e 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -579,6 +579,8 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("clipboard_set_primary", "clipboard_primary"), &DisplayServer::clipboard_set_primary);
ClassDB::bind_method(D_METHOD("clipboard_get_primary"), &DisplayServer::clipboard_get_primary);
+ ClassDB::bind_method(D_METHOD("get_display_cutouts"), &DisplayServer::get_display_cutouts);
+
ClassDB::bind_method(D_METHOD("get_screen_count"), &DisplayServer::get_screen_count);
ClassDB::bind_method(D_METHOD("screen_get_position", "screen"), &DisplayServer::screen_get_position, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_size", "screen"), &DisplayServer::screen_get_size, DEFVAL(SCREEN_OF_MAIN_WINDOW));
diff --git a/servers/display_server.h b/servers/display_server.h
index 19efcbd3dd..64340cff73 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -228,6 +228,8 @@ public:
virtual void clipboard_set_primary(const String &p_text);
virtual String clipboard_get_primary() const;
+ virtual Array get_display_cutouts() const { return Array(); }
+
enum {
SCREEN_OF_MAIN_WINDOW = -1
};
diff --git a/tests/scene/test_theme.h b/tests/scene/test_theme.h
new file mode 100644
index 0000000000..fedffc8449
--- /dev/null
+++ b/tests/scene/test_theme.h
@@ -0,0 +1,257 @@
+/*************************************************************************/
+/* test_theme.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 TEST_THEME_H
+#define TEST_THEME_H
+
+#include "scene/resources/theme.h"
+#include "tests/test_tools.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestTheme {
+
+class Fixture {
+public:
+ struct DataEntry {
+ Theme::DataType type;
+ Variant value;
+ } const valid_data[Theme::DATA_TYPE_MAX] = {
+ { Theme::DATA_TYPE_COLOR, Color() },
+ { Theme::DATA_TYPE_CONSTANT, 42 },
+ { Theme::DATA_TYPE_FONT, Ref<Font>(memnew(Font)) },
+ { Theme::DATA_TYPE_FONT_SIZE, 42 },
+ { Theme::DATA_TYPE_ICON, Ref<Texture>(memnew(ImageTexture)) },
+ { Theme::DATA_TYPE_STYLEBOX, Ref<StyleBox>(memnew(StyleBoxFlat)) },
+ };
+
+ const StringName valid_item_name = "valid_item_name";
+ const StringName valid_type_name = "ValidTypeName";
+};
+
+TEST_CASE_FIXTURE(Fixture, "[Theme] Good theme type names") {
+ StringName names[] = {
+ "", // Empty name.
+ "CapitalizedName",
+ "snake_cased_name",
+ "42",
+ "_Underscore_",
+ };
+
+ SUBCASE("add_type") {
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->add_type(name);
+ CHECK_FALSE(ed.has_error);
+ }
+ }
+
+ SUBCASE("set_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_theme_item(entry.type, valid_item_name, name, entry.value);
+ CHECK_FALSE(ed.has_error);
+ }
+ }
+ }
+
+ SUBCASE("add_theme_item_type") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->add_theme_item_type(entry.type, name);
+ CHECK_FALSE(ed.has_error);
+ }
+ }
+ }
+
+ SUBCASE("set_type_variation") {
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_type_variation(valid_type_name, name);
+ CHECK(ed.has_error == (name == StringName()));
+ }
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_type_variation(name, valid_type_name);
+ CHECK(ed.has_error == (name == StringName()));
+ }
+ }
+}
+
+TEST_CASE_FIXTURE(Fixture, "[Theme] Bad theme type names") {
+ StringName names[] = {
+ "With/Slash",
+ "With Space",
+ "With@various$symbols!",
+ String::utf8("contains_汉字"),
+ };
+
+ SUBCASE("add_type") {
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->add_type(name);
+ CHECK(ed.has_error);
+ }
+ }
+
+ SUBCASE("set_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_theme_item(entry.type, valid_item_name, name, entry.value);
+ CHECK(ed.has_error);
+ }
+ }
+ }
+
+ SUBCASE("add_theme_item_type") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->add_theme_item_type(entry.type, name);
+ CHECK(ed.has_error);
+ }
+ }
+ }
+
+ SUBCASE("set_type_variation") {
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_type_variation(valid_type_name, name);
+ CHECK(ed.has_error);
+ }
+ for (const StringName &name : names) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_type_variation(name, valid_type_name);
+ CHECK(ed.has_error);
+ }
+ }
+}
+
+TEST_CASE_FIXTURE(Fixture, "[Theme] Good theme item names") {
+ StringName names[] = {
+ "CapitalizedName",
+ "snake_cased_name",
+ "42",
+ "_Underscore_",
+ };
+
+ SUBCASE("set_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_theme_item(entry.type, name, valid_type_name, entry.value);
+ CHECK_FALSE(ed.has_error);
+ CHECK(theme->has_theme_item(entry.type, name, valid_type_name));
+ }
+ }
+ }
+
+ SUBCASE("rename_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+ theme->set_theme_item(entry.type, valid_item_name, valid_type_name, entry.value);
+
+ ErrorDetector ed;
+ theme->rename_theme_item(entry.type, valid_item_name, name, valid_type_name);
+ CHECK_FALSE(ed.has_error);
+ CHECK_FALSE(theme->has_theme_item(entry.type, valid_item_name, valid_type_name));
+ CHECK(theme->has_theme_item(entry.type, name, valid_type_name));
+ }
+ }
+ }
+}
+
+TEST_CASE_FIXTURE(Fixture, "[Theme] Bad theme item names") {
+ StringName names[] = {
+ "", // Empty name.
+ "With/Slash",
+ "With Space",
+ "With@various$symbols!",
+ String::utf8("contains_汉字"),
+ };
+
+ SUBCASE("set_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+
+ ErrorDetector ed;
+ theme->set_theme_item(entry.type, name, valid_type_name, entry.value);
+ CHECK(ed.has_error);
+ CHECK_FALSE(theme->has_theme_item(entry.type, name, valid_type_name));
+ }
+ }
+ }
+
+ SUBCASE("rename_theme_item") {
+ for (const StringName &name : names) {
+ for (const DataEntry &entry : valid_data) {
+ Ref<Theme> theme = memnew(Theme);
+ theme->set_theme_item(entry.type, valid_item_name, valid_type_name, entry.value);
+
+ ErrorDetector ed;
+ theme->rename_theme_item(entry.type, valid_item_name, name, valid_type_name);
+ CHECK(ed.has_error);
+ CHECK(theme->has_theme_item(entry.type, valid_item_name, valid_type_name));
+ CHECK_FALSE(theme->has_theme_item(entry.type, name, valid_type_name));
+ }
+ }
+ }
+}
+
+} // namespace TestTheme
+
+#endif // TEST_THEME_H
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index a059949105..be51afd83c 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -77,6 +77,7 @@
#include "tests/scene/test_gradient.h"
#include "tests/scene/test_path_3d.h"
#include "tests/scene/test_text_edit.h"
+#include "tests/scene/test_theme.h"
#include "tests/servers/test_text_server.h"
#include "tests/test_validate_testing.h"