summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/io/resource_loader.cpp26
-rw-r--r--core/object/object.h1
-rw-r--r--core/os/os.h3
-rw-r--r--core/string/locales.h1196
-rw-r--r--core/string/translation.cpp1227
-rw-r--r--core/string/translation.h36
-rw-r--r--core/templates/vector.h23
-rw-r--r--core/variant/array.cpp22
-rw-r--r--core/variant/array.h4
-rw-r--r--core/variant/variant_call.cpp20
-rw-r--r--core/variant/variant_parser.cpp9
-rw-r--r--doc/classes/@GlobalScope.xml5
-rw-r--r--doc/classes/Array.xml7
-rw-r--r--doc/classes/ArrayMesh.xml3
-rw-r--r--doc/classes/CodeEdit.xml2
-rw-r--r--doc/classes/Environment.xml9
-rw-r--r--doc/classes/FlowContainer.xml20
-rw-r--r--doc/classes/HFlowContainer.xml19
-rw-r--r--doc/classes/Image.xml2
-rw-r--r--doc/classes/ImporterMesh.xml5
-rw-r--r--doc/classes/PackedByteArray.xml5
-rw-r--r--doc/classes/PackedColorArray.xml5
-rw-r--r--doc/classes/PackedFloat32Array.xml5
-rw-r--r--doc/classes/PackedFloat64Array.xml5
-rw-r--r--doc/classes/PackedInt32Array.xml5
-rw-r--r--doc/classes/PackedInt64Array.xml5
-rw-r--r--doc/classes/PackedScene.xml2
-rw-r--r--doc/classes/PackedStringArray.xml5
-rw-r--r--doc/classes/PackedVector2Array.xml5
-rw-r--r--doc/classes/PackedVector3Array.xml5
-rw-r--r--doc/classes/ProjectSettings.xml22
-rw-r--r--doc/classes/RenderingServer.xml11
-rw-r--r--doc/classes/TextLine.xml3
-rw-r--r--doc/classes/TextParagraph.xml6
-rw-r--r--doc/classes/TextServer.xml3
-rw-r--r--doc/classes/TranslationServer.xml54
-rw-r--r--doc/classes/VFlowContainer.xml19
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h2
-rw-r--r--editor/doc_tools.cpp14
-rw-r--r--editor/editor_help.cpp64
-rw-r--r--editor/editor_help.h12
-rw-r--r--editor/editor_locale_dialog.cpp563
-rw-r--r--editor/editor_locale_dialog.h93
-rw-r--r--editor/editor_node.cpp37
-rw-r--r--editor/editor_node.h8
-rw-r--r--editor/editor_plugin.cpp30
-rw-r--r--editor/editor_properties.cpp62
-rw-r--r--editor/editor_properties.h21
-rw-r--r--editor/editor_run.cpp18
-rw-r--r--editor/editor_settings.cpp16
-rw-r--r--editor/editor_themes.cpp21
-rw-r--r--editor/editor_toaster.cpp8
-rw-r--r--editor/editor_toaster.h1
-rw-r--r--editor/icons/HFlowContainer.svg1
-rw-r--r--editor/icons/Notification.svg2
-rw-r--r--editor/icons/NotificationDisabled.svg2
-rw-r--r--editor/icons/VFlowContainer.svg1
-rw-r--r--editor/import/dynamicfont_import_settings.cpp425
-rw-r--r--editor/import/dynamicfont_import_settings.h7
-rw-r--r--editor/import/resource_importer_csv_translation.cpp3
-rw-r--r--editor/import/resource_importer_scene.cpp77
-rw-r--r--editor/import/resource_importer_scene.h2
-rw-r--r--editor/localization_editor.cpp251
-rw-r--r--editor/localization_editor.h17
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp34
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h10
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp56
-rw-r--r--editor/plugins/node_3d_editor_plugin.h13
-rw-r--r--editor/property_editor.cpp25
-rw-r--r--editor/property_editor.h3
-rw-r--r--editor/scene_tree_dock.cpp197
-rw-r--r--editor/scene_tree_dock.h3
-rw-r--r--main/main.cpp50
-rw-r--r--modules/gdnative/include/nativescript/godot_nativescript.h1
-rw-r--r--modules/gridmap/grid_map_editor_plugin.cpp8
-rw-r--r--modules/webrtc/doc_classes/WebRTCPeerConnection.xml6
-rw-r--r--platform/android/export/export_plugin.cpp2
-rw-r--r--scene/2d/camera_2d.cpp2
-rw-r--r--scene/gui/button.cpp2
-rw-r--r--scene/gui/control.cpp2
-rw-r--r--scene/gui/flow_container.cpp252
-rw-r--r--scene/gui/flow_container.h76
-rw-r--r--scene/gui/graph_node.cpp2
-rw-r--r--scene/gui/label.cpp2
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/link_button.cpp2
-rw-r--r--scene/gui/rich_text_label.cpp25
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/register_scene_types.cpp5
-rw-r--r--scene/resources/default_theme/default_theme.cpp4
-rw-r--r--scene/resources/environment.cpp22
-rw-r--r--scene/resources/environment.h13
-rw-r--r--scene/resources/packed_scene.cpp14
-rw-r--r--servers/rendering/rasterizer_dummy.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_environment_rd.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_environment_rd.h4
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp10
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_gi_rd.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp4
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.h2
-rw-r--r--servers/rendering/renderer_scene.h2
-rw-r--r--servers/rendering/renderer_scene_cull.h2
-rw-r--r--servers/rendering/renderer_scene_render.h2
-rw-r--r--servers/rendering/rendering_server_default.h2
-rw-r--r--servers/rendering_server.cpp4
-rw-r--r--servers/rendering_server.h9
-rw-r--r--tests/core/templates/test_vector.h25
-rw-r--r--tests/core/variant/test_array.h35
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch73
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp1
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp1
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp1
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp1
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp1
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp2
118 files changed, 3558 insertions, 2031 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 6f26288eb7..2f5fd05e6a 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -584,6 +584,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_NONE);
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 8fe1ac29b3..7d42a9c8d2 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -806,38 +806,26 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
// To find the path of the remapped resource, we extract the locale name after
// the last ':' to match the project locale.
- // We also fall back in case of regional locales as done in TranslationServer::translate
- // (e.g. 'ru_RU' -> 'ru' if the former has no specific mapping).
String locale = TranslationServer::get_singleton()->get_locale();
ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
- String lang = TranslationServer::get_language_code(locale);
Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
- bool near_match = false;
+ int best_score = 0;
for (int i = 0; i < res_remaps.size(); i++) {
int split = res_remaps[i].rfind(":");
if (split == -1) {
continue;
}
-
String l = res_remaps[i].substr(split + 1).strip_edges();
- if (l == locale) { // Exact match.
- new_path = res_remaps[i].left(split);
- break;
- } else if (near_match) {
- continue; // Already found near match, keep going for potential exact match.
- }
-
- // No exact match (e.g. locale 'ru_RU' but remap is 'ru'), let's look further
- // for a near match (same language code, i.e. first 2 or 3 letters before
- // regional code, if included).
- if (TranslationServer::get_language_code(l) == lang) {
- // Language code matches, that's a near match. Keep looking for exact match.
- near_match = true;
+ int score = TranslationServer::get_singleton()->compare_locales(locale, l);
+ if (score >= best_score) {
new_path = res_remaps[i].left(split);
- continue;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
}
}
diff --git a/core/object/object.h b/core/object/object.h
index 602bd3cda1..63130a1aef 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -94,6 +94,7 @@ enum PropertyHint {
PROPERTY_HINT_INT_IS_OBJECTID,
PROPERTY_HINT_ARRAY_TYPE,
PROPERTY_HINT_INT_IS_POINTER,
+ PROPERTY_HINT_LOCALE_ID,
PROPERTY_HINT_MAX,
// When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit
};
diff --git a/core/os/os.h b/core/os/os.h
index 6b4e2798bd..36d85da70b 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -295,6 +295,9 @@ public:
virtual void debug_break();
virtual int get_exit_code() const;
+ // `set_exit_code` should only be used from `SceneTree` (or from a similar
+ // level, e.g. from the `Main::start` if leaving without creating a `SceneTree`).
+ // For other components, `SceneTree.quit()` should be used instead.
virtual void set_exit_code(int p_code);
virtual int get_processor_count() const;
diff --git a/core/string/locales.h b/core/string/locales.h
new file mode 100644
index 0000000000..9ef6dee5ac
--- /dev/null
+++ b/core/string/locales.h
@@ -0,0 +1,1196 @@
+/*************************************************************************/
+/* locales.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 LOCALES_H
+#define LOCALES_H
+
+// Windows has some weird locale identifiers which do not honor the ISO 639-1
+// standardized nomenclature. Whenever those don't conflict with existing ISO
+// identifiers, we override them.
+//
+// Reference:
+// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx
+
+static const char *locale_renames[][2] = {
+ { "in", "id" }, // Indonesian
+ { "iw", "he" }, // Hebrew
+ { "no", "nb" }, // Norwegian Bokmål
+ { nullptr, nullptr }
+};
+
+// Additional script information to preferred scripts.
+// Language code, script code, default country, supported countries.
+// Reference:
+// - https://lh.2xlibre.net/locales/
+// - https://www.localeplanet.com/icu/index.html
+// - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
+
+static const char *locale_scripts[][4] = {
+ { "az", "Latn", "", "AZ" },
+ { "az", "Arab", "", "IR" },
+ { "bs", "Latn", "", "BA" },
+ { "ff", "Latn", "", "BF,CM,GH,GM,GN,GW,LR,MR,NE,NG,SL,SN" },
+ { "pa", "Arab", "PK", "PK" },
+ { "pa", "Guru", "IN", "IN" },
+ { "sd", "Arab", "PK", "PK" },
+ { "sd", "Deva", "IN", "IN" },
+ { "shi", "Tfng", "", "MA" },
+ { "sr", "Cyrl", "", "BA,RS,XK" },
+ { "sr", "Latn", "", "ME" },
+ { "uz", "Latn", "", "UZ" },
+ { "uz", "Arab", "AF", "AF" },
+ { "vai", "Vaii", "", "LR" },
+ { "yue", "Hans", "CN", "CN" },
+ { "yue", "Hant", "HK", "HK" },
+ { "zh", "Hans", "CN", "CN,SG" },
+ { "zh", "Hant", "TW", "HK,MO,TW" },
+ { nullptr, nullptr, nullptr, nullptr }
+};
+
+// Additional mapping for outdated, temporary or exceptionally reserved country codes.
+// Reference:
+// - https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+// - https://www.iso.org/obp/ui/#search/code/
+
+static const char *country_renames[][2] = {
+ { "BU", "MM" }, // Burma, name changed to Myanmar.
+ { "KV", "XK" }, // Kosovo (temporary FIPS code to European Commission code), no official ISO code assigned.
+ { "TP", "TL" }, // East Timor, name changed to Timor-Leste.
+ { "UK", "GB" }, // United Kingdom, exceptionally reserved code.
+ { nullptr, nullptr }
+};
+
+// Country code, country name.
+// Reference:
+// - https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+// - https://www.iso.org/obp/ui/#search/code/
+
+static const char *country_names[][2] = {
+ { "AC", "Ascension Island" }, // Exceptionally reserved.
+ { "AD", "Andorra" },
+ { "AE", "United Arab Emirates" },
+ { "AF", "Afghanistan" },
+ { "AG", "Antigua and Barbuda" },
+ { "AI", "Anguilla" },
+ { "AL", "Albania" },
+ { "AM", "Armenia" },
+ { "AN", "Netherlands Antilles" }, // Transitionally reserved, divided into BQ, CW and SX.
+ { "AO", "Angola" },
+ { "AQ", "Antarctica" },
+ { "AR", "Argentina" },
+ { "AS", "American Samoa" },
+ { "AT", "Austria" },
+ { "AU", "Australia" },
+ { "AW", "Aruba" },
+ { "AX", "Åland Islands" },
+ { "AZ", "Azerbaijan" },
+ { "BA", "Bosnia and Herzegovina" },
+ { "BB", "Barbados" },
+ { "BD", "Bangladesh" },
+ { "BE", "Belgium" },
+ { "BF", "Burkina Faso" },
+ { "BG", "Bulgaria" },
+ { "BH", "Bahrain" },
+ { "BI", "Burundi" },
+ { "BJ", "Benin" },
+ { "BL", "St. Barthélemy" },
+ { "BM", "Bermuda" },
+ { "BN", "Brunei" },
+ { "BO", "Bolivia" },
+ { "BQ", "Caribbean Netherlands" },
+ { "BR", "Brazil" },
+ { "BS", "Bahamas" },
+ { "BT", "Bhutan" },
+ { "BV", "Bouvet Island" },
+ { "BW", "Botswana" },
+ { "BY", "Belarus" },
+ { "BZ", "Belize" },
+ { "CA", "Canada" },
+ { "CC", "Cocos (Keeling) Islands" },
+ { "CD", "Congo - Kinshasa" },
+ { "CF", "Central African Republic" },
+ { "CG", "Congo - Brazzaville" },
+ { "CH", "Switzerland" },
+ { "CI", "Côte d'Ivoire" },
+ { "CK", "Cook Islands" },
+ { "CL", "Chile" },
+ { "CM", "Cameroon" },
+ { "CN", "China" },
+ { "CO", "Colombia" },
+ { "CP", "Clipperton Island" }, // Exceptionally reserved.
+ { "CR", "Costa Rica" },
+ { "CQ", "Island of Sark" }, // Exceptionally reserved.
+ { "CU", "Cuba" },
+ { "CV", "Cabo Verde" },
+ { "CW", "Curaçao" },
+ { "CX", "Christmas Island" },
+ { "CY", "Cyprus" },
+ { "CZ", "Czechia" },
+ { "DE", "Germany" },
+ { "DG", "Diego Garcia" }, // Exceptionally reserved.
+ { "DJ", "Djibouti" },
+ { "DK", "Denmark" },
+ { "DM", "Dominica" },
+ { "DO", "Dominican Republic" },
+ { "DZ", "Algeria" },
+ { "EA", "Ceuta and Melilla" }, // Exceptionally reserved.
+ { "EC", "Ecuador" },
+ { "EE", "Estonia" },
+ { "EG", "Egypt" },
+ { "EH", "Western Sahara" },
+ { "ER", "Eritrea" },
+ { "ES", "Spain" },
+ { "ET", "Ethiopia" },
+ { "EU", "European Union" }, // Exceptionally reserved.
+ { "EZ", "Eurozone" }, // Exceptionally reserved.
+ { "FI", "Finland" },
+ { "FJ", "Fiji" },
+ { "FK", "Falkland Islands" },
+ { "FM", "Micronesia" },
+ { "FO", "Faroe Islands" },
+ { "FR", "France" },
+ { "FX", "France, Metropolitan" }, // Exceptionally reserved.
+ { "GA", "Gabon" },
+ { "GB", "United Kingdom" },
+ { "GD", "Grenada" },
+ { "GE", "Georgia" },
+ { "GF", "French Guiana" },
+ { "GG", "Guernsey" },
+ { "GH", "Ghana" },
+ { "GI", "Gibraltar" },
+ { "GL", "Greenland" },
+ { "GM", "Gambia" },
+ { "GN", "Guinea" },
+ { "GP", "Guadeloupe" },
+ { "GQ", "Equatorial Guinea" },
+ { "GR", "Greece" },
+ { "GS", "South Georgia and South Sandwich Islands" },
+ { "GT", "Guatemala" },
+ { "GU", "Guam" },
+ { "GW", "Guinea-Bissau" },
+ { "GY", "Guyana" },
+ { "HK", "Hong Kong" },
+ { "HM", "Heard Island and McDonald Islands" },
+ { "HN", "Honduras" },
+ { "HR", "Croatia" },
+ { "HT", "Haiti" },
+ { "HU", "Hungary" },
+ { "IC", "Canary Islands" }, // Exceptionally reserved.
+ { "ID", "Indonesia" },
+ { "IE", "Ireland" },
+ { "IL", "Israel" },
+ { "IM", "Isle of Man" },
+ { "IN", "India" },
+ { "IO", "British Indian Ocean Territory" },
+ { "IQ", "Iraq" },
+ { "IR", "Iran" },
+ { "IS", "Iceland" },
+ { "IT", "Italy" },
+ { "JE", "Jersey" },
+ { "JM", "Jamaica" },
+ { "JO", "Jordan" },
+ { "JP", "Japan" },
+ { "KE", "Kenya" },
+ { "KG", "Kyrgyzstan" },
+ { "KH", "Cambodia" },
+ { "KI", "Kiribati" },
+ { "KM", "Comoros" },
+ { "KN", "St. Kitts and Nevis" },
+ { "KP", "North Korea" },
+ { "KR", "South Korea" },
+ { "KW", "Kuwait" },
+ { "KY", "Cayman Islands" },
+ { "KZ", "Kazakhstan" },
+ { "LA", "Laos" },
+ { "LB", "Lebanon" },
+ { "LC", "St. Lucia" },
+ { "LI", "Liechtenstein" },
+ { "LK", "Sri Lanka" },
+ { "LR", "Liberia" },
+ { "LS", "Lesotho" },
+ { "LT", "Lithuania" },
+ { "LU", "Luxembourg" },
+ { "LV", "Latvia" },
+ { "LY", "Libya" },
+ { "MA", "Morocco" },
+ { "MC", "Monaco" },
+ { "MD", "Moldova" },
+ { "ME", "Montenegro" },
+ { "MF", "St. Martin" },
+ { "MG", "Madagascar" },
+ { "MH", "Marshall Islands" },
+ { "MK", "North Macedonia" },
+ { "ML", "Mali" },
+ { "MM", "Myanmar" },
+ { "MN", "Mongolia" },
+ { "MO", "Macao" },
+ { "MP", "Northern Mariana Islands" },
+ { "MQ", "Martinique" },
+ { "MR", "Mauritania" },
+ { "MS", "Montserrat" },
+ { "MT", "Malta" },
+ { "MU", "Mauritius" },
+ { "MV", "Maldives" },
+ { "MW", "Malawi" },
+ { "MX", "Mexico" },
+ { "MY", "Malaysia" },
+ { "MZ", "Mozambique" },
+ { "NA", "Namibia" },
+ { "NC", "New Caledonia" },
+ { "NE", "Niger" },
+ { "NF", "Norfolk Island" },
+ { "NG", "Nigeria" },
+ { "NI", "Nicaragua" },
+ { "NL", "Netherlands" },
+ { "NO", "Norway" },
+ { "NP", "Nepal" },
+ { "NR", "Nauru" },
+ { "NU", "Niue" },
+ { "NZ", "New Zealand" },
+ { "OM", "Oman" },
+ { "PA", "Panama" },
+ { "PE", "Peru" },
+ { "PF", "French Polynesia" },
+ { "PG", "Papua New Guinea" },
+ { "PH", "Philippines" },
+ { "PK", "Pakistan" },
+ { "PL", "Poland" },
+ { "PM", "St. Pierre and Miquelon" },
+ { "PN", "Pitcairn Islands" },
+ { "PR", "Puerto Rico" },
+ { "PS", "Palestine" },
+ { "PT", "Portugal" },
+ { "PW", "Palau" },
+ { "PY", "Paraguay" },
+ { "QA", "Qatar" },
+ { "RE", "Réunion" },
+ { "RO", "Romania" },
+ { "RS", "Serbia" },
+ { "RU", "Russia" },
+ { "RW", "Rwanda" },
+ { "SA", "Saudi Arabia" },
+ { "SB", "Solomon Islands" },
+ { "SC", "Seychelles" },
+ { "SD", "Sudan" },
+ { "SE", "Sweden" },
+ { "SG", "Singapore" },
+ { "SH", "St. Helena, Ascension and Tristan da Cunha" },
+ { "SI", "Slovenia" },
+ { "SJ", "Svalbard and Jan Mayen" },
+ { "SK", "Slovakia" },
+ { "SL", "Sierra Leone" },
+ { "SM", "San Marino" },
+ { "SN", "Senegal" },
+ { "SO", "Somalia" },
+ { "SR", "Suriname" },
+ { "SS", "South Sudan" },
+ { "ST", "Sao Tome and Principe" },
+ { "SV", "El Salvador" },
+ { "SX", "Sint Maarten" },
+ { "SY", "Syria" },
+ { "SZ", "Eswatini" },
+ { "TA", "Tristan da Cunha" }, // Exceptionally reserved.
+ { "TC", "Turks and Caicos Islands" },
+ { "TD", "Chad" },
+ { "TF", "French Southern Territories" },
+ { "TG", "Togo" },
+ { "TH", "Thailand" },
+ { "TJ", "Tajikistan" },
+ { "TK", "Tokelau" },
+ { "TL", "Timor-Leste" },
+ { "TM", "Turkmenistan" },
+ { "TN", "Tunisia" },
+ { "TO", "Tonga" },
+ { "TR", "Turkey" },
+ { "TT", "Trinidad and Tobago" },
+ { "TV", "Tuvalu" },
+ { "TW", "Taiwan" },
+ { "TZ", "Tanzania" },
+ { "UA", "Ukraine" },
+ { "UG", "Uganda" },
+ { "UM", "U.S. Outlying Islands" },
+ { "US", "United States of America" },
+ { "UY", "Uruguay" },
+ { "UZ", "Uzbekistan" },
+ { "VA", "Holy See" },
+ { "VC", "St. Vincent and the Grenadines" },
+ { "VE", "Venezuela" },
+ { "VG", "British Virgin Islands" },
+ { "VI", "U.S. Virgin Islands" },
+ { "VN", "Viet Nam" },
+ { "VU", "Vanuatu" },
+ { "WF", "Wallis and Futuna" },
+ { "WS", "Samoa" },
+ { "XK", "Kosovo" }, // Temporary code, no official ISO code assigned.
+ { "YE", "Yemen" },
+ { "YT", "Mayotte" },
+ { "ZA", "South Africa" },
+ { "ZM", "Zambia" },
+ { "ZW", "Zimbabwe" },
+ { nullptr, nullptr }
+};
+
+// Languages code, language name.
+// Reference:
+// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+// - https://www.localeplanet.com/icu/index.html
+// - https://lh.2xlibre.net/locales/
+
+static const char *language_list[][2] = {
+ { "aa", "Afar" },
+ { "ab", "Abkhazian" },
+ { "ace", "Achinese" },
+ { "ach", "Acoli" },
+ { "ada", "Adangme" },
+ { "ady", "Adyghe" },
+ { "ae", "Avestan" },
+ { "aeb", "Tunisian Arabic" },
+ { "af", "Afrikaans" },
+ { "afh", "Afrihili" },
+ { "agq", "Aghem" },
+ { "ain", "Ainu" },
+ { "agr", "Aguaruna" },
+ { "ak", "Akan" },
+ { "akk", "Akkadian" },
+ { "akz", "Alabama" },
+ { "ale", "Aleut" },
+ { "aln", "Gheg Albanian" },
+ { "alt", "Southern Altai" },
+ { "am", "Amharic" },
+ { "an", "Aragonese" },
+ { "ang", "Old English" },
+ { "anp", "Angika" },
+ { "ar", "Arabic" },
+ { "arc", "Aramaic" },
+ { "arn", "Mapudungun" },
+ { "aro", "Araona" },
+ { "arp", "Arapaho" },
+ { "arq", "Algerian Arabic" },
+ { "ars", "Najdi Arabic" },
+ { "arw", "Arawak" },
+ { "ary", "Moroccan Arabic" },
+ { "arz", "Egyptian Arabic" },
+ { "as", "Assamese" },
+ { "asa", "Asu" },
+ { "ase", "American Sign Language" },
+ { "ast", "Asturian" },
+ { "av", "Avaric" },
+ { "avk", "Kotava" },
+ { "awa", "Awadhi" },
+ { "ayc", "Southern Aymara" },
+ { "ay", "Aymara" },
+ { "az", "Azerbaijani" },
+ { "ba", "Bashkir" },
+ { "bal", "Baluchi" },
+ { "ban", "Balinese" },
+ { "bar", "Bavarian" },
+ { "bas", "Bassa" },
+ { "bax", "Bamun" },
+ { "bbc", "Batak Toba" },
+ { "bbj", "Ghomala" },
+ { "be", "Belarusian" },
+ { "bej", "Beja" },
+ { "bem", "Bemba" },
+ { "ber", "Berber" },
+ { "bew", "Betawi" },
+ { "bez", "Bena" },
+ { "bfd", "Bafut" },
+ { "bfq", "Badaga" },
+ { "bg", "Bulgarian" },
+ { "bhb", "Bhili" },
+ { "bgn", "Western Balochi" },
+ { "bho", "Bhojpuri" },
+ { "bi", "Bislama" },
+ { "bik", "Bikol" },
+ { "bin", "Bini" },
+ { "bjn", "Banjar" },
+ { "bkm", "Kom" },
+ { "bla", "Siksika" },
+ { "bm", "Bambara" },
+ { "bn", "Bengali" },
+ { "bo", "Tibetan" },
+ { "bpy", "Bishnupriya" },
+ { "bqi", "Bakhtiari" },
+ { "br", "Breton" },
+ { "brh", "Brahui" },
+ { "brx", "Bodo" },
+ { "bs", "Bosnian" },
+ { "bss", "Akoose" },
+ { "bua", "Buriat" },
+ { "bug", "Buginese" },
+ { "bum", "Bulu" },
+ { "byn", "Bilin" },
+ { "byv", "Medumba" },
+ { "ca", "Catalan" },
+ { "cad", "Caddo" },
+ { "car", "Carib" },
+ { "cay", "Cayuga" },
+ { "cch", "Atsam" },
+ { "ccp", "Chakma" },
+ { "ce", "Chechen" },
+ { "ceb", "Cebuano" },
+ { "cgg", "Chiga" },
+ { "ch", "Chamorro" },
+ { "chb", "Chibcha" },
+ { "chg", "Chagatai" },
+ { "chk", "Chuukese" },
+ { "chm", "Mari" },
+ { "chn", "Chinook Jargon" },
+ { "cho", "Choctaw" },
+ { "chp", "Chipewyan" },
+ { "chr", "Cherokee" },
+ { "chy", "Cheyenne" },
+ { "cic", "Chickasaw" },
+ { "ckb", "Central Kurdish" },
+ { "csb", "Kashubian" },
+ { "cmn", "Mandarin Chinese" },
+ { "co", "Corsican" },
+ { "cop", "Coptic" },
+ { "cps", "Capiznon" },
+ { "cr", "Cree" },
+ { "crh", "Crimean Tatar" },
+ { "crs", "Seselwa Creole French" },
+ { "cs", "Czech" },
+ { "csb", "Kashubian" },
+ { "cu", "Church Slavic" },
+ { "cv", "Chuvash" },
+ { "cy", "Welsh" },
+ { "da", "Danish" },
+ { "dak", "Dakota" },
+ { "dar", "Dargwa" },
+ { "dav", "Taita" },
+ { "de", "German" },
+ { "del", "Delaware" },
+ { "den", "Slave" },
+ { "dgr", "Dogrib" },
+ { "din", "Dinka" },
+ { "dje", "Zarma" },
+ { "doi", "Dogri" },
+ { "dsb", "Lower Sorbian" },
+ { "dtp", "Central Dusun" },
+ { "dua", "Duala" },
+ { "dum", "Middle Dutch" },
+ { "dv", "Dhivehi" },
+ { "dyo", "Jola-Fonyi" },
+ { "dyu", "Dyula" },
+ { "dz", "Dzongkha" },
+ { "dzg", "Dazaga" },
+ { "ebu", "Embu" },
+ { "ee", "Ewe" },
+ { "efi", "Efik" },
+ { "egl", "Emilian" },
+ { "egy", "Ancient Egyptian" },
+ { "eka", "Ekajuk" },
+ { "el", "Greek" },
+ { "elx", "Elamite" },
+ { "en", "English" },
+ { "enm", "Middle English" },
+ { "eo", "Esperanto" },
+ { "es", "Spanish" },
+ { "esu", "Central Yupik" },
+ { "et", "Estonian" },
+ { "eu", "Basque" },
+ { "ewo", "Ewondo" },
+ { "ext", "Extremaduran" },
+ { "fa", "Persian" },
+ { "fan", "Fang" },
+ { "fat", "Fanti" },
+ { "ff", "Fulah" },
+ { "fi", "Finnish" },
+ { "fil", "Filipino" },
+ { "fit", "Tornedalen Finnish" },
+ { "fj", "Fijian" },
+ { "fo", "Faroese" },
+ { "fon", "Fon" },
+ { "fr", "French" },
+ { "frc", "Cajun French" },
+ { "frm", "Middle French" },
+ { "fro", "Old French" },
+ { "frp", "Arpitan" },
+ { "frr", "Northern Frisian" },
+ { "frs", "Eastern Frisian" },
+ { "fur", "Friulian" },
+ { "fy", "Western Frisian" },
+ { "ga", "Irish" },
+ { "gaa", "Ga" },
+ { "gag", "Gagauz" },
+ { "gan", "Gan Chinese" },
+ { "gay", "Gayo" },
+ { "gba", "Gbaya" },
+ { "gbz", "Zoroastrian Dari" },
+ { "gd", "Scottish Gaelic" },
+ { "gez", "Geez" },
+ { "gil", "Gilbertese" },
+ { "gl", "Galician" },
+ { "glk", "Gilaki" },
+ { "gmh", "Middle High German" },
+ { "gn", "Guarani" },
+ { "goh", "Old High German" },
+ { "gom", "Goan Konkani" },
+ { "gon", "Gondi" },
+ { "gor", "Gorontalo" },
+ { "got", "Gothic" },
+ { "grb", "Grebo" },
+ { "grc", "Ancient Greek" },
+ { "gsw", "Swiss German" },
+ { "gu", "Gujarati" },
+ { "guc", "Wayuu" },
+ { "gur", "Frafra" },
+ { "guz", "Gusii" },
+ { "gv", "Manx" },
+ { "gwi", "Gwichʼin" },
+ { "ha", "Hausa" },
+ { "hai", "Haida" },
+ { "hak", "Hakka Chinese" },
+ { "haw", "Hawaiian" },
+ { "he", "Hebrew" },
+ { "hi", "Hindi" },
+ { "hif", "Fiji Hindi" },
+ { "hil", "Hiligaynon" },
+ { "hit", "Hittite" },
+ { "hmn", "Hmong" },
+ { "ho", "Hiri Motu" },
+ { "hne", "Chhattisgarhi" },
+ { "hr", "Croatian" },
+ { "hsb", "Upper Sorbian" },
+ { "hsn", "Xiang Chinese" },
+ { "ht", "Haitian" },
+ { "hu", "Hungarian" },
+ { "hup", "Hupa" },
+ { "hus", "Huastec" },
+ { "hy", "Armenian" },
+ { "hz", "Herero" },
+ { "ia", "Interlingua" },
+ { "iba", "Iban" },
+ { "ibb", "Ibibio" },
+ { "id", "Indonesian" },
+ { "ie", "Interlingue" },
+ { "ig", "Igbo" },
+ { "ii", "Sichuan Yi" },
+ { "ik", "Inupiaq" },
+ { "ilo", "Iloko" },
+ { "inh", "Ingush" },
+ { "io", "Ido" },
+ { "is", "Icelandic" },
+ { "it", "Italian" },
+ { "iu", "Inuktitut" },
+ { "izh", "Ingrian" },
+ { "ja", "Japanese" },
+ { "jam", "Jamaican Creole English" },
+ { "jbo", "Lojban" },
+ { "jgo", "Ngomba" },
+ { "jmc", "Machame" },
+ { "jpr", "Judeo-Persian" },
+ { "jrb", "Judeo-Arabic" },
+ { "jut", "Jutish" },
+ { "jv", "Javanese" },
+ { "ka", "Georgian" },
+ { "kaa", "Kara-Kalpak" },
+ { "kab", "Kabyle" },
+ { "kac", "Kachin" },
+ { "kaj", "Jju" },
+ { "kam", "Kamba" },
+ { "kaw", "Kawi" },
+ { "kbd", "Kabardian" },
+ { "kbl", "Kanembu" },
+ { "kcg", "Tyap" },
+ { "kde", "Makonde" },
+ { "kea", "Kabuverdianu" },
+ { "ken", "Kenyang" },
+ { "kfo", "Koro" },
+ { "kg", "Kongo" },
+ { "kgp", "Kaingang" },
+ { "kha", "Khasi" },
+ { "kho", "Khotanese" },
+ { "khq", "Koyra Chiini" },
+ { "khw", "Khowar" },
+ { "ki", "Kikuyu" },
+ { "kiu", "Kirmanjki" },
+ { "kj", "Kuanyama" },
+ { "kk", "Kazakh" },
+ { "kkj", "Kako" },
+ { "kl", "Kalaallisut" },
+ { "kln", "Kalenjin" },
+ { "km", "Central Khmer" },
+ { "kmb", "Kimbundu" },
+ { "kn", "Kannada" },
+ { "ko", "Korean" },
+ { "koi", "Komi-Permyak" },
+ { "kok", "Konkani" },
+ { "kos", "Kosraean" },
+ { "kpe", "Kpelle" },
+ { "kr", "Kanuri" },
+ { "krc", "Karachay-Balkar" },
+ { "kri", "Krio" },
+ { "krj", "Kinaray-a" },
+ { "krl", "Karelian" },
+ { "kru", "Kurukh" },
+ { "ks", "Kashmiri" },
+ { "ksb", "Shambala" },
+ { "ksf", "Bafia" },
+ { "ksh", "Colognian" },
+ { "ku", "Kurdish" },
+ { "kum", "Kumyk" },
+ { "kut", "Kutenai" },
+ { "kv", "Komi" },
+ { "kw", "Cornish" },
+ { "ky", "Kirghiz" },
+ { "lag", "Langi" },
+ { "la", "Latin" },
+ { "lad", "Ladino" },
+ { "lag", "Langi" },
+ { "lah", "Lahnda" },
+ { "lam", "Lamba" },
+ { "lb", "Luxembourgish" },
+ { "lez", "Lezghian" },
+ { "lfn", "Lingua Franca Nova" },
+ { "lg", "Ganda" },
+ { "li", "Limburgan" },
+ { "lij", "Ligurian" },
+ { "liv", "Livonian" },
+ { "lkt", "Lakota" },
+ { "lmo", "Lombard" },
+ { "ln", "Lingala" },
+ { "lo", "Lao" },
+ { "lol", "Mongo" },
+ { "lou", "Louisiana Creole" },
+ { "loz", "Lozi" },
+ { "lrc", "Northern Luri" },
+ { "lt", "Lithuanian" },
+ { "ltg", "Latgalian" },
+ { "lu", "Luba-Katanga" },
+ { "lua", "Luba-Lulua" },
+ { "lui", "Luiseno" },
+ { "lun", "Lunda" },
+ { "luo", "Luo" },
+ { "lus", "Mizo" },
+ { "luy", "Luyia" },
+ { "lv", "Latvian" },
+ { "lzh", "Literary Chinese" },
+ { "lzz", "Laz" },
+ { "mad", "Madurese" },
+ { "maf", "Mafa" },
+ { "mag", "Magahi" },
+ { "mai", "Maithili" },
+ { "mak", "Makasar" },
+ { "man", "Mandingo" },
+ { "mas", "Masai" },
+ { "mde", "Maba" },
+ { "mdf", "Moksha" },
+ { "mdr", "Mandar" },
+ { "men", "Mende" },
+ { "mer", "Meru" },
+ { "mfe", "Morisyen" },
+ { "mg", "Malagasy" },
+ { "mga", "Middle Irish" },
+ { "mgh", "Makhuwa-Meetto" },
+ { "mgo", "Metaʼ" },
+ { "mh", "Marshallese" },
+ { "mhr", "Eastern Mari" },
+ { "mi", "Māori" },
+ { "mic", "Mi'kmaq" },
+ { "min", "Minangkabau" },
+ { "miq", "Mískito" },
+ { "mjw", "Karbi" },
+ { "mk", "Macedonian" },
+ { "ml", "Malayalam" },
+ { "mn", "Mongolian" },
+ { "mnc", "Manchu" },
+ { "mni", "Manipuri" },
+ { "mnw", "Mon" },
+ { "mos", "Mossi" },
+ { "moh", "Mohawk" },
+ { "mr", "Marathi" },
+ { "mrj", "Western Mari" },
+ { "ms", "Malay" },
+ { "mt", "Maltese" },
+ { "mua", "Mundang" },
+ { "mus", "Muscogee" },
+ { "mwl", "Mirandese" },
+ { "mwr", "Marwari" },
+ { "mwv", "Mentawai" },
+ { "my", "Burmese" },
+ { "mye", "Myene" },
+ { "myv", "Erzya" },
+ { "mzn", "Mazanderani" },
+ { "na", "Nauru" },
+ { "nah", "Nahuatl" },
+ { "nan", "Min Nan Chinese" },
+ { "nap", "Neapolitan" },
+ { "naq", "Nama" },
+ { "nan", "Min Nan Chinese" },
+ { "nb", "Norwegian Bokmål" },
+ { "nd", "North Ndebele" },
+ { "nds", "Low German" },
+ { "ne", "Nepali" },
+ { "new", "Newari" },
+ { "nhn", "Central Nahuatl" },
+ { "ng", "Ndonga" },
+ { "nia", "Nias" },
+ { "niu", "Niuean" },
+ { "njo", "Ao Naga" },
+ { "nl", "Dutch" },
+ { "nmg", "Kwasio" },
+ { "nn", "Norwegian Nynorsk" },
+ { "nnh", "Ngiemboon" },
+ { "nog", "Nogai" },
+ { "non", "Old Norse" },
+ { "nov", "Novial" },
+ { "nqo", "N'ko" },
+ { "nr", "South Ndebele" },
+ { "nso", "Pedi" },
+ { "nus", "Nuer" },
+ { "nv", "Navajo" },
+ { "nwc", "Classical Newari" },
+ { "ny", "Nyanja" },
+ { "nym", "Nyamwezi" },
+ { "nyn", "Nyankole" },
+ { "nyo", "Nyoro" },
+ { "nzi", "Nzima" },
+ { "oc", "Occitan" },
+ { "oj", "Ojibwa" },
+ { "om", "Oromo" },
+ { "or", "Odia" },
+ { "os", "Ossetic" },
+ { "osa", "Osage" },
+ { "ota", "Ottoman Turkish" },
+ { "pa", "Panjabi" },
+ { "pag", "Pangasinan" },
+ { "pal", "Pahlavi" },
+ { "pam", "Pampanga" },
+ { "pap", "Papiamento" },
+ { "pau", "Palauan" },
+ { "pcd", "Picard" },
+ { "pcm", "Nigerian Pidgin" },
+ { "pdc", "Pennsylvania German" },
+ { "pdt", "Plautdietsch" },
+ { "peo", "Old Persian" },
+ { "pfl", "Palatine German" },
+ { "phn", "Phoenician" },
+ { "pi", "Pali" },
+ { "pl", "Polish" },
+ { "pms", "Piedmontese" },
+ { "pnt", "Pontic" },
+ { "pon", "Pohnpeian" },
+ { "pr", "Pirate" },
+ { "prg", "Prussian" },
+ { "pro", "Old Provençal" },
+ { "prs", "Dari" },
+ { "ps", "Pushto" },
+ { "pt", "Portuguese" },
+ { "qu", "Quechua" },
+ { "quc", "K'iche" },
+ { "qug", "Chimborazo Highland Quichua" },
+ { "quy", "Ayacucho Quechua" },
+ { "quz", "Cusco Quechua" },
+ { "raj", "Rajasthani" },
+ { "rap", "Rapanui" },
+ { "rar", "Rarotongan" },
+ { "rgn", "Romagnol" },
+ { "rif", "Riffian" },
+ { "rm", "Romansh" },
+ { "rn", "Rundi" },
+ { "ro", "Romanian" },
+ { "rof", "Rombo" },
+ { "rom", "Romany" },
+ { "rtm", "Rotuman" },
+ { "ru", "Russian" },
+ { "rue", "Rusyn" },
+ { "rug", "Roviana" },
+ { "rup", "Aromanian" },
+ { "rw", "Kinyarwanda" },
+ { "rwk", "Rwa" },
+ { "sa", "Sanskrit" },
+ { "sad", "Sandawe" },
+ { "sah", "Sakha" },
+ { "sam", "Samaritan Aramaic" },
+ { "saq", "Samburu" },
+ { "sas", "Sasak" },
+ { "sat", "Santali" },
+ { "saz", "Saurashtra" },
+ { "sba", "Ngambay" },
+ { "sbp", "Sangu" },
+ { "sc", "Sardinian" },
+ { "scn", "Sicilian" },
+ { "sco", "Scots" },
+ { "sd", "Sindhi" },
+ { "sdc", "Sassarese Sardinian" },
+ { "sdh", "Southern Kurdish" },
+ { "se", "Northern Sami" },
+ { "see", "Seneca" },
+ { "seh", "Sena" },
+ { "sei", "Seri" },
+ { "sel", "Selkup" },
+ { "ses", "Koyraboro Senni" },
+ { "sg", "Sango" },
+ { "sga", "Old Irish" },
+ { "sgs", "Samogitian" },
+ { "sh", "Serbo-Croatian" },
+ { "shi", "Tachelhit" },
+ { "shn", "Shan" },
+ { "shs", "Shuswap" },
+ { "shu", "Chadian Arabic" },
+ { "si", "Sinhala" },
+ { "sid", "Sidamo" },
+ { "sk", "Slovak" },
+ { "sl", "Slovenian" },
+ { "sli", "Lower Silesian" },
+ { "sly", "Selayar" },
+ { "sm", "Samoan" },
+ { "sma", "Southern Sami" },
+ { "smj", "Lule Sami" },
+ { "smn", "Inari Sami" },
+ { "sms", "Skolt Sami" },
+ { "sn", "Shona" },
+ { "snk", "Soninke" },
+ { "so", "Somali" },
+ { "sog", "Sogdien" },
+ { "son", "Songhai" },
+ { "sq", "Albanian" },
+ { "sr", "Serbian" },
+ { "srn", "Sranan Tongo" },
+ { "srr", "Serer" },
+ { "ss", "Swati" },
+ { "ssy", "Saho" },
+ { "st", "Southern Sotho" },
+ { "stq", "Saterland Frisian" },
+ { "su", "Sundanese" },
+ { "suk", "Sukuma" },
+ { "sus", "Susu" },
+ { "sux", "Sumerian" },
+ { "sv", "Swedish" },
+ { "sw", "Swahili" },
+ { "swb", "Comorian" },
+ { "swc", "Congo Swahili" },
+ { "syc", "Classical Syriac" },
+ { "syr", "Syriac" },
+ { "szl", "Silesian" },
+ { "ta", "Tamil" },
+ { "tcy", "Tulu" },
+ { "te", "Telugu" },
+ { "tem", "Timne" },
+ { "teo", "Teso" },
+ { "ter", "Tereno" },
+ { "tet", "Tetum" },
+ { "tg", "Tajik" },
+ { "th", "Thai" },
+ { "the", "Chitwania Tharu" },
+ { "ti", "Tigrinya" },
+ { "tig", "Tigre" },
+ { "tiv", "Tiv" },
+ { "tk", "Turkmen" },
+ { "tkl", "Tokelau" },
+ { "tkr", "Tsakhur" },
+ { "tl", "Tagalog" },
+ { "tlh", "Klingon" },
+ { "tli", "Tlingit" },
+ { "tly", "Talysh" },
+ { "tmh", "Tamashek" },
+ { "tn", "Tswana" },
+ { "to", "Tongan" },
+ { "tog", "Nyasa Tonga" },
+ { "tpi", "Tok Pisin" },
+ { "tr", "Turkish" },
+ { "tru", "Turoyo" },
+ { "trv", "Taroko" },
+ { "ts", "Tsonga" },
+ { "tsd", "Tsakonian" },
+ { "tsi", "Tsimshian" },
+ { "tt", "Tatar" },
+ { "ttt", "Muslim Tat" },
+ { "tum", "Tumbuka" },
+ { "tvl", "Tuvalu" },
+ { "tw", "Twi" },
+ { "twq", "Tasawaq" },
+ { "ty", "Tahitian" },
+ { "tyv", "Tuvinian" },
+ { "tzm", "Central Atlas Tamazight" },
+ { "udm", "Udmurt" },
+ { "ug", "Uyghur" },
+ { "uga", "Ugaritic" },
+ { "uk", "Ukrainian" },
+ { "umb", "Umbundu" },
+ { "unm", "Unami" },
+ { "ur", "Urdu" },
+ { "uz", "Uzbek" },
+ { "vai", "Vai" },
+ { "ve", "Venda" },
+ { "vec", "Venetian" },
+ { "vep", "Veps" },
+ { "vi", "Vietnamese" },
+ { "vls", "West Flemish" },
+ { "vmf", "Main-Franconian" },
+ { "vo", "Volapük" },
+ { "vot", "Votic" },
+ { "vro", "Võro" },
+ { "vun", "Vunjo" },
+ { "wa", "Walloon" },
+ { "wae", "Walser" },
+ { "wal", "Wolaytta" },
+ { "war", "Waray" },
+ { "was", "Washo" },
+ { "wbp", "Warlpiri" },
+ { "wo", "Wolof" },
+ { "wuu", "Wu Chinese" },
+ { "xal", "Kalmyk" },
+ { "xh", "Xhosa" },
+ { "xmf", "Mingrelian" },
+ { "xog", "Soga" },
+ { "yao", "Yao" },
+ { "yap", "Yapese" },
+ { "yav", "Yangben" },
+ { "ybb", "Yemba" },
+ { "yi", "Yiddish" },
+ { "yo", "Yoruba" },
+ { "yrl", "Nheengatu" },
+ { "yue", "Yue Chinese" },
+ { "yuw", "Papua New Guinea" },
+ { "za", "Zhuang" },
+ { "zap", "Zapotec" },
+ { "zbl", "Blissymbols" },
+ { "zea", "Zeelandic" },
+ { "zen", "Zenaga" },
+ { "zgh", "Standard Moroccan Tamazight" },
+ { "zh", "Chinese" },
+ { "zu", "Zulu" },
+ { "zun", "Zuni" },
+ { "zza", "Zaza" },
+ { nullptr, nullptr }
+};
+
+// Additional regional variants.
+// Variant name, supported languages.
+
+static const char *locale_variants[][2] = {
+ { "valencia", "ca" },
+ { "iqtelif", "tt" },
+ { "saaho", "aa" },
+ { "tradnl", "es" },
+ { nullptr, nullptr },
+};
+
+// Script names and codes (excludes typographic variants, special codes, reserved codes and aliases for combined scripts).
+// Reference:
+// - https://en.wikipedia.org/wiki/ISO_15924
+
+static const char *script_list[][2] = {
+ { "Adlam", "Adlm" },
+ { "Afaka", "Afak" },
+ { "Caucasian Albanian", "Aghb" },
+ { "Ahom", "Ahom" },
+ { "Arabic", "Arab" },
+ { "Imperial Aramaic", "Armi" },
+ { "Armenian", "Armn" },
+ { "Avestan", "Avst" },
+ { "Balinese", "Bali" },
+ { "Bamum", "Bamu" },
+ { "Bassa Vah", "Bass" },
+ { "Batak", "Batk" },
+ { "Bengali", "Beng" },
+ { "Bhaiksuki", "Bhks" },
+ { "Blissymbols", "Blis" },
+ { "Bopomofo", "Bopo" },
+ { "Brahmi", "Brah" },
+ { "Braille", "Brai" },
+ { "Buginese", "Bugi" },
+ { "Buhid", "Buhd" },
+ { "Chakma", "Cakm" },
+ { "Unified Canadian Aboriginal", "Cans" },
+ { "Carian", "Cari" },
+ { "Cham", "Cham" },
+ { "Cherokee", "Cher" },
+ { "Chorasmian", "Chrs" },
+ { "Cirth", "Cirt" },
+ { "Coptic", "Copt" },
+ { "Cypro-Minoan", "Cpmn" },
+ { "Cypriot", "Cprt" },
+ { "Cyrillic", "Cyrl" },
+ { "Devanagari", "Deva" },
+ { "Dives Akuru", "Diak" },
+ { "Dogra", "Dogr" },
+ { "Deseret", "Dsrt" },
+ { "Duployan", "Dupl" },
+ { "Egyptian demotic", "Egyd" },
+ { "Egyptian hieratic", "Egyh" },
+ { "Egyptian hieroglyphs", "Egyp" },
+ { "Elbasan", "Elba" },
+ { "Elymaic", "Elym" },
+ { "Ethiopic", "Ethi" },
+ { "Khutsuri", "Geok" },
+ { "Georgian", "Geor" },
+ { "Glagolitic", "Glag" },
+ { "Gunjala Gondi", "Gong" },
+ { "Masaram Gondi", "Gonm" },
+ { "Gothic", "Goth" },
+ { "Grantha", "Gran" },
+ { "Greek", "Grek" },
+ { "Gujarati", "Gujr" },
+ { "Gurmukhi", "Guru" },
+ { "Hangul", "Hang" },
+ { "Han", "Hani" },
+ { "Hanunoo", "Hano" },
+ { "Simplified", "Hans" },
+ { "Traditional", "Hant" },
+ { "Hatran", "Hatr" },
+ { "Hebrew", "Hebr" },
+ { "Hiragana", "Hira" },
+ { "Anatolian Hieroglyphs", "Hluw" },
+ { "Pahawh Hmong", "Hmng" },
+ { "Nyiakeng Puachue Hmong", "Hmnp" },
+ { "Old Hungarian", "Hung" },
+ { "Indus", "Inds" },
+ { "Old Italic", "Ital" },
+ { "Javanese", "Java" },
+ { "Jurchen", "Jurc" },
+ { "Kayah Li", "Kali" },
+ { "Katakana", "Kana" },
+ { "Kharoshthi", "Khar" },
+ { "Khmer", "Khmr" },
+ { "Khojki", "Khoj" },
+ { "Khitan large script", "Kitl" },
+ { "Khitan small script", "Kits" },
+ { "Kannada", "Knda" },
+ { "Kpelle", "Kpel" },
+ { "Kaithi", "Kthi" },
+ { "Tai Tham", "Lana" },
+ { "Lao", "Laoo" },
+ { "Latin", "Latn" },
+ { "Leke", "Leke" },
+ { "Lepcha", "Lepc" },
+ { "Limbu", "Limb" },
+ { "Linear A", "Lina" },
+ { "Linear B", "Linb" },
+ { "Lisu", "Lisu" },
+ { "Loma", "Loma" },
+ { "Lycian", "Lyci" },
+ { "Lydian", "Lydi" },
+ { "Mahajani", "Mahj" },
+ { "Makasar", "Maka" },
+ { "Mandaic", "Mand" },
+ { "Manichaean", "Mani" },
+ { "Marchen", "Marc" },
+ { "Mayan Hieroglyphs", "Maya" },
+ { "Medefaidrin", "Medf" },
+ { "Mende Kikakui", "Mend" },
+ { "Meroitic Cursive", "Merc" },
+ { "Meroitic Hieroglyphs", "Mero" },
+ { "Malayalam", "Mlym" },
+ { "Modi", "Modi" },
+ { "Mongolian", "Mong" },
+ { "Moon", "Moon" },
+ { "Mro", "Mroo" },
+ { "Meitei Mayek", "Mtei" },
+ { "Multani", "Mult" },
+ { "Myanmar (Burmese)", "Mymr" },
+ { "Nandinagari", "Nand" },
+ { "Old North Arabian", "Narb" },
+ { "Nabataean", "Nbat" },
+ { "Newa", "Newa" },
+ { "Naxi Dongba", "Nkdb" },
+ { "Nakhi Geba", "Nkgb" },
+ { "N'ko", "Nkoo" },
+ { "Nüshu", "Nshu" },
+ { "Ogham", "Ogam" },
+ { "Ol Chiki", "Olck" },
+ { "Old Turkic", "Orkh" },
+ { "Oriya", "Orya" },
+ { "Osage", "Osge" },
+ { "Osmanya", "Osma" },
+ { "Old Uyghur", "Ougr" },
+ { "Palmyrene", "Palm" },
+ { "Pau Cin Hau", "Pauc" },
+ { "Proto-Cuneiform", "Pcun" },
+ { "Proto-Elamite", "Pelm" },
+ { "Old Permic", "Perm" },
+ { "Phags-pa", "Phag" },
+ { "Inscriptional Pahlavi", "Phli" },
+ { "Psalter Pahlavi", "Phlp" },
+ { "Book Pahlavi", "Phlv" },
+ { "Phoenician", "Phnx" },
+ { "Klingon", "Piqd" },
+ { "Miao", "Plrd" },
+ { "Inscriptional Parthian", "Prti" },
+ { "Proto-Sinaitic", "Psin" },
+ { "Ranjana", "Ranj" },
+ { "Rejang", "Rjng" },
+ { "Hanifi Rohingya", "Rohg" },
+ { "Rongorongo", "Roro" },
+ { "Runic", "Runr" },
+ { "Samaritan", "Samr" },
+ { "Sarati", "Sara" },
+ { "Old South Arabian", "Sarb" },
+ { "Saurashtra", "Saur" },
+ { "SignWriting", "Sgnw" },
+ { "Shavian", "Shaw" },
+ { "Sharada", "Shrd" },
+ { "Shuishu", "Shui" },
+ { "Siddham", "Sidd" },
+ { "Khudawadi", "Sind" },
+ { "Sinhala", "Sinh" },
+ { "Sogdian", "Sogd" },
+ { "Old Sogdian", "Sogo" },
+ { "Sora Sompeng", "Sora" },
+ { "Soyombo", "Soyo" },
+ { "Sundanese", "Sund" },
+ { "Syloti Nagri", "Sylo" },
+ { "Syriac", "Syrc" },
+ { "Tagbanwa", "Tagb" },
+ { "Takri", "Takr" },
+ { "Tai Le", "Tale" },
+ { "New Tai Lue", "Talu" },
+ { "Tamil", "Taml" },
+ { "Tangut", "Tang" },
+ { "Tai Viet", "Tavt" },
+ { "Telugu", "Telu" },
+ { "Tengwar", "Teng" },
+ { "Tifinagh", "Tfng" },
+ { "Tagalog", "Tglg" },
+ { "Thaana", "Thaa" },
+ { "Thai", "Thai" },
+ { "Tibetan", "Tibt" },
+ { "Tirhuta", "Tirh" },
+ { "Tangsa", "Tnsa" },
+ { "Toto", "Toto" },
+ { "Ugaritic", "Ugar" },
+ { "Vai", "Vaii" },
+ { "Visible Speech", "Visp" },
+ { "Vithkuqi", "Vith" },
+ { "Warang Citi", "Wara" },
+ { "Wancho", "Wcho" },
+ { "Woleai", "Wole" },
+ { "Old Persian", "Xpeo" },
+ { "Cuneiform", "Xsux" },
+ { "Yezidi", "Yezi" },
+ { "Yi", "Yiii" },
+ { "Zanabazar Square", "Zanb" },
+ { nullptr, nullptr }
+};
+
+#endif // LOCALES_H
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 615ecf2432..5d7e583de1 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -33,793 +33,12 @@
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/os/os.h"
+#include "core/string/locales.h"
#ifdef TOOLS_ENABLED
#include "main/main.h"
#endif
-// ISO 639-1 language codes (and a couple of three-letter ISO 639-2 codes),
-// with the addition of glibc locales with their regional identifiers.
-// This list must match the language names (in English) of locale_names.
-//
-// References:
-// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
-// - https://lh.2xlibre.net/locales/
-// - https://iso639-3.sil.org/
-
-static const char *locale_list[] = {
- "aa", // Afar
- "aa_DJ", // Afar (Djibouti)
- "aa_ER", // Afar (Eritrea)
- "aa_ET", // Afar (Ethiopia)
- "af", // Afrikaans
- "af_ZA", // Afrikaans (South Africa)
- "agr_PE", // Aguaruna (Peru)
- "ak_GH", // Akan (Ghana)
- "am_ET", // Amharic (Ethiopia)
- "an_ES", // Aragonese (Spain)
- "anp_IN", // Angika (India)
- "ar", // Arabic
- "ar_AE", // Arabic (United Arab Emirates)
- "ar_BH", // Arabic (Bahrain)
- "ar_DZ", // Arabic (Algeria)
- "ar_EG", // Arabic (Egypt)
- "ar_IN", // Arabic (India)
- "ar_IQ", // Arabic (Iraq)
- "ar_JO", // Arabic (Jordan)
- "ar_KW", // Arabic (Kuwait)
- "ar_LB", // Arabic (Lebanon)
- "ar_LY", // Arabic (Libya)
- "ar_MA", // Arabic (Morocco)
- "ar_OM", // Arabic (Oman)
- "ar_QA", // Arabic (Qatar)
- "ar_SA", // Arabic (Saudi Arabia)
- "ar_SD", // Arabic (Sudan)
- "ar_SS", // Arabic (South Soudan)
- "ar_SY", // Arabic (Syria)
- "ar_TN", // Arabic (Tunisia)
- "ar_YE", // Arabic (Yemen)
- "as_IN", // Assamese (India)
- "ast_ES", // Asturian (Spain)
- "ayc_PE", // Southern Aymara (Peru)
- "ay_PE", // Aymara (Peru)
- "az", // Azerbaijani
- "az_AZ", // Azerbaijani (Azerbaijan)
- "be", // Belarusian
- "be_BY", // Belarusian (Belarus)
- "bem_ZM", // Bemba (Zambia)
- "ber_DZ", // Berber languages (Algeria)
- "ber_MA", // Berber languages (Morocco)
- "bg", // Bulgarian
- "bg_BG", // Bulgarian (Bulgaria)
- "bhb_IN", // Bhili (India)
- "bho_IN", // Bhojpuri (India)
- "bi_TV", // Bislama (Tuvalu)
- "bn", // Bengali
- "bn_BD", // Bengali (Bangladesh)
- "bn_IN", // Bengali (India)
- "bo", // Tibetan
- "bo_CN", // Tibetan (China)
- "bo_IN", // Tibetan (India)
- "br", // Breton
- "br_FR", // Breton (France)
- "brx_IN", // Bodo (India)
- "bs_BA", // Bosnian (Bosnia and Herzegovina)
- "byn_ER", // Bilin (Eritrea)
- "ca", // Catalan
- "ca_AD", // Catalan (Andorra)
- "ca_ES", // Catalan (Spain)
- "ca_FR", // Catalan (France)
- "ca_IT", // Catalan (Italy)
- "ce_RU", // Chechen (Russia)
- "chr_US", // Cherokee (United States)
- "cmn_TW", // Mandarin Chinese (Taiwan)
- "crh_UA", // Crimean Tatar (Ukraine)
- "csb_PL", // Kashubian (Poland)
- "cs", // Czech
- "cs_CZ", // Czech (Czech Republic)
- "cv_RU", // Chuvash (Russia)
- "cy_GB", // Welsh (United Kingdom)
- "da", // Danish
- "da_DK", // Danish (Denmark)
- "de", // German
- "de_AT", // German (Austria)
- "de_BE", // German (Belgium)
- "de_CH", // German (Switzerland)
- "de_DE", // German (Germany)
- "de_IT", // German (Italy)
- "de_LU", // German (Luxembourg)
- "doi_IN", // Dogri (India)
- "dv_MV", // Dhivehi (Maldives)
- "dz_BT", // Dzongkha (Bhutan)
- "el", // Greek
- "el_CY", // Greek (Cyprus)
- "el_GR", // Greek (Greece)
- "en", // English
- "en_AG", // English (Antigua and Barbuda)
- "en_AU", // English (Australia)
- "en_BW", // English (Botswana)
- "en_CA", // English (Canada)
- "en_DK", // English (Denmark)
- "en_GB", // English (United Kingdom)
- "en_HK", // English (Hong Kong)
- "en_IE", // English (Ireland)
- "en_IL", // English (Israel)
- "en_IN", // English (India)
- "en_NG", // English (Nigeria)
- "en_NZ", // English (New Zealand)
- "en_PH", // English (Philippines)
- "en_SG", // English (Singapore)
- "en_US", // English (United States)
- "en_ZA", // English (South Africa)
- "en_ZM", // English (Zambia)
- "en_ZW", // English (Zimbabwe)
- "eo", // Esperanto
- "es", // Spanish
- "es_AR", // Spanish (Argentina)
- "es_BO", // Spanish (Bolivia)
- "es_CL", // Spanish (Chile)
- "es_CO", // Spanish (Colombia)
- "es_CR", // Spanish (Costa Rica)
- "es_CU", // Spanish (Cuba)
- "es_DO", // Spanish (Dominican Republic)
- "es_EC", // Spanish (Ecuador)
- "es_ES", // Spanish (Spain)
- "es_GT", // Spanish (Guatemala)
- "es_HN", // Spanish (Honduras)
- "es_MX", // Spanish (Mexico)
- "es_NI", // Spanish (Nicaragua)
- "es_PA", // Spanish (Panama)
- "es_PE", // Spanish (Peru)
- "es_PR", // Spanish (Puerto Rico)
- "es_PY", // Spanish (Paraguay)
- "es_SV", // Spanish (El Salvador)
- "es_US", // Spanish (United States)
- "es_UY", // Spanish (Uruguay)
- "es_VE", // Spanish (Venezuela)
- "et", // Estonian
- "et_EE", // Estonian (Estonia)
- "eu", // Basque
- "eu_ES", // Basque (Spain)
- "fa", // Persian
- "fa_IR", // Persian (Iran)
- "ff_SN", // Fulah (Senegal)
- "fi", // Finnish
- "fi_FI", // Finnish (Finland)
- "fil", // Filipino
- "fil_PH", // Filipino (Philippines)
- "fo_FO", // Faroese (Faroe Islands)
- "fr", // French
- "fr_BE", // French (Belgium)
- "fr_CA", // French (Canada)
- "fr_CH", // French (Switzerland)
- "fr_FR", // French (France)
- "fr_LU", // French (Luxembourg)
- "fur_IT", // Friulian (Italy)
- "fy_DE", // Western Frisian (Germany)
- "fy_NL", // Western Frisian (Netherlands)
- "ga", // Irish
- "ga_IE", // Irish (Ireland)
- "gd_GB", // Scottish Gaelic (United Kingdom)
- "gez_ER", // Geez (Eritrea)
- "gez_ET", // Geez (Ethiopia)
- "gl", // Galician
- "gl_ES", // Galician (Spain)
- "gu_IN", // Gujarati (India)
- "gv_GB", // Manx (United Kingdom)
- "hak_TW", // Hakka Chinese (Taiwan)
- "ha_NG", // Hausa (Nigeria)
- "he", // Hebrew
- "he_IL", // Hebrew (Israel)
- "hi", // Hindi
- "hi_IN", // Hindi (India)
- "hne_IN", // Chhattisgarhi (India)
- "hr", // Croatian
- "hr_HR", // Croatian (Croatia)
- "hsb_DE", // Upper Sorbian (Germany)
- "ht_HT", // Haitian (Haiti)
- "hu", // Hungarian
- "hu_HU", // Hungarian (Hungary)
- "hus_MX", // Huastec (Mexico)
- "hy_AM", // Armenian (Armenia)
- "ia_FR", // Interlingua (France)
- "id", // Indonesian
- "id_ID", // Indonesian (Indonesia)
- "ig_NG", // Igbo (Nigeria)
- "ik_CA", // Inupiaq (Canada)
- "is", // Icelandic
- "is_IS", // Icelandic (Iceland)
- "it", // Italian
- "it_CH", // Italian (Switzerland)
- "it_IT", // Italian (Italy)
- "iu_CA", // Inuktitut (Canada)
- "ja", // Japanese
- "ja_JP", // Japanese (Japan)
- "kab_DZ", // Kabyle (Algeria)
- "ka", // Georgian
- "ka_GE", // Georgian (Georgia)
- "kk_KZ", // Kazakh (Kazakhstan)
- "kl_GL", // Kalaallisut (Greenland)
- "km", // Central Khmer
- "km_KH", // Central Khmer (Cambodia)
- "kn_IN", // Kannada (India)
- "kok_IN", // Konkani (India)
- "ko", // Korean
- "ko_KR", // Korean (South Korea)
- "ks_IN", // Kashmiri (India)
- "ku", // Kurdish
- "ku_TR", // Kurdish (Turkey)
- "kw_GB", // Cornish (United Kingdom)
- "ky_KG", // Kirghiz (Kyrgyzstan)
- "lb_LU", // Luxembourgish (Luxembourg)
- "lg_UG", // Ganda (Uganda)
- "li_BE", // Limburgan (Belgium)
- "li_NL", // Limburgan (Netherlands)
- "lij_IT", // Ligurian (Italy)
- "ln_CD", // Lingala (Congo)
- "lo_LA", // Lao (Laos)
- "lt", // Lithuanian
- "lt_LT", // Lithuanian (Lithuania)
- "lv", // Latvian
- "lv_LV", // Latvian (Latvia)
- "lzh_TW", // Literary Chinese (Taiwan)
- "mag_IN", // Magahi (India)
- "mai_IN", // Maithili (India)
- "mg_MG", // Malagasy (Madagascar)
- "mh_MH", // Marshallese (Marshall Islands)
- "mhr_RU", // Eastern Mari (Russia)
- "mi", // Māori
- "mi_NZ", // Māori (New Zealand)
- "miq_NI", // Mískito (Nicaragua)
- "mk", // Macedonian
- "mk_MK", // Macedonian (Macedonia)
- "ml", // Malayalam
- "ml_IN", // Malayalam (India)
- "mni_IN", // Manipuri (India)
- "mn_MN", // Mongolian (Mongolia)
- "mr", // Marathi
- "mr_IN", // Marathi (India)
- "ms", // Malay
- "ms_MY", // Malay (Malaysia)
- "mt", // Maltese
- "mt_MT", // Maltese (Malta)
- "my_MM", // Burmese (Myanmar)
- "myv_RU", // Erzya (Russia)
- "nah_MX", // Nahuatl languages (Mexico)
- "nan_TW", // Min Nan Chinese (Taiwan)
- "nb", // Norwegian Bokmål
- "nb_NO", // Norwegian Bokmål (Norway)
- "nds_DE", // Low German (Germany)
- "nds_NL", // Low German (Netherlands)
- "ne_NP", // Nepali (Nepal)
- "nhn_MX", // Central Nahuatl (Mexico)
- "niu_NU", // Niuean (Niue)
- "niu_NZ", // Niuean (New Zealand)
- "nl", // Dutch
- "nl_AW", // Dutch (Aruba)
- "nl_BE", // Dutch (Belgium)
- "nl_NL", // Dutch (Netherlands)
- "nn", // Norwegian Nynorsk
- "nn_NO", // Norwegian Nynorsk (Norway)
- "nr_ZA", // South Ndebele (South Africa)
- "nso_ZA", // Pedi (South Africa)
- "oc_FR", // Occitan (France)
- "om", // Oromo
- "om_ET", // Oromo (Ethiopia)
- "om_KE", // Oromo (Kenya)
- "or", // Oriya
- "or_IN", // Oriya (India)
- "os_RU", // Ossetian (Russia)
- "pa_IN", // Panjabi (India)
- "pap", // Papiamento
- "pap_AN", // Papiamento (Netherlands Antilles)
- "pap_AW", // Papiamento (Aruba)
- "pap_CW", // Papiamento (Curaçao)
- "pa_PK", // Panjabi (Pakistan)
- "pl", // Polish
- "pl_PL", // Polish (Poland)
- "pr", // Pirate
- "ps_AF", // Pushto (Afghanistan)
- "pt", // Portuguese
- "pt_BR", // Portuguese (Brazil)
- "pt_PT", // Portuguese (Portugal)
- "quy_PE", // Ayacucho Quechua (Peru)
- "quz_PE", // Cusco Quechua (Peru)
- "raj_IN", // Rajasthani (India)
- "ro", // Romanian
- "ro_RO", // Romanian (Romania)
- "ru", // Russian
- "ru_RU", // Russian (Russia)
- "ru_UA", // Russian (Ukraine)
- "rw_RW", // Kinyarwanda (Rwanda)
- "sa_IN", // Sanskrit (India)
- "sat_IN", // Santali (India)
- "sc_IT", // Sardinian (Italy)
- "sco", // Scots
- "sd_IN", // Sindhi (India)
- "se_NO", // Northern Sami (Norway)
- "sgs_LT", // Samogitian (Lithuania)
- "shs_CA", // Shuswap (Canada)
- "sid_ET", // Sidamo (Ethiopia)
- "si", // Sinhala
- "si_LK", // Sinhala (Sri Lanka)
- "sk", // Slovak
- "sk_SK", // Slovak (Slovakia)
- "sl", // Slovenian
- "sl_SI", // Slovenian (Slovenia)
- "so", // Somali
- "so_DJ", // Somali (Djibouti)
- "so_ET", // Somali (Ethiopia)
- "so_KE", // Somali (Kenya)
- "so_SO", // Somali (Somalia)
- "son_ML", // Songhai languages (Mali)
- "sq", // Albanian
- "sq_AL", // Albanian (Albania)
- "sq_KV", // Albanian (Kosovo)
- "sq_MK", // Albanian (Macedonia)
- "sr", // Serbian
- "sr_Cyrl", // Serbian (Cyrillic)
- "sr_Latn", // Serbian (Latin)
- "sr_ME", // Serbian (Montenegro)
- "sr_RS", // Serbian (Serbia)
- "ss_ZA", // Swati (South Africa)
- "st_ZA", // Southern Sotho (South Africa)
- "sv", // Swedish
- "sv_FI", // Swedish (Finland)
- "sv_SE", // Swedish (Sweden)
- "sw_KE", // Swahili (Kenya)
- "sw_TZ", // Swahili (Tanzania)
- "szl_PL", // Silesian (Poland)
- "ta", // Tamil
- "ta_IN", // Tamil (India)
- "ta_LK", // Tamil (Sri Lanka)
- "tcy_IN", // Tulu (India)
- "te", // Telugu
- "te_IN", // Telugu (India)
- "tg_TJ", // Tajik (Tajikistan)
- "the_NP", // Chitwania Tharu (Nepal)
- "th", // Thai
- "th_TH", // Thai (Thailand)
- "ti", // Tigrinya
- "ti_ER", // Tigrinya (Eritrea)
- "ti_ET", // Tigrinya (Ethiopia)
- "tig_ER", // Tigre (Eritrea)
- "tk_TM", // Turkmen (Turkmenistan)
- "tl_PH", // Tagalog (Philippines)
- "tn_ZA", // Tswana (South Africa)
- "tr", // Turkish
- "tr_CY", // Turkish (Cyprus)
- "tr_TR", // Turkish (Turkey)
- "ts_ZA", // Tsonga (South Africa)
- "tt", // Tatar
- "tt_RU", // Tatar (Russia)
- "tzm", // Central Atlas Tamazight
- "tzm_MA", // Central Atlas Tamazight (Marrocos)
- "ug_CN", // Uighur (China)
- "uk", // Ukrainian
- "uk_UA", // Ukrainian (Ukraine)
- "unm_US", // Unami (United States)
- "ur", // Urdu
- "ur_IN", // Urdu (India)
- "ur_PK", // Urdu (Pakistan)
- "uz", // Uzbek
- "uz_UZ", // Uzbek (Uzbekistan)
- "ve_ZA", // Venda (South Africa)
- "vi", // Vietnamese
- "vi_VN", // Vietnamese (Vietnam)
- "wa_BE", // Walloon (Belgium)
- "wae_CH", // Walser (Switzerland)
- "wal_ET", // Wolaytta (Ethiopia)
- "wo_SN", // Wolof (Senegal)
- "xh_ZA", // Xhosa (South Africa)
- "yi_US", // Yiddish (United States)
- "yo_NG", // Yoruba (Nigeria)
- "yue_HK", // Yue Chinese (Hong Kong)
- "zh", // Chinese
- "zh_CN", // Chinese (China)
- "zh_HK", // Chinese (Hong Kong)
- "zh_SG", // Chinese (Singapore)
- "zh_TW", // Chinese (Taiwan)
- "zu_ZA", // Zulu (South Africa)
- nullptr
-};
-
-static const char *locale_names[] = {
- "Afar",
- "Afar (Djibouti)",
- "Afar (Eritrea)",
- "Afar (Ethiopia)",
- "Afrikaans",
- "Afrikaans (South Africa)",
- "Aguaruna (Peru)",
- "Akan (Ghana)",
- "Amharic (Ethiopia)",
- "Aragonese (Spain)",
- "Angika (India)",
- "Arabic",
- "Arabic (United Arab Emirates)",
- "Arabic (Bahrain)",
- "Arabic (Algeria)",
- "Arabic (Egypt)",
- "Arabic (India)",
- "Arabic (Iraq)",
- "Arabic (Jordan)",
- "Arabic (Kuwait)",
- "Arabic (Lebanon)",
- "Arabic (Libya)",
- "Arabic (Morocco)",
- "Arabic (Oman)",
- "Arabic (Qatar)",
- "Arabic (Saudi Arabia)",
- "Arabic (Sudan)",
- "Arabic (South Soudan)",
- "Arabic (Syria)",
- "Arabic (Tunisia)",
- "Arabic (Yemen)",
- "Assamese (India)",
- "Asturian (Spain)",
- "Southern Aymara (Peru)",
- "Aymara (Peru)",
- "Azerbaijani",
- "Azerbaijani (Azerbaijan)",
- "Belarusian",
- "Belarusian (Belarus)",
- "Bemba (Zambia)",
- "Berber languages (Algeria)",
- "Berber languages (Morocco)",
- "Bulgarian",
- "Bulgarian (Bulgaria)",
- "Bhili (India)",
- "Bhojpuri (India)",
- "Bislama (Tuvalu)",
- "Bengali",
- "Bengali (Bangladesh)",
- "Bengali (India)",
- "Tibetan",
- "Tibetan (China)",
- "Tibetan (India)",
- "Breton",
- "Breton (France)",
- "Bodo (India)",
- "Bosnian (Bosnia and Herzegovina)",
- "Bilin (Eritrea)",
- "Catalan",
- "Catalan (Andorra)",
- "Catalan (Spain)",
- "Catalan (France)",
- "Catalan (Italy)",
- "Chechen (Russia)",
- "Cherokee (United States)",
- "Mandarin Chinese (Taiwan)",
- "Crimean Tatar (Ukraine)",
- "Kashubian (Poland)",
- "Czech",
- "Czech (Czech Republic)",
- "Chuvash (Russia)",
- "Welsh (United Kingdom)",
- "Danish",
- "Danish (Denmark)",
- "German",
- "German (Austria)",
- "German (Belgium)",
- "German (Switzerland)",
- "German (Germany)",
- "German (Italy)",
- "German (Luxembourg)",
- "Dogri (India)",
- "Dhivehi (Maldives)",
- "Dzongkha (Bhutan)",
- "Greek",
- "Greek (Cyprus)",
- "Greek (Greece)",
- "English",
- "English (Antigua and Barbuda)",
- "English (Australia)",
- "English (Botswana)",
- "English (Canada)",
- "English (Denmark)",
- "English (United Kingdom)",
- "English (Hong Kong)",
- "English (Ireland)",
- "English (Israel)",
- "English (India)",
- "English (Nigeria)",
- "English (New Zealand)",
- "English (Philippines)",
- "English (Singapore)",
- "English (United States)",
- "English (South Africa)",
- "English (Zambia)",
- "English (Zimbabwe)",
- "Esperanto",
- "Spanish",
- "Spanish (Argentina)",
- "Spanish (Bolivia)",
- "Spanish (Chile)",
- "Spanish (Colombia)",
- "Spanish (Costa Rica)",
- "Spanish (Cuba)",
- "Spanish (Dominican Republic)",
- "Spanish (Ecuador)",
- "Spanish (Spain)",
- "Spanish (Guatemala)",
- "Spanish (Honduras)",
- "Spanish (Mexico)",
- "Spanish (Nicaragua)",
- "Spanish (Panama)",
- "Spanish (Peru)",
- "Spanish (Puerto Rico)",
- "Spanish (Paraguay)",
- "Spanish (El Salvador)",
- "Spanish (United States)",
- "Spanish (Uruguay)",
- "Spanish (Venezuela)",
- "Estonian",
- "Estonian (Estonia)",
- "Basque",
- "Basque (Spain)",
- "Persian",
- "Persian (Iran)",
- "Fulah (Senegal)",
- "Finnish",
- "Finnish (Finland)",
- "Filipino",
- "Filipino (Philippines)",
- "Faroese (Faroe Islands)",
- "French",
- "French (Belgium)",
- "French (Canada)",
- "French (Switzerland)",
- "French (France)",
- "French (Luxembourg)",
- "Friulian (Italy)",
- "Western Frisian (Germany)",
- "Western Frisian (Netherlands)",
- "Irish",
- "Irish (Ireland)",
- "Scottish Gaelic (United Kingdom)",
- "Geez (Eritrea)",
- "Geez (Ethiopia)",
- "Galician",
- "Galician (Spain)",
- "Gujarati (India)",
- "Manx (United Kingdom)",
- "Hakka Chinese (Taiwan)",
- "Hausa (Nigeria)",
- "Hebrew",
- "Hebrew (Israel)",
- "Hindi",
- "Hindi (India)",
- "Chhattisgarhi (India)",
- "Croatian",
- "Croatian (Croatia)",
- "Upper Sorbian (Germany)",
- "Haitian (Haiti)",
- "Hungarian",
- "Hungarian (Hungary)",
- "Huastec (Mexico)",
- "Armenian (Armenia)",
- "Interlingua (France)",
- "Indonesian",
- "Indonesian (Indonesia)",
- "Igbo (Nigeria)",
- "Inupiaq (Canada)",
- "Icelandic",
- "Icelandic (Iceland)",
- "Italian",
- "Italian (Switzerland)",
- "Italian (Italy)",
- "Inuktitut (Canada)",
- "Japanese",
- "Japanese (Japan)",
- "Kabyle (Algeria)",
- "Georgian",
- "Georgian (Georgia)",
- "Kazakh (Kazakhstan)",
- "Kalaallisut (Greenland)",
- "Central Khmer",
- "Central Khmer (Cambodia)",
- "Kannada (India)",
- "Konkani (India)",
- "Korean",
- "Korean (South Korea)",
- "Kashmiri (India)",
- "Kurdish",
- "Kurdish (Turkey)",
- "Cornish (United Kingdom)",
- "Kirghiz (Kyrgyzstan)",
- "Luxembourgish (Luxembourg)",
- "Ganda (Uganda)",
- "Limburgan (Belgium)",
- "Limburgan (Netherlands)",
- "Ligurian (Italy)",
- "Lingala (Congo)",
- "Lao (Laos)",
- "Lithuanian",
- "Lithuanian (Lithuania)",
- "Latvian",
- "Latvian (Latvia)",
- "Literary Chinese (Taiwan)",
- "Magahi (India)",
- "Maithili (India)",
- "Malagasy (Madagascar)",
- "Marshallese (Marshall Islands)",
- "Eastern Mari (Russia)",
- "Māori",
- "Māori (New Zealand)",
- "Mískito (Nicaragua)",
- "Macedonian",
- "Macedonian (Macedonia)",
- "Malayalam",
- "Malayalam (India)",
- "Manipuri (India)",
- "Mongolian (Mongolia)",
- "Marathi",
- "Marathi (India)",
- "Malay",
- "Malay (Malaysia)",
- "Maltese",
- "Maltese (Malta)",
- "Burmese (Myanmar)",
- "Erzya (Russia)",
- "Nahuatl languages (Mexico)",
- "Min Nan Chinese (Taiwan)",
- "Norwegian Bokmål",
- "Norwegian Bokmål (Norway)",
- "Low German (Germany)",
- "Low German (Netherlands)",
- "Nepali (Nepal)",
- "Central Nahuatl (Mexico)",
- "Niuean (Niue)",
- "Niuean (New Zealand)",
- "Dutch",
- "Dutch (Aruba)",
- "Dutch (Belgium)",
- "Dutch (Netherlands)",
- "Norwegian Nynorsk",
- "Norwegian Nynorsk (Norway)",
- "South Ndebele (South Africa)",
- "Pedi (South Africa)",
- "Occitan (France)",
- "Oromo",
- "Oromo (Ethiopia)",
- "Oromo (Kenya)",
- "Oriya",
- "Oriya (India)",
- "Ossetian (Russia)",
- "Panjabi (India)",
- "Papiamento",
- "Papiamento (Netherlands Antilles)",
- "Papiamento (Aruba)",
- "Papiamento (Curaçao)",
- "Panjabi (Pakistan)",
- "Polish",
- "Polish (Poland)",
- "Pirate",
- "Pushto (Afghanistan)",
- "Portuguese",
- "Portuguese (Brazil)",
- "Portuguese (Portugal)",
- "Ayacucho Quechua (Peru)",
- "Cusco Quechua (Peru)",
- "Rajasthani (India)",
- "Romanian",
- "Romanian (Romania)",
- "Russian",
- "Russian (Russia)",
- "Russian (Ukraine)",
- "Kinyarwanda (Rwanda)",
- "Sanskrit (India)",
- "Santali (India)",
- "Sardinian (Italy)",
- "Scots (Scotland)",
- "Sindhi (India)",
- "Northern Sami (Norway)",
- "Samogitian (Lithuania)",
- "Shuswap (Canada)",
- "Sidamo (Ethiopia)",
- "Sinhala",
- "Sinhala (Sri Lanka)",
- "Slovak",
- "Slovak (Slovakia)",
- "Slovenian",
- "Slovenian (Slovenia)",
- "Somali",
- "Somali (Djibouti)",
- "Somali (Ethiopia)",
- "Somali (Kenya)",
- "Somali (Somalia)",
- "Songhai languages (Mali)",
- "Albanian",
- "Albanian (Albania)",
- "Albanian (Kosovo)",
- "Albanian (Macedonia)",
- "Serbian",
- "Serbian (Cyrillic)",
- "Serbian (Latin)",
- "Serbian (Montenegro)",
- "Serbian (Serbia)",
- "Swati (South Africa)",
- "Southern Sotho (South Africa)",
- "Swedish",
- "Swedish (Finland)",
- "Swedish (Sweden)",
- "Swahili (Kenya)",
- "Swahili (Tanzania)",
- "Silesian (Poland)",
- "Tamil",
- "Tamil (India)",
- "Tamil (Sri Lanka)",
- "Tulu (India)",
- "Telugu",
- "Telugu (India)",
- "Tajik (Tajikistan)",
- "Chitwania Tharu (Nepal)",
- "Thai",
- "Thai (Thailand)",
- "Tigrinya",
- "Tigrinya (Eritrea)",
- "Tigrinya (Ethiopia)",
- "Tigre (Eritrea)",
- "Turkmen (Turkmenistan)",
- "Tagalog (Philippines)",
- "Tswana (South Africa)",
- "Turkish",
- "Turkish (Cyprus)",
- "Turkish (Turkey)",
- "Tsonga (South Africa)",
- "Tatar",
- "Tatar (Russia)",
- "Central Atlas Tamazight",
- "Central Atlas Tamazight (Marrocos)",
- "Uighur (China)",
- "Ukrainian",
- "Ukrainian (Ukraine)",
- "Unami (United States)",
- "Urdu",
- "Urdu (India)",
- "Urdu (Pakistan)",
- "Uzbek",
- "Uzbek (Uzbekistan)",
- "Venda (South Africa)",
- "Vietnamese",
- "Vietnamese (Vietnam)",
- "Walloon (Belgium)",
- "Walser (Switzerland)",
- "Wolaytta (Ethiopia)",
- "Wolof (Senegal)",
- "Xhosa (South Africa)",
- "Yiddish (United States)",
- "Yoruba (Nigeria)",
- "Yue Chinese (Hong Kong)",
- "Chinese",
- "Chinese (China)",
- "Chinese (Hong Kong)",
- "Chinese (Singapore)",
- "Chinese (Taiwan)",
- "Zulu (South Africa)",
- nullptr
-};
-
-// Windows has some weird locale identifiers which do not honor the ISO 639-1
-// standardized nomenclature. Whenever those don't conflict with existing ISO
-// identifiers, we override them.
-//
-// Reference:
-// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx
-
-static const char *locale_renames[][2] = {
- { "in", "id" }, // Indonesian
- { "iw", "he" }, // Hebrew
- { "no", "nb" }, // Norwegian Bokmål
- { "C", "en" }, // "C" is the simple/default/untranslated Computer locale.
- // ASCII-only, English, no currency symbols. Godot treats this as "en".
- // See https://unix.stackexchange.com/a/87763/164141 "The C locale is"...
- { nullptr, nullptr }
-};
-
-///////////////////////////////////////////////
-
Dictionary Translation::_get_messages() const {
Dictionary d;
for (const KeyValue<StringName, StringName> &E : translation_map) {
@@ -849,17 +68,7 @@ void Translation::_set_messages(const Dictionary &p_messages) {
}
void Translation::set_locale(const String &p_locale) {
- String univ_locale = TranslationServer::standardize_locale(p_locale);
-
- if (!TranslationServer::is_locale_valid(univ_locale)) {
- String trimmed_locale = TranslationServer::get_language_code(univ_locale);
-
- ERR_FAIL_COND_MSG(!TranslationServer::is_locale_valid(trimmed_locale), "Invalid locale: " + trimmed_locale + ".");
-
- locale = trimmed_locale;
- } else {
- locale = univ_locale;
- }
+ locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
if (OS::get_singleton()->get_main_loop() && TranslationServer::get_singleton()->get_loaded_locales().has(this)) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
@@ -1004,124 +213,314 @@ static _character_accent_pair _character_to_accented[] = {
{ 'z', U"ź" },
};
-bool TranslationServer::is_locale_valid(const String &p_locale) {
- const char **ptr = locale_list;
+static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
+ return (c >= 'A' && c <= 'Z');
+}
- while (*ptr) {
- if (*ptr == p_locale) {
- return true;
+static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
+
+Map<String, String> TranslationServer::language_map;
+Map<String, String> TranslationServer::script_map;
+Map<String, String> TranslationServer::locale_rename_map;
+Map<String, String> TranslationServer::country_name_map;
+Map<String, String> TranslationServer::variant_map;
+Map<String, String> TranslationServer::country_rename_map;
+
+void TranslationServer::init_locale_info() {
+ // Init locale info.
+ language_map.clear();
+ int idx = 0;
+ while (language_list[idx][0] != nullptr) {
+ language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
+ idx++;
+ }
+
+ // Init locale-script map.
+ locale_script_info.clear();
+ idx = 0;
+ while (locale_scripts[idx][0] != nullptr) {
+ LocaleScriptInfo info;
+ info.name = locale_scripts[idx][0];
+ info.script = locale_scripts[idx][1];
+ info.default_country = locale_scripts[idx][2];
+ Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
+ for (int i = 0; i < supported_countries.size(); i++) {
+ info.supported_countries.insert(supported_countries[i]);
}
- ptr++;
+ locale_script_info.push_back(info);
+ idx++;
}
- return false;
-}
+ // Init supported script list.
+ script_map.clear();
+ idx = 0;
+ while (script_list[idx][0] != nullptr) {
+ script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
+ idx++;
+ }
-String TranslationServer::standardize_locale(const String &p_locale) {
- // Replaces '-' with '_' for macOS Sierra-style locales
- String univ_locale = p_locale.replace("-", "_");
+ // Init regional variant map.
+ variant_map.clear();
+ idx = 0;
+ while (locale_variants[idx][0] != nullptr) {
+ variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
+ idx++;
+ }
- // Handles known non-ISO locale names used e.g. on Windows
- int idx = 0;
+ // Init locale renames.
+ locale_rename_map.clear();
+ idx = 0;
while (locale_renames[idx][0] != nullptr) {
- if (locale_renames[idx][0] == univ_locale) {
- univ_locale = locale_renames[idx][1];
- break;
+ if (!String(locale_renames[idx][1]).is_empty()) {
+ locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
}
idx++;
}
- return univ_locale;
+ // Init country names.
+ country_name_map.clear();
+ idx = 0;
+ while (country_names[idx][0] != nullptr) {
+ country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
+ idx++;
+ }
+
+ // Init country renames.
+ country_rename_map.clear();
+ idx = 0;
+ while (country_renames[idx][0] != nullptr) {
+ if (!String(country_renames[idx][1]).is_empty()) {
+ country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
+ }
+ idx++;
+ }
}
-String TranslationServer::get_language_code(const String &p_locale) {
- ERR_FAIL_COND_V_MSG(p_locale.length() < 2, p_locale, "Invalid locale '" + p_locale + "'.");
- // Most language codes are two letters, but some are three,
- // so we have to look for a regional code separator ('_' or '-')
- // to extract the left part.
- // For example we get 'nah_MX' as input and should return 'nah'.
- int split = p_locale.find("_");
- if (split == -1) {
- split = p_locale.find("-");
+String TranslationServer::standardize_locale(const String &p_locale) const {
+ // Replaces '-' with '_' for macOS style locales.
+ String univ_locale = p_locale.replace("-", "_");
+
+ // Extract locale elements.
+ String lang, script, country, variant;
+ Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
+ lang = locale_elements[0];
+ if (locale_elements.size() >= 2) {
+ if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ script = locale_elements[1];
+ }
+ if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ country = locale_elements[1];
+ }
}
- if (split == -1) { // No separator, so the locale is already only a language code.
- return p_locale;
+ if (locale_elements.size() >= 3) {
+ if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ country = locale_elements[2];
+ } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang) {
+ variant = locale_elements[2].to_lower();
+ }
+ }
+ if (locale_elements.size() >= 4) {
+ if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang) {
+ variant = locale_elements[3].to_lower();
+ }
}
- return p_locale.left(split);
-}
-void TranslationServer::set_locale(const String &p_locale) {
- String univ_locale = standardize_locale(p_locale);
+ // Try extract script and variant from the extra part.
+ Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
+ for (int i = 0; i < script_extra.size(); i++) {
+ if (script_extra[i].to_lower() == "cyrillic") {
+ script = "Cyrl";
+ break;
+ } else if (script_extra[i].to_lower() == "latin") {
+ script = "Latn";
+ break;
+ } else if (script_extra[i].to_lower() == "devanagari") {
+ script = "Deva";
+ break;
+ } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang) {
+ variant = script_extra[i].to_lower();
+ }
+ }
- if (!is_locale_valid(univ_locale)) {
- String trimmed_locale = get_language_code(univ_locale);
- print_verbose(vformat("Unsupported locale '%s', falling back to '%s'.", p_locale, trimmed_locale));
+ // Handles known non-ISO language names used e.g. on Windows.
+ if (locale_rename_map.has(lang)) {
+ lang = locale_rename_map[lang];
+ }
- if (!is_locale_valid(trimmed_locale)) {
- ERR_PRINT(vformat("Unsupported locale '%s', falling back to 'en'.", trimmed_locale));
- locale = "en";
- } else {
- locale = trimmed_locale;
+ // Handle country renames.
+ if (country_rename_map.has(country)) {
+ country = country_rename_map[country];
+ }
+
+ // Remove unsupported script codes.
+ if (!script_map.has(script)) {
+ script = "";
+ }
+
+ // Add script code base on language and country codes for some ambiguous cases.
+ if (script.is_empty()) {
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang) {
+ if (country.is_empty() || info.supported_countries.has(country)) {
+ script = info.script;
+ break;
+ }
+ }
+ }
+ }
+ if (!script.is_empty() && country.is_empty()) {
+ // Add conntry code based on script for some ambiguous cases.
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang && info.script == script) {
+ country = info.default_country;
+ break;
+ }
+ }
+ }
+
+ // Combine results.
+ String locale = lang;
+ if (!script.is_empty()) {
+ locale = locale + "_" + script;
+ }
+ if (!country.is_empty()) {
+ locale = locale + "_" + country;
+ }
+ if (!variant.is_empty()) {
+ locale = locale + "_" + variant;
+ }
+ return locale;
+}
+
+int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ String locale_a = standardize_locale(p_locale_a);
+ String locale_b = standardize_locale(p_locale_b);
+
+ if (locale_a == locale_b) {
+ // Exact match.
+ return 10;
+ }
+
+ Vector<String> locale_a_elements = locale_a.split("_");
+ Vector<String> locale_b_elements = locale_b.split("_");
+ if (locale_a_elements[0] == locale_b_elements[0]) {
+ // Matching language, both locales have extra parts.
+ // Return number of matching elements.
+ int matching_elements = 1;
+ for (int i = 1; i < locale_a_elements.size(); i++) {
+ for (int j = 1; j < locale_b_elements.size(); j++) {
+ if (locale_a_elements[i] == locale_b_elements[j]) {
+ matching_elements++;
+ }
+ }
}
+ return matching_elements;
} else {
- locale = univ_locale;
+ // No match.
+ return 0;
}
+}
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+String TranslationServer::get_locale_name(const String &p_locale) const {
+ String locale = standardize_locale(p_locale);
+
+ String lang, script, country;
+ Vector<String> locale_elements = locale.split("_");
+ lang = locale_elements[0];
+ if (locale_elements.size() >= 2) {
+ if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ script = locale_elements[1];
+ }
+ if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ country = locale_elements[1];
+ }
+ }
+ if (locale_elements.size() >= 3) {
+ if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ country = locale_elements[2];
+ }
}
- ResourceLoader::reload_translation_remaps();
+ String name = language_map[lang];
+ if (!script.is_empty()) {
+ name = name + " (" + script_map[script] + ")";
+ }
+ if (!country.is_empty()) {
+ name = name + ", " + country_name_map[country];
+ }
+ return name;
}
-String TranslationServer::get_locale() const {
- return locale;
+Vector<String> TranslationServer::get_all_languages() const {
+ Vector<String> languages;
+
+ for (const Map<String, String>::Element *E = language_map.front(); E; E = E->next()) {
+ languages.push_back(E->key());
+ }
+
+ return languages;
}
-String TranslationServer::get_locale_name(const String &p_locale) const {
- if (!locale_name_map.has(p_locale)) {
- return String();
+String TranslationServer::get_language_name(const String &p_language) const {
+ return language_map[p_language];
+}
+
+Vector<String> TranslationServer::get_all_scripts() const {
+ Vector<String> scripts;
+
+ for (const Map<String, String>::Element *E = script_map.front(); E; E = E->next()) {
+ scripts.push_back(E->key());
}
- return locale_name_map[p_locale];
+
+ return scripts;
}
-Array TranslationServer::get_loaded_locales() const {
- Array locales;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
- const Ref<Translation> &t = E->get();
- ERR_FAIL_COND_V(t.is_null(), Array());
- String l = t->get_locale();
+String TranslationServer::get_script_name(const String &p_script) const {
+ return script_map[p_script];
+}
- if (!locales.has(l)) {
- locales.push_back(l);
- }
+Vector<String> TranslationServer::get_all_countries() const {
+ Vector<String> countries;
+
+ for (const Map<String, String>::Element *E = country_name_map.front(); E; E = E->next()) {
+ countries.push_back(E->key());
}
- locales.sort();
- return locales;
+ return countries;
}
-Vector<String> TranslationServer::get_all_locales() {
- Vector<String> locales;
+String TranslationServer::get_country_name(const String &p_country) const {
+ return country_name_map[p_country];
+}
- const char **ptr = locale_list;
+void TranslationServer::set_locale(const String &p_locale) {
+ locale = standardize_locale(p_locale);
- while (*ptr) {
- locales.push_back(*ptr);
- ptr++;
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
- return locales;
+ ResourceLoader::reload_translation_remaps();
}
-Vector<String> TranslationServer::get_all_locale_names() {
- Vector<String> locales;
+String TranslationServer::get_locale() const {
+ return locale;
+}
- const char **ptr = locale_names;
+Array TranslationServer::get_loaded_locales() const {
+ Array locales;
+ for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ const Ref<Translation> &t = E->get();
+ ERR_FAIL_COND_V(t.is_null(), Array());
+ String l = t->get_locale();
- while (*ptr) {
- locales.push_back(String::utf8(*ptr));
- ptr++;
+ locales.push_back(l);
}
return locales;
@@ -1137,23 +536,20 @@ void TranslationServer::remove_translation(const Ref<Translation> &p_translation
Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
Ref<Translation> res;
- String lang = get_language_code(p_locale);
- bool near_match_found = false;
+ int best_score = 0;
for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), nullptr);
String l = t->get_locale();
- // Exact match.
- if (l == p_locale) {
- return t;
- }
-
- // If near match found, keep that match, but keep looking to try to look for perfect match.
- if (get_language_code(l) == lang && !near_match_found) {
+ int score = compare_locales(p_locale, l);
+ if (score >= best_score) {
res = t;
- near_match_found = true;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
}
}
return res;
@@ -1212,52 +608,27 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
}
StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
- // Locale can be of the form 'll_CC', i.e. language code and regional code,
- // e.g. 'en_US', 'en_GB', etc. It might also be simply 'll', e.g. 'en'.
- // To find the relevant translation, we look for those with locale starting
- // with the language code, and then if any is an exact match for the long
- // form. If not found, we fall back to a near match (another locale with
- // same language code).
-
- // Note: ResourceLoader::_path_remap reproduces this locale near matching
- // logic, so be sure to propagate changes there when changing things here.
-
StringName res;
- String lang = get_language_code(p_locale);
- bool near_match = false;
+ int best_score = 0;
for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), p_message);
String l = t->get_locale();
- bool exact_match = (l == p_locale);
- if (!exact_match) {
- if (near_match) {
- continue; // Only near-match once, but keep looking for exact matches.
+ int score = compare_locales(p_locale, l);
+ if (score >= best_score) {
+ StringName r;
+ if (!plural) {
+ res = t->get_message(p_message, p_context);
+ } else {
+ res = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
}
- if (get_language_code(l) != lang) {
- continue; // Language code does not match.
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
}
}
-
- StringName r;
- if (!plural) {
- r = t->get_message(p_message, p_context);
- } else {
- r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
- }
-
- if (!r) {
- continue;
- }
- res = r;
-
- if (exact_match) {
- break;
- } else {
- near_match = true;
- }
}
return res;
@@ -1308,18 +679,7 @@ void TranslationServer::setup() {
pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
#ifdef TOOLS_ENABLED
- {
- String options = "";
- int idx = 0;
- while (locale_list[idx]) {
- if (idx > 0) {
- options += ",";
- }
- options += locale_list[idx];
- idx++;
- }
- ProjectSettings::get_singleton()->set_custom_property_info("internationalization/locale/fallback", PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_ENUM, options));
- }
+ ProjectSettings::get_singleton()->set_custom_property_info("internationalization/locale/fallback", PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
#endif
}
@@ -1574,6 +934,18 @@ void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+ ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
+ ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
+
+ ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
+ ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
+ ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
+ ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
+
ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
@@ -1606,8 +978,5 @@ void TranslationServer::load_translations() {
TranslationServer::TranslationServer() {
singleton = this;
-
- for (int i = 0; locale_list[i]; ++i) {
- locale_name_map.insert(locale_list[i], String::utf8(locale_names[i]));
- }
+ init_locale_info();
}
diff --git a/core/string/translation.h b/core/string/translation.h
index e95d15e2ab..947ca4c6d8 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -78,8 +78,6 @@ class TranslationServer : public Object {
Ref<Translation> tool_translation;
Ref<Translation> doc_translation;
- Map<String, String> locale_name_map;
-
bool enabled = true;
bool pseudolocalization_enabled = false;
@@ -109,6 +107,23 @@ class TranslationServer : public Object {
static void _bind_methods();
+ struct LocaleScriptInfo {
+ String name;
+ String script;
+ String default_country;
+ Set<String> supported_countries;
+ };
+ static Vector<LocaleScriptInfo> locale_script_info;
+
+ static Map<String, String> language_map;
+ static Map<String, String> script_map;
+ static Map<String, String> locale_rename_map;
+ static Map<String, String> country_name_map;
+ static Map<String, String> country_rename_map;
+ static Map<String, String> variant_map;
+
+ void init_locale_info();
+
public:
_FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
@@ -119,6 +134,15 @@ public:
String get_locale() const;
Ref<Translation> get_translation_object(const String &p_locale);
+ Vector<String> get_all_languages() const;
+ String get_language_name(const String &p_language) const;
+
+ Vector<String> get_all_scripts() const;
+ String get_script_name(const String &p_script) const;
+
+ Vector<String> get_all_countries() const;
+ String get_country_name(const String &p_country) const;
+
String get_locale_name(const String &p_locale) const;
Array get_loaded_locales() const;
@@ -136,11 +160,9 @@ public:
void set_editor_pseudolocalization(bool p_enabled);
void reload_pseudolocalization();
- static Vector<String> get_all_locales();
- static Vector<String> get_all_locale_names();
- static bool is_locale_valid(const String &p_locale);
- static String standardize_locale(const String &p_locale);
- static String get_language_code(const String &p_locale);
+ String standardize_locale(const String &p_locale) const;
+
+ int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
String get_tool_locale();
void set_tool_translation(const Ref<Translation> &p_translation);
diff --git a/core/templates/vector.h b/core/templates/vector.h
index e53c502f67..bd4c6ade86 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -42,6 +42,7 @@
#include "core/templates/search_array.h"
#include "core/templates/sort_array.h"
+#include <climits>
#include <initializer_list>
template <class T>
@@ -144,25 +145,29 @@ public:
return ret;
}
- Vector<T> slice(int p_begin, int p_end) const {
+ Vector<T> slice(int p_begin, int p_end = INT_MAX) const {
Vector<T> result;
- if (p_end < 0) {
- p_end += size() + 1;
- }
+ const int s = size();
- ERR_FAIL_INDEX_V(p_begin, size(), result);
- ERR_FAIL_INDEX_V(p_end, size() + 1, result);
+ int begin = CLAMP(p_begin, -s, s);
+ if (begin < 0) {
+ begin += s;
+ }
+ int end = CLAMP(p_end, -s, s);
+ if (end < 0) {
+ end += s;
+ }
- ERR_FAIL_COND_V(p_begin > p_end, result);
+ ERR_FAIL_COND_V(begin > end, result);
- int result_size = p_end - p_begin;
+ int result_size = end - begin;
result.resize(result_size);
const T *const r = ptr();
T *const w = result.ptrw();
for (int i = 0; i < result_size; ++i) {
- w[i] = r[p_begin + i];
+ w[i] = r[begin + i];
}
return result;
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 8d20b1bc79..3d2f337442 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -370,20 +370,24 @@ Array Array::slice(int p_begin, int p_end, int p_step, bool p_deep) const {
ERR_FAIL_COND_V_MSG(p_step == 0, result, "Slice step cannot be zero.");
- if (p_end < 0) {
- p_end += size() + 1;
- }
+ const int s = size();
- ERR_FAIL_INDEX_V(p_begin, size(), result);
- ERR_FAIL_INDEX_V(p_end, size() + 1, result);
+ int begin = CLAMP(p_begin, -s, s);
+ if (begin < 0) {
+ begin += s;
+ }
+ int end = CLAMP(p_end, -s, s);
+ if (end < 0) {
+ end += s;
+ }
- ERR_FAIL_COND_V_MSG(p_step > 0 && p_begin > p_end, result, "Slice is positive, but bounds is decreasing");
- ERR_FAIL_COND_V_MSG(p_step < 0 && p_begin < p_end, result, "Slice is negative, but bounds is increasing");
+ ERR_FAIL_COND_V_MSG(p_step > 0 && begin > end, result, "Slice is positive, but bounds is decreasing.");
+ ERR_FAIL_COND_V_MSG(p_step < 0 && begin < end, result, "Slice is negative, but bounds is increasing.");
- int result_size = (p_end - p_begin) / p_step;
+ int result_size = (end - begin) / p_step;
result.resize(result_size);
- for (int src_idx = p_begin, dest_idx = 0; dest_idx < result_size; ++dest_idx) {
+ for (int src_idx = begin, dest_idx = 0; dest_idx < result_size; ++dest_idx) {
result[dest_idx] = p_deep ? get(src_idx).duplicate(true) : get(src_idx);
src_idx += p_step;
}
diff --git a/core/variant/array.h b/core/variant/array.h
index f48444bb39..72bed5932c 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -33,6 +33,8 @@
#include "core/typedefs.h"
+#include <climits>
+
class Variant;
class ArrayPrivate;
class Object;
@@ -102,7 +104,7 @@ public:
Array duplicate(bool p_deep = false) const;
Array recursive_duplicate(bool p_deep, int recursion_count) const;
- Array slice(int p_begin, int p_end, int p_step = 1, bool p_deep = false) const;
+ Array slice(int p_begin, int p_end = INT_MAX, int p_step = 1, bool p_deep = false) const;
Array filter(const Callable &p_callable) const;
Array map(const Callable &p_callable) const;
Variant reduce(const Callable &p_callable, const Variant &p_accum) const;
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index ecf5009fb6..8dd48a4c28 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1838,7 +1838,7 @@ static void _register_variant_builtin_methods() {
bind_method(Array, bsearch_custom, sarray("value", "func", "before"), varray(true));
bind_method(Array, reverse, sarray(), varray());
bind_method(Array, duplicate, sarray("deep"), varray(false));
- bind_method(Array, slice, sarray("begin", "end", "step", "deep"), varray(1, false));
+ bind_method(Array, slice, sarray("begin", "end", "step", "deep"), varray(INT_MAX, 1, false));
bind_method(Array, filter, sarray("method"), varray());
bind_method(Array, map, sarray("method"), varray());
bind_method(Array, reduce, sarray("method", "accum"), varray(Variant()));
@@ -1858,7 +1858,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedByteArray, resize, sarray("new_size"), varray());
bind_method(PackedByteArray, has, sarray("value"), varray());
bind_method(PackedByteArray, reverse, sarray(), varray());
- bind_method(PackedByteArray, slice, sarray("begin", "end"), varray());
+ bind_method(PackedByteArray, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedByteArray, sort, sarray(), varray());
bind_method(PackedByteArray, bsearch, sarray("value", "before"), varray(true));
bind_method(PackedByteArray, duplicate, sarray(), varray());
@@ -1919,7 +1919,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedInt32Array, resize, sarray("new_size"), varray());
bind_method(PackedInt32Array, has, sarray("value"), varray());
bind_method(PackedInt32Array, reverse, sarray(), varray());
- bind_method(PackedInt32Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedInt32Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedInt32Array, to_byte_array, sarray(), varray());
bind_method(PackedInt32Array, sort, sarray(), varray());
bind_method(PackedInt32Array, bsearch, sarray("value", "before"), varray(true));
@@ -1939,7 +1939,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedInt64Array, resize, sarray("new_size"), varray());
bind_method(PackedInt64Array, has, sarray("value"), varray());
bind_method(PackedInt64Array, reverse, sarray(), varray());
- bind_method(PackedInt64Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedInt64Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedInt64Array, to_byte_array, sarray(), varray());
bind_method(PackedInt64Array, sort, sarray(), varray());
bind_method(PackedInt64Array, bsearch, sarray("value", "before"), varray(true));
@@ -1959,7 +1959,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedFloat32Array, resize, sarray("new_size"), varray());
bind_method(PackedFloat32Array, has, sarray("value"), varray());
bind_method(PackedFloat32Array, reverse, sarray(), varray());
- bind_method(PackedFloat32Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedFloat32Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedFloat32Array, to_byte_array, sarray(), varray());
bind_method(PackedFloat32Array, sort, sarray(), varray());
bind_method(PackedFloat32Array, bsearch, sarray("value", "before"), varray(true));
@@ -1979,7 +1979,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedFloat64Array, resize, sarray("new_size"), varray());
bind_method(PackedFloat64Array, has, sarray("value"), varray());
bind_method(PackedFloat64Array, reverse, sarray(), varray());
- bind_method(PackedFloat64Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedFloat64Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedFloat64Array, to_byte_array, sarray(), varray());
bind_method(PackedFloat64Array, sort, sarray(), varray());
bind_method(PackedFloat64Array, bsearch, sarray("value", "before"), varray(true));
@@ -1999,7 +1999,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedStringArray, resize, sarray("new_size"), varray());
bind_method(PackedStringArray, has, sarray("value"), varray());
bind_method(PackedStringArray, reverse, sarray(), varray());
- bind_method(PackedStringArray, slice, sarray("begin", "end"), varray());
+ bind_method(PackedStringArray, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedStringArray, to_byte_array, sarray(), varray());
bind_method(PackedStringArray, sort, sarray(), varray());
bind_method(PackedStringArray, bsearch, sarray("value", "before"), varray(true));
@@ -2019,7 +2019,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedVector2Array, resize, sarray("new_size"), varray());
bind_method(PackedVector2Array, has, sarray("value"), varray());
bind_method(PackedVector2Array, reverse, sarray(), varray());
- bind_method(PackedVector2Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedVector2Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedVector2Array, to_byte_array, sarray(), varray());
bind_method(PackedVector2Array, sort, sarray(), varray());
bind_method(PackedVector2Array, bsearch, sarray("value", "before"), varray(true));
@@ -2039,7 +2039,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedVector3Array, resize, sarray("new_size"), varray());
bind_method(PackedVector3Array, has, sarray("value"), varray());
bind_method(PackedVector3Array, reverse, sarray(), varray());
- bind_method(PackedVector3Array, slice, sarray("begin", "end"), varray());
+ bind_method(PackedVector3Array, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedVector3Array, to_byte_array, sarray(), varray());
bind_method(PackedVector3Array, sort, sarray(), varray());
bind_method(PackedVector3Array, bsearch, sarray("value", "before"), varray(true));
@@ -2059,7 +2059,7 @@ static void _register_variant_builtin_methods() {
bind_method(PackedColorArray, resize, sarray("new_size"), varray());
bind_method(PackedColorArray, has, sarray("value"), varray());
bind_method(PackedColorArray, reverse, sarray(), varray());
- bind_method(PackedColorArray, slice, sarray("begin", "end"), varray());
+ bind_method(PackedColorArray, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(PackedColorArray, to_byte_array, sarray(), varray());
bind_method(PackedColorArray, sort, sarray(), varray());
bind_method(PackedColorArray, bsearch, sarray("value", "before"), varray(true));
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index 571181f665..57875bf50f 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -1649,12 +1649,13 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
dict.get_key_list(&keys);
keys.sort();
+ if (keys.is_empty()) { // Avoid unnecessary line break.
+ p_store_string_func(p_store_string_ud, "{}");
+ break;
+ }
+
p_store_string_func(p_store_string_ud, "{\n");
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- /*
- if (!_check_type(dict[E->get()]))
- continue;
- */
write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, recursion_count);
p_store_string_func(p_store_string_ud, ": ");
write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, recursion_count);
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index be7ac1164e..d8c9ca08e8 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2493,7 +2493,10 @@
</constant>
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="39" enum="PropertyHint">
</constant>
- <constant name="PROPERTY_HINT_MAX" value="41" enum="PropertyHint">
+ <constant name="PROPERTY_HINT_LOCALE_ID" value="41" enum="PropertyHint">
+ Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
+ </constant>
+ <constant name="PROPERTY_HINT_MAX" value="42" enum="PropertyHint">
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags">
</constant>
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 7cb6f71190..57f51c7ccf 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -445,13 +445,14 @@
<method name="slice" qualifiers="const">
<return type="Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<argument index="2" name="step" type="int" default="1" />
<argument index="3" name="deep" type="bool" default="false" />
<description>
Returns the slice of the [Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [Array].
- If [code]end[/code] is negative, it will be relative to the end of the array.
- If specified, [code]step[/code] is the relative index between source elements.
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
+ If specified, [code]step[/code] is the relative index between source elements. It can be negative, then [code]begin[/code] must be higher than [code]end[/code]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code]).
If [code]deep[/code] is true, each element will be copied by value rather than by reference.
</description>
</method>
diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml
index c986947dfb..fa980d9f75 100644
--- a/doc/classes/ArrayMesh.xml
+++ b/doc/classes/ArrayMesh.xml
@@ -62,8 +62,7 @@
<argument index="0" name="primitive" type="int" enum="Mesh.PrimitiveType" />
<argument index="1" name="arrays" type="Array" />
<argument index="2" name="blend_shapes" type="Array" default="[]" />
- <argument index="3" name="lods" type="Dictionary" default="{
-}" />
+ <argument index="3" name="lods" type="Dictionary" default="{}" />
<argument index="4" name="compress_flags" type="int" default="0" />
<description>
Creates a new surface.
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index 697b0443f1..4a9d79978c 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -442,7 +442,7 @@
<member name="auto_brace_completion_highlight_matching" type="bool" setter="set_highlight_matching_braces_enabled" getter="is_highlight_matching_braces_enabled" default="false">
Highlight mismatching brace pairs.
</member>
- <member name="auto_brace_completion_pairs" type="Dictionary" setter="set_auto_brace_completion_pairs" getter="get_auto_brace_completion_pairs" default="{&quot;\&quot;&quot;: &quot;\&quot;&quot;,&quot;&apos;&quot;: &quot;&apos;&quot;,&quot;(&quot;: &quot;)&quot;,&quot;[&quot;: &quot;]&quot;,&quot;{&quot;: &quot;}&quot;}">
+ <member name="auto_brace_completion_pairs" type="Dictionary" setter="set_auto_brace_completion_pairs" getter="get_auto_brace_completion_pairs" default="{ &quot;\&quot;&quot;: &quot;\&quot;&quot;, &quot;&apos;&quot;: &quot;&apos;&quot;, &quot;(&quot;: &quot;)&quot;, &quot;[&quot;: &quot;]&quot;, &quot;{&quot;: &quot;}&quot; }">
Sets the brace pairs to be autocompleted.
</member>
<member name="code_completion_enabled" type="bool" setter="set_code_completion_enabled" getter="is_code_completion_enabled" default="false">
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index 2090672acc..dab9d7b054 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -168,7 +168,8 @@
</member>
<member name="sdfgi_cascade0_distance" type="float" setter="set_sdfgi_cascade0_distance" getter="get_sdfgi_cascade0_distance" default="12.8">
</member>
- <member name="sdfgi_cascades" type="int" setter="set_sdfgi_cascades" getter="get_sdfgi_cascades" enum="Environment.SDFGICascades" default="1">
+ <member name="sdfgi_cascades" type="int" setter="set_sdfgi_cascades" getter="get_sdfgi_cascades" default="6">
+ The number of cascades to use for SDFGI (between 1 and 8). A higher number of cascades allows displaying SDFGI further away while preserving detail up close, at the cost of performance. When using SDFGI on small-scale levels, [member sdfgi_cascades] can often be decreased between [code]1[/code] and [code]4[/code] to improve performance.
</member>
<member name="sdfgi_enabled" type="bool" setter="set_sdfgi_enabled" getter="is_sdfgi_enabled" default="false">
If [code]true[/code], enables signed distance field global illumination for meshes that have their [member GeometryInstance3D.gi_mode] set to [constant GeometryInstance3D.GI_MODE_BAKED]. SDFGI is a real-time global illumination technique that works well with procedurally generated and user-built levels, including in situations where geometry is created during gameplay. The signed distance field is automatically generated around the camera as it moves. Dynamic lights are supported, but dynamic occluders and emissive surfaces are not.
@@ -371,12 +372,6 @@
<constant name="GLOW_BLEND_MODE_MIX" value="4" enum="GlowBlendMode">
Mixes the glow with the underlying color to avoid increasing brightness as much while still maintaining a glow effect.
</constant>
- <constant name="SDFGI_CASCADES_4" value="0" enum="SDFGICascades">
- </constant>
- <constant name="SDFGI_CASCADES_6" value="1" enum="SDFGICascades">
- </constant>
- <constant name="SDFGI_CASCADES_8" value="2" enum="SDFGICascades">
- </constant>
<constant name="SDFGI_Y_SCALE_DISABLED" value="0" enum="SDFGIYScale">
</constant>
<constant name="SDFGI_Y_SCALE_75_PERCENT" value="1" enum="SDFGIYScale">
diff --git a/doc/classes/FlowContainer.xml b/doc/classes/FlowContainer.xml
new file mode 100644
index 0000000000..482d879b09
--- /dev/null
+++ b/doc/classes/FlowContainer.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="FlowContainer" inherits="Container" version="4.0">
+ <brief_description>
+ Base class for flow containers.
+ </brief_description>
+ <description>
+ Arranges child [Control] nodes vertically or horizontally in a left-to-right or top-to-bottom flow.
+ A line is filled with [Control] nodes until no more fit on the same line, similar to text in an autowrapped label.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_line_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the current line count.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/HFlowContainer.xml b/doc/classes/HFlowContainer.xml
new file mode 100644
index 0000000000..8ee2da69b7
--- /dev/null
+++ b/doc/classes/HFlowContainer.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="HFlowContainer" inherits="FlowContainer" version="4.0">
+ <brief_description>
+ Horizontal flow container.
+ </brief_description>
+ <description>
+ Horizontal version of [FlowContainer].
+ </description>
+ <tutorials>
+ </tutorials>
+ <theme_items>
+ <theme_item name="hseparation" data_type="constant" type="int" default="4">
+ The horizontal separation of children nodes.
+ </theme_item>
+ <theme_item name="vseparation" data_type="constant" type="int" default="4">
+ The vertical separation of children nodes.
+ </theme_item>
+ </theme_items>
+</class>
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index e7bb1b9825..60d4b664d2 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -452,7 +452,7 @@
</method>
</methods>
<members>
- <member name="data" type="Dictionary" setter="_set_data" getter="_get_data" default="{&quot;data&quot;: PackedByteArray(),&quot;format&quot;: &quot;Lum8&quot;,&quot;height&quot;: 0,&quot;mipmaps&quot;: false,&quot;width&quot;: 0}">
+ <member name="data" type="Dictionary" setter="_set_data" getter="_get_data" default="{ &quot;data&quot;: PackedByteArray(), &quot;format&quot;: &quot;Lum8&quot;, &quot;height&quot;: 0, &quot;mipmaps&quot;: false, &quot;width&quot;: 0 }">
Holds all the image's color data in a given format. See [enum Format] constants.
</member>
</members>
diff --git a/doc/classes/ImporterMesh.xml b/doc/classes/ImporterMesh.xml
index ab344f908c..4c323156c9 100644
--- a/doc/classes/ImporterMesh.xml
+++ b/doc/classes/ImporterMesh.xml
@@ -23,8 +23,7 @@
<argument index="0" name="primitive" type="int" enum="Mesh.PrimitiveType" />
<argument index="1" name="arrays" type="Array" />
<argument index="2" name="blend_shapes" type="Array" default="[]" />
- <argument index="3" name="lods" type="Dictionary" default="{
-}" />
+ <argument index="3" name="lods" type="Dictionary" default="{}" />
<argument index="4" name="material" type="Material" default="null" />
<argument index="5" name="name" type="String" default="&quot;&quot;" />
<argument index="6" name="flags" type="int" default="0" />
@@ -178,7 +177,7 @@
</method>
</methods>
<members>
- <member name="_data" type="Dictionary" setter="_set_data" getter="_get_data" default="{&quot;surfaces&quot;: []}">
+ <member name="_data" type="Dictionary" setter="_set_data" getter="_get_data" default="{ &quot;surfaces&quot;: [] }">
</member>
</members>
</class>
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index 686854ffe8..3dc8307d44 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -369,10 +369,11 @@
<method name="slice" qualifiers="const">
<return type="PackedByteArray" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
Returns the slice of the [PackedByteArray], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedByteArray].
- If [code]end[/code]is negative, it will be relative to the end of the array.
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml
index d875549319..8aac7d1bf4 100644
--- a/doc/classes/PackedColorArray.xml
+++ b/doc/classes/PackedColorArray.xml
@@ -132,8 +132,11 @@
<method name="slice" qualifiers="const">
<return type="PackedColorArray" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedColorArray], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedColorArray].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml
index 6c77c4bee2..0e66dd7967 100644
--- a/doc/classes/PackedFloat32Array.xml
+++ b/doc/classes/PackedFloat32Array.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedFloat32Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedFloat32Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedFloat32Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml
index a8282c099a..eaad4fec54 100644
--- a/doc/classes/PackedFloat64Array.xml
+++ b/doc/classes/PackedFloat64Array.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedFloat64Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedFloat64Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedFloat64Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml
index da26e93b96..ec698ed8e5 100644
--- a/doc/classes/PackedInt32Array.xml
+++ b/doc/classes/PackedInt32Array.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedInt32Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedInt32Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedInt32Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml
index 9ddf43a837..ec4b3c1209 100644
--- a/doc/classes/PackedInt64Array.xml
+++ b/doc/classes/PackedInt64Array.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedInt64Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedInt64Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedInt64Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index d3a770b35b..4bba6563bb 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -104,7 +104,7 @@
</method>
</methods>
<members>
- <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{&quot;conn_count&quot;: 0,&quot;conns&quot;: PackedInt32Array(),&quot;editable_instances&quot;: [],&quot;names&quot;: PackedStringArray(),&quot;node_count&quot;: 0,&quot;node_paths&quot;: [],&quot;nodes&quot;: PackedInt32Array(),&quot;variants&quot;: [],&quot;version&quot;: 2}">
+ <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{ &quot;conn_count&quot;: 0, &quot;conns&quot;: PackedInt32Array(), &quot;editable_instances&quot;: [], &quot;names&quot;: PackedStringArray(), &quot;node_count&quot;: 0, &quot;node_paths&quot;: [], &quot;nodes&quot;: PackedInt32Array(), &quot;variants&quot;: [], &quot;version&quot;: 2 }">
A dictionary representation of the scene contents.
Available keys include "rnames" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for base scene children overrides, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene.
</member>
diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml
index 439d59dde7..ebe9c591b8 100644
--- a/doc/classes/PackedStringArray.xml
+++ b/doc/classes/PackedStringArray.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedStringArray" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedStringArray], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedStringArray].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml
index 8c4f052016..d72ca4b4bb 100644
--- a/doc/classes/PackedVector2Array.xml
+++ b/doc/classes/PackedVector2Array.xml
@@ -133,8 +133,11 @@
<method name="slice" qualifiers="const">
<return type="PackedVector2Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedVector2Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedVector2Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml
index 8229af984d..cbae0c40e7 100644
--- a/doc/classes/PackedVector3Array.xml
+++ b/doc/classes/PackedVector3Array.xml
@@ -132,8 +132,11 @@
<method name="slice" qualifiers="const">
<return type="PackedVector3Array" />
<argument index="0" name="begin" type="int" />
- <argument index="1" name="end" type="int" />
+ <argument index="1" name="end" type="int" default="2147483647" />
<description>
+ Returns the slice of the [PackedVector3Array], from [code]begin[/code] (inclusive) to [code]end[/code] (exclusive), as a new [PackedVector3Array].
+ The absolute value of [code]begin[/code] and [code]end[/code] will be clamped to the array size, so the default value for [code]end[/code] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
+ If either [code]begin[/code] or [code]end[/code] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
</description>
</method>
<method name="sort">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 9e2d789076..357186b917 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -487,7 +487,7 @@
</member>
<member name="display/window/handheld/orientation" type="int" setter="" getter="" default="0">
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
- [b]Note:[/b] When set to a portrait orientation, this project setting does not flip the project resolution's width and height automatically. Instead, you have to set [member display/window/size/width] and [member display/window/size/height] accordingly.
+ [b]Note:[/b] When set to a portrait orientation, this project setting does not flip the project resolution's width and height automatically. Instead, you have to set [member display/window/size/viewport_width] and [member display/window/size/viewport_height] accordingly.
</member>
<member name="display/window/ios/hide_home_indicator" type="bool" setter="" getter="" default="true">
If [code]true[/code], the home indicator is hidden automatically. This only affects iOS devices without a physical home button.
@@ -505,21 +505,23 @@
Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
[b]Note:[/b] This setting is ignored on iOS, Android, and HTML5.
</member>
- <member name="display/window/size/height" type="int" setter="" getter="" default="600">
- Sets the game's main viewport height. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.
- </member>
<member name="display/window/size/resizable" type="bool" setter="" getter="" default="true">
Allows the window to be resizable by default.
[b]Note:[/b] This setting is ignored on iOS and Android.
</member>
- <member name="display/window/size/test_height" type="int" setter="" getter="" default="0">
- If greater than zero, overrides the window height when running the game. Useful for testing stretch modes.
+ <member name="display/window/size/viewport_height" type="int" setter="" getter="" default="600">
+ Sets the game's main viewport height. On desktop platforms, this is also the initial window height.
+ </member>
+ <member name="display/window/size/viewport_width" type="int" setter="" getter="" default="1024">
+ Sets the game's main viewport width. On desktop platforms, this is also the initial window width.
</member>
- <member name="display/window/size/test_width" type="int" setter="" getter="" default="0">
- If greater than zero, overrides the window width when running the game. Useful for testing stretch modes.
+ <member name="display/window/size/window_height_override" type="int" setter="" getter="" default="0">
+ On desktop platforms, sets the game's initial window height.
+ [b]Note:[/b] By default, or when set to 0, the initial window height is the [member display/window/size/viewport_height]. This setting is ignored on iOS, Android, and HTML5.
</member>
- <member name="display/window/size/width" type="int" setter="" getter="" default="1024">
- Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.
+ <member name="display/window/size/window_width_override" type="int" setter="" getter="" default="0">
+ On desktop platforms, sets the game's initial window width.
+ [b]Note:[/b] By default, or when set to 0, the initial window width is the viewport [member display/window/size/viewport_width]. This setting is ignored on iOS, Android, and HTML5.
</member>
<member name="display/window/vsync/vsync_mode" type="int" setter="" getter="" default="1">
Sets the VSync mode for the main game window.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 5127455b4d..9e78372013 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -1001,7 +1001,7 @@
<return type="void" />
<argument index="0" name="env" type="RID" />
<argument index="1" name="enable" type="bool" />
- <argument index="2" name="cascades" type="int" enum="RenderingServer.EnvironmentSDFGICascades" />
+ <argument index="2" name="cascades" type="int" />
<argument index="3" name="min_cell_size" type="float" />
<argument index="4" name="y_scale" type="int" enum="RenderingServer.EnvironmentSDFGIYScale" />
<argument index="5" name="use_occlusion" type="bool" />
@@ -1838,8 +1838,7 @@
<argument index="1" name="primitive" type="int" enum="RenderingServer.PrimitiveType" />
<argument index="2" name="arrays" type="Array" />
<argument index="3" name="blend_shapes" type="Array" default="[]" />
- <argument index="4" name="lods" type="Dictionary" default="{
-}" />
+ <argument index="4" name="lods" type="Dictionary" default="{}" />
<argument index="5" name="compress_format" type="int" default="0" />
<description>
</description>
@@ -4180,12 +4179,6 @@
<constant name="ENV_SSIL_QUALITY_ULTRA" value="4" enum="EnvironmentSSILQuality">
Highest quality screen-space indirect lighting. Uses the adaptive target setting which can be dynamically adjusted to smoothly balance performance and visual quality.
</constant>
- <constant name="ENV_SDFGI_CASCADES_4" value="0" enum="EnvironmentSDFGICascades">
- </constant>
- <constant name="ENV_SDFGI_CASCADES_6" value="1" enum="EnvironmentSDFGICascades">
- </constant>
- <constant name="ENV_SDFGI_CASCADES_8" value="2" enum="EnvironmentSDFGICascades">
- </constant>
<constant name="ENV_SDFGI_Y_SCALE_DISABLED" value="0" enum="EnvironmentSDFGIYScale">
</constant>
<constant name="ENV_SDFGI_Y_SCALE_75_PERCENT" value="1" enum="EnvironmentSDFGIYScale">
diff --git a/doc/classes/TextLine.xml b/doc/classes/TextLine.xml
index 1eaccf4604..bf1abf86ae 100644
--- a/doc/classes/TextLine.xml
+++ b/doc/classes/TextLine.xml
@@ -24,8 +24,7 @@
<argument index="0" name="text" type="String" />
<argument index="1" name="fonts" type="Font" />
<argument index="2" name="size" type="int" />
- <argument index="3" name="opentype_features" type="Dictionary" default="{
-}" />
+ <argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
<description>
Adds text span and font to draw it.
diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml
index ff193a0e4b..3d3c0dd6ff 100644
--- a/doc/classes/TextParagraph.xml
+++ b/doc/classes/TextParagraph.xml
@@ -24,8 +24,7 @@
<argument index="0" name="text" type="String" />
<argument index="1" name="fonts" type="Font" />
<argument index="2" name="size" type="int" />
- <argument index="3" name="opentype_features" type="Dictionary" default="{
-}" />
+ <argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
<description>
Adds text span and font to draw it.
@@ -259,8 +258,7 @@
<argument index="1" name="fonts" type="Font" />
<argument index="2" name="size" type="int" />
<argument index="3" name="dropcap_margins" type="Rect2" default="Rect2(0, 0, 0, 0)" />
- <argument index="4" name="opentype_features" type="Dictionary" default="{
-}" />
+ <argument index="4" name="opentype_features" type="Dictionary" default="{}" />
<argument index="5" name="language" type="String" default="&quot;&quot;" />
<description>
Sets drop cap, overrides previously set drop cap. Drop cap (dropped capital) is a decorative element at the beginning of a paragraph that is larger than the rest of the text.
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 5be6113dda..61024ef0c8 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -913,8 +913,7 @@
<argument index="1" name="text" type="String" />
<argument index="2" name="fonts" type="Array" />
<argument index="3" name="size" type="int" />
- <argument index="4" name="opentype_features" type="Dictionary" default="{
-}" />
+ <argument index="4" name="opentype_features" type="Dictionary" default="{}" />
<argument index="5" name="language" type="String" default="&quot;&quot;" />
<description>
Adds text span and font to draw it to the text buffer.
diff --git a/doc/classes/TranslationServer.xml b/doc/classes/TranslationServer.xml
index a1b4404079..c90cb2987c 100644
--- a/doc/classes/TranslationServer.xml
+++ b/doc/classes/TranslationServer.xml
@@ -24,6 +24,46 @@
Clears the server from all translations.
</description>
</method>
+ <method name="compare_locales" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="locale_a" type="String" />
+ <argument index="1" name="locale_b" type="String" />
+ <description>
+ Compares two locales and return similarity score between [code]0[/code](no match) and [code]10[/code](full match).
+ </description>
+ </method>
+ <method name="get_all_countries" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of known country codes.
+ </description>
+ </method>
+ <method name="get_all_languages" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of known language codes.
+ </description>
+ </method>
+ <method name="get_all_scripts" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of known script codes.
+ </description>
+ </method>
+ <method name="get_country_name" qualifiers="const">
+ <return type="String" />
+ <argument index="0" name="country" type="String" />
+ <description>
+ Returns readable country name for the [code]country[/code] code.
+ </description>
+ </method>
+ <method name="get_language_name" qualifiers="const">
+ <return type="String" />
+ <argument index="0" name="language" type="String" />
+ <description>
+ Returns readable language name for the [code]language[/code] code.
+ </description>
+ </method>
<method name="get_loaded_locales" qualifiers="const">
<return type="Array" />
<description>
@@ -44,6 +84,13 @@
Returns a locale's language and its variant (e.g. [code]"en_US"[/code] would return [code]"English (United States)"[/code]).
</description>
</method>
+ <method name="get_script_name" qualifiers="const">
+ <return type="String" />
+ <argument index="0" name="script" type="String" />
+ <description>
+ Returns readable script name for the [code]script[/code] code.
+ </description>
+ </method>
<method name="get_translation_object">
<return type="Translation" />
<argument index="0" name="locale" type="String" />
@@ -80,6 +127,13 @@
If translations have been loaded beforehand for the new locale, they will be applied.
</description>
</method>
+ <method name="standardize_locale" qualifiers="const">
+ <return type="String" />
+ <argument index="0" name="locale" type="String" />
+ <description>
+ Retunrs [code]locale[/code] string standardized to match known locales (e.g. [code]en-US[/code] would be matched to [code]en_US[/code]).
+ </description>
+ </method>
<method name="translate" qualifiers="const">
<return type="StringName" />
<argument index="0" name="message" type="StringName" />
diff --git a/doc/classes/VFlowContainer.xml b/doc/classes/VFlowContainer.xml
new file mode 100644
index 0000000000..f58075a140
--- /dev/null
+++ b/doc/classes/VFlowContainer.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VFlowContainer" inherits="FlowContainer" version="4.0">
+ <brief_description>
+ Vertical flow container.
+ </brief_description>
+ <description>
+ Vertical version of [FlowContainer].
+ </description>
+ <tutorials>
+ </tutorials>
+ <theme_items>
+ <theme_item name="hseparation" data_type="constant" type="int" default="4">
+ The horizontal separation of children nodes.
+ </theme_item>
+ <theme_item name="vseparation" data_type="constant" type="int" default="4">
+ The vertical separation of children nodes.
+ </theme_item>
+ </theme_items>
+</class>
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 9e7d4f5435..31dda16b26 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -234,7 +234,7 @@ void RasterizerSceneGLES3::environment_set_ssil(RID p_env, bool p_enable, float
void RasterizerSceneGLES3::environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) {
}
-void RasterizerSceneGLES3::environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
+void RasterizerSceneGLES3::environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
}
void RasterizerSceneGLES3::environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) {
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index b73c053bc7..0f8c2e88b4 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -126,7 +126,7 @@ public:
void environment_set_ssil(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_sharpness, float p_normal_rejection) override;
void environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) override;
- void environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override;
+ void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override;
void environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) override;
void environment_set_sdfgi_frames_to_converge(RS::EnvironmentSDFGIFramesToConverge p_frames) override;
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index 976d8697ee..6acf654b04 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -413,7 +413,7 @@ void DocTools::generate(bool p_basic_types) {
//used to track uninitialized values using valgrind
//print_line("getting default value for " + String(name) + "." + String(E.name));
if (default_value_valid && default_value.get_type() != Variant::OBJECT) {
- prop.default_value = default_value.get_construct_string().replace("\n", "");
+ prop.default_value = default_value.get_construct_string().replace("\n", " ");
}
StringName setter = ClassDB::get_property_setter(name, E.name);
@@ -525,7 +525,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = i - (E.arguments.size() - E.default_arguments.size());
if (darg_idx >= 0) {
Variant default_arg = E.default_arguments[darg_idx];
- argument.default_value = default_arg.get_construct_string();
+ argument.default_value = default_arg.get_construct_string().replace("\n", " ");
}
method.arguments.push_back(argument);
@@ -588,7 +588,7 @@ void DocTools::generate(bool p_basic_types) {
tid.name = E;
tid.type = "Color";
tid.data_type = "color";
- tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string();
+ tid.default_value = Variant(Theme::get_default()->get_color(E, cname)).get_construct_string().replace("\n", " ");
c.theme_properties.push_back(tid);
}
@@ -760,7 +760,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = mi.default_arguments.size() - mi.arguments.size() + j;
if (darg_idx >= 0) {
Variant default_arg = mi.default_arguments[darg_idx];
- ad.default_value = default_arg.get_construct_string();
+ ad.default_value = default_arg.get_construct_string().replace("\n", " ");
}
method.arguments.push_back(ad);
@@ -804,7 +804,7 @@ void DocTools::generate(bool p_basic_types) {
DocData::PropertyDoc property;
property.name = pi.name;
property.type = Variant::get_type_name(pi.type);
- property.default_value = v.get(pi.name).get_construct_string();
+ property.default_value = v.get(pi.name).get_construct_string().replace("\n", " ");
c.properties.push_back(property);
}
@@ -816,7 +816,7 @@ void DocTools::generate(bool p_basic_types) {
DocData::ConstantDoc constant;
constant.name = E;
Variant value = Variant::get_constant_value(Variant::Type(i), E);
- constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string();
+ constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n", " ");
constant.is_value_valid = true;
c.constants.push_back(constant);
}
@@ -933,7 +933,7 @@ void DocTools::generate(bool p_basic_types) {
int darg_idx = j - (mi.arguments.size() - mi.default_arguments.size());
if (darg_idx >= 0) {
Variant default_arg = mi.default_arguments[darg_idx];
- ad.default_value = default_arg.get_construct_string();
+ ad.default_value = default_arg.get_construct_string().replace("\n", " ");
}
md.arguments.push_back(ad);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 06e3a63f4a..28cf2ee75f 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -44,18 +44,25 @@
DocTools *EditorHelp::doc = nullptr;
-void EditorHelp::_init_colors() {
- title_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- text_color = get_theme_color(SNAME("default_color"), SNAME("RichTextLabel"));
- headline_color = get_theme_color(SNAME("headline_color"), SNAME("EditorHelp"));
- base_type_color = title_color.lerp(text_color, 0.5);
- comment_color = text_color * Color(1, 1, 1, 0.6);
- symbol_color = comment_color;
- value_color = text_color * Color(1, 1, 1, 0.6);
- qualifier_color = text_color * Color(1, 1, 1, 0.8);
- type_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(text_color, 0.5);
- class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
- class_desc->add_theme_constant_override("line_separation", Math::round(5 * EDSCALE));
+void EditorHelp::_update_theme() {
+ text_color = get_theme_color("text_color", "EditorHelp");
+ title_color = get_theme_color("title_color", "EditorHelp");
+ headline_color = get_theme_color("headline_color", "EditorHelp");
+ comment_color = get_theme_color("comment_color", "EditorHelp");
+ symbol_color = get_theme_color("symbol_color", "EditorHelp");
+ value_color = get_theme_color("value_color", "EditorHelp");
+ qualifier_color = get_theme_color("qualifier_color", "EditorHelp");
+ type_color = get_theme_color("type_color", "EditorHelp");
+
+ class_desc->add_theme_color_override("selection_color", get_theme_color("selection_color", "EditorHelp"));
+ class_desc->add_theme_constant_override("line_separation", get_theme_constant("line_separation", "EditorHelp"));
+ class_desc->add_theme_constant_override("table_hseparation", get_theme_constant("table_hseparation", "EditorHelp"));
+ class_desc->add_theme_constant_override("table_vseparation", get_theme_constant("table_vseparation", "EditorHelp"));
+
+ doc_font = get_theme_font(SNAME("doc"), SNAME("EditorFonts"));
+ doc_bold_font = get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"));
+ doc_title_font = get_theme_font(SNAME("doc_title"), SNAME("EditorFonts"));
+ doc_code_font = get_theme_font(SNAME("doc_source"), SNAME("EditorFonts"));
}
void EditorHelp::_search(bool p_search_previous) {
@@ -184,8 +191,7 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
t = p_enum.get_slice(".", 0);
}
}
- const Color text_color = get_theme_color(SNAME("default_color"), SNAME("RichTextLabel"));
- const Color type_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(text_color, 0.5);
+
class_desc->push_color(type_color);
bool add_array = false;
if (can_ref) {
@@ -496,16 +502,11 @@ void EditorHelp::_update_doc() {
method_line.clear();
section_line.clear();
- _init_colors();
-
- DocData::ClassDoc cd = doc->class_list[edited_class]; //make a copy, so we can sort without worrying
-
- Ref<Font> doc_font = get_theme_font(SNAME("doc"), SNAME("EditorFonts"));
- Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"));
- Ref<Font> doc_title_font = get_theme_font(SNAME("doc_title"), SNAME("EditorFonts"));
- Ref<Font> doc_code_font = get_theme_font(SNAME("doc_source"), SNAME("EditorFonts"));
+ _update_theme();
String link_color_text = title_color.to_html(false);
+ DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying.
+
// Class name
section_line.push_back(Pair<String, int>(TTR("Top"), 0));
class_desc->push_font(doc_title_font);
@@ -1476,11 +1477,9 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt) {
Ref<Font> doc_kbd_font = p_rt->get_theme_font(SNAME("doc_keyboard"), SNAME("EditorFonts"));
Color headline_color = p_rt->get_theme_color(SNAME("headline_color"), SNAME("EditorHelp"));
- Color accent_color = p_rt->get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- Color property_color = p_rt->get_theme_color(SNAME("property_color"), SNAME("Editor"));
- Color link_color = accent_color.lerp(headline_color, 0.8);
- Color code_color = accent_color.lerp(headline_color, 0.6);
- Color kbd_color = accent_color.lerp(property_color, 0.6);
+ Color link_color = p_rt->get_theme_color(SNAME("link_color"), SNAME("EditorHelp"));
+ Color code_color = p_rt->get_theme_color(SNAME("code_color"), SNAME("EditorHelp"));
+ Color kbd_color = p_rt->get_theme_color(SNAME("kbd_color"), SNAME("EditorHelp"));
String bbcode = p_bbcode.dedent().replace("\t", "").replace("\r", "").strip_edges();
@@ -1941,16 +1940,16 @@ void EditorHelpBit::_bind_methods() {
void EditorHelpBit::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp")));
+ } break;
+
case NOTIFICATION_READY: {
rich_text->clear();
_add_text_to_rt(text, rich_text);
} break;
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
- } break;
- default:
- break;
}
}
@@ -1964,7 +1963,6 @@ EditorHelpBit::EditorHelpBit() {
rich_text = memnew(RichTextLabel);
add_child(rich_text);
rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
- rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
rich_text->set_override_selected_font_color(false);
set_custom_minimum_size(Size2(0, 70 * EDSCALE));
}
diff --git a/editor/editor_help.h b/editor/editor_help.h
index eb879c6d39..377ae05a08 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -126,17 +126,21 @@ class EditorHelp : public VBoxContainer {
String base_path;
- Color title_color;
Color text_color;
+ Color title_color;
Color headline_color;
- Color base_type_color;
- Color type_color;
Color comment_color;
Color symbol_color;
Color value_color;
Color qualifier_color;
+ Color type_color;
+
+ Ref<Font> doc_font;
+ Ref<Font> doc_bold_font;
+ Ref<Font> doc_title_font;
+ Ref<Font> doc_code_font;
- void _init_colors();
+ void _update_theme();
void _help_callback(const String &p_topic);
void _add_text(const String &p_bbcode);
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
new file mode 100644
index 0000000000..5c4ece7065
--- /dev/null
+++ b/editor/editor_locale_dialog.cpp
@@ -0,0 +1,563 @@
+/*************************************************************************/
+/* editor_locale_dialog.cpp */
+/*************************************************************************/
+/* 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. */
+/*************************************************************************/
+
+#include "editor_locale_dialog.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "scene/gui/check_button.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/tree.h"
+
+static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
+ return (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+void EditorLocaleDialog::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("locale_selected", PropertyInfo(Variant::STRING, "locale")));
+}
+
+void EditorLocaleDialog::ok_pressed() {
+ if (edit_filters->is_pressed()) {
+ return; // Do not update, if in filter edit mode.
+ }
+
+ String locale;
+ if (lang_code->get_text().is_empty()) {
+ return; // Language code is required.
+ }
+ locale = lang_code->get_text();
+
+ if (!script_code->get_text().is_empty()) {
+ locale += "_" + script_code->get_text();
+ }
+ if (!country_code->get_text().is_empty()) {
+ locale += "_" + country_code->get_text();
+ }
+ if (!variant_code->get_text().is_empty()) {
+ locale += "_" + variant_code->get_text();
+ }
+
+ emit_signal(SNAME("locale_selected"), TranslationServer::get_singleton()->standardize_locale(locale));
+ hide();
+}
+
+void EditorLocaleDialog::_item_selected() {
+ if (updating_lists) {
+ return;
+ }
+
+ if (edit_filters->is_pressed()) {
+ return; // Do not update, if in filter edit mode.
+ }
+
+ TreeItem *l = lang_list->get_selected();
+ if (l) {
+ lang_code->set_text(l->get_metadata(0).operator String());
+ }
+
+ TreeItem *s = script_list->get_selected();
+ if (s) {
+ script_code->set_text(s->get_metadata(0).operator String());
+ }
+
+ TreeItem *c = cnt_list->get_selected();
+ if (c) {
+ country_code->set_text(c->get_metadata(0).operator String());
+ }
+}
+
+void EditorLocaleDialog::_toggle_advanced(bool p_checked) {
+ if (!p_checked) {
+ script_code->set_text("");
+ variant_code->set_text("");
+ }
+ _update_tree();
+}
+
+void EditorLocaleDialog::_post_popup() {
+ ConfirmationDialog::_post_popup();
+
+ if (!locale_set) {
+ lang_code->set_text("");
+ script_code->set_text("");
+ country_code->set_text("");
+ variant_code->set_text("");
+ }
+ edit_filters->set_pressed(false);
+ _update_tree();
+}
+
+void EditorLocaleDialog::_filter_lang_option_changed() {
+ TreeItem *t = lang_list->get_edited();
+ String lang = t->get_metadata(0);
+ bool checked = t->is_checked(0);
+
+ Variant prev;
+ Array f_lang_all;
+
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
+ f_lang_all = ProjectSettings::get_singleton()->get("internationalization/locale/language_filter");
+ prev = f_lang_all;
+ }
+
+ int l_idx = f_lang_all.find(lang);
+
+ if (checked) {
+ if (l_idx == -1) {
+ f_lang_all.append(lang);
+ }
+ } else {
+ if (l_idx != -1) {
+ f_lang_all.remove_at(l_idx);
+ }
+ }
+
+ f_lang_all.sort();
+
+ undo_redo->create_action(TTR("Changed Locale Language Filter"));
+ undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", f_lang_all);
+ undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", prev);
+ undo_redo->commit_action();
+}
+
+void EditorLocaleDialog::_filter_script_option_changed() {
+ TreeItem *t = script_list->get_edited();
+ String script = t->get_metadata(0);
+ bool checked = t->is_checked(0);
+
+ Variant prev;
+ Array f_script_all;
+
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
+ f_script_all = ProjectSettings::get_singleton()->get("internationalization/locale/script_filter");
+ prev = f_script_all;
+ }
+
+ int l_idx = f_script_all.find(script);
+
+ if (checked) {
+ if (l_idx == -1) {
+ f_script_all.append(script);
+ }
+ } else {
+ if (l_idx != -1) {
+ f_script_all.remove_at(l_idx);
+ }
+ }
+
+ f_script_all.sort();
+
+ undo_redo->create_action(TTR("Changed Locale Script Filter"));
+ undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", f_script_all);
+ undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", prev);
+ undo_redo->commit_action();
+}
+
+void EditorLocaleDialog::_filter_cnt_option_changed() {
+ TreeItem *t = cnt_list->get_edited();
+ String cnt = t->get_metadata(0);
+ bool checked = t->is_checked(0);
+
+ Variant prev;
+ Array f_cnt_all;
+
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
+ f_cnt_all = ProjectSettings::get_singleton()->get("internationalization/locale/country_filter");
+ prev = f_cnt_all;
+ }
+
+ int l_idx = f_cnt_all.find(cnt);
+
+ if (checked) {
+ if (l_idx == -1) {
+ f_cnt_all.append(cnt);
+ }
+ } else {
+ if (l_idx != -1) {
+ f_cnt_all.remove_at(l_idx);
+ }
+ }
+
+ f_cnt_all.sort();
+
+ undo_redo->create_action(TTR("Changed Locale Country Filter"));
+ undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", f_cnt_all);
+ undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", prev);
+ undo_redo->commit_action();
+}
+
+void EditorLocaleDialog::_filter_mode_changed(int p_mode) {
+ int f_mode = filter_mode->get_selected_id();
+ Variant prev;
+
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
+ prev = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter_mode");
+ }
+
+ undo_redo->create_action(TTR("Changed Locale Filter Mode"));
+ undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", f_mode);
+ undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", prev);
+ undo_redo->commit_action();
+
+ _update_tree();
+}
+
+void EditorLocaleDialog::_edit_filters(bool p_checked) {
+ _update_tree();
+}
+
+void EditorLocaleDialog::_update_tree() {
+ updating_lists = true;
+
+ int filter = SHOW_ALL_LOCALES;
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
+ filter = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter_mode");
+ }
+ Array f_lang_all;
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
+ f_lang_all = ProjectSettings::get_singleton()->get("internationalization/locale/language_filter");
+ }
+ Array f_cnt_all;
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
+ f_cnt_all = ProjectSettings::get_singleton()->get("internationalization/locale/country_filter");
+ }
+ Array f_script_all;
+ if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
+ f_script_all = ProjectSettings::get_singleton()->get("internationalization/locale/script_filter");
+ }
+ bool is_edit_mode = edit_filters->is_pressed();
+
+ filter_mode->select(filter);
+
+ // Hide text advanced edit and disable OK button if in filter edit mode.
+ advanced->set_visible(!is_edit_mode);
+ hb_locale->set_visible(!is_edit_mode && advanced->is_pressed());
+ vb_script_list->set_visible(advanced->is_pressed());
+ get_ok_button()->set_disabled(is_edit_mode);
+
+ // Update language list.
+ lang_list->clear();
+ TreeItem *l_root = lang_list->create_item(nullptr);
+ lang_list->set_hide_root(true);
+
+ Vector<String> languages = TranslationServer::get_singleton()->get_all_languages();
+ for (const String &E : languages) {
+ if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_lang_all.has(E) || f_lang_all.is_empty()) {
+ const String &lang = TranslationServer::get_singleton()->get_language_name(E);
+ TreeItem *t = lang_list->create_item(l_root);
+ if (is_edit_mode) {
+ t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ t->set_editable(0, true);
+ t->set_checked(0, f_lang_all.has(E));
+ } else if (lang_code->get_text() == E) {
+ t->select(0);
+ }
+ t->set_text(0, vformat("%s [%s]", lang, E));
+ t->set_metadata(0, E);
+ }
+ }
+
+ // Update script list.
+ script_list->clear();
+ TreeItem *s_root = script_list->create_item(nullptr);
+ script_list->set_hide_root(true);
+
+ if (!is_edit_mode) {
+ TreeItem *t = script_list->create_item(s_root);
+ t->set_text(0, "[Default]");
+ t->set_metadata(0, "");
+ }
+
+ Vector<String> scripts = TranslationServer::get_singleton()->get_all_scripts();
+ for (const String &E : scripts) {
+ if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_script_all.has(E) || f_script_all.is_empty()) {
+ const String &script = TranslationServer::get_singleton()->get_script_name(E);
+ TreeItem *t = script_list->create_item(s_root);
+ if (is_edit_mode) {
+ t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ t->set_editable(0, true);
+ t->set_checked(0, f_script_all.has(E));
+ } else if (script_code->get_text() == E) {
+ t->select(0);
+ }
+ t->set_text(0, vformat("%s [%s]", script, E));
+ t->set_metadata(0, E);
+ }
+ }
+
+ // Update country list.
+ cnt_list->clear();
+ TreeItem *c_root = cnt_list->create_item(nullptr);
+ cnt_list->set_hide_root(true);
+
+ if (!is_edit_mode) {
+ TreeItem *t = cnt_list->create_item(c_root);
+ t->set_text(0, "[Default]");
+ t->set_metadata(0, "");
+ }
+
+ Vector<String> countries = TranslationServer::get_singleton()->get_all_countries();
+ for (const String &E : countries) {
+ if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_cnt_all.has(E) || f_cnt_all.is_empty()) {
+ const String &cnt = TranslationServer::get_singleton()->get_country_name(E);
+ TreeItem *t = cnt_list->create_item(c_root);
+ if (is_edit_mode) {
+ t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ t->set_editable(0, true);
+ t->set_checked(0, f_cnt_all.has(E));
+ } else if (country_code->get_text() == E) {
+ t->select(0);
+ }
+ t->set_text(0, vformat("%s [%s]", cnt, E));
+ t->set_metadata(0, E);
+ }
+ }
+ updating_lists = false;
+}
+
+void EditorLocaleDialog::set_locale(const String &p_locale) {
+ const String &locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
+ if (locale.is_empty()) {
+ locale_set = false;
+
+ lang_code->set_text("");
+ script_code->set_text("");
+ country_code->set_text("");
+ variant_code->set_text("");
+ } else {
+ locale_set = true;
+
+ Vector<String> locale_elements = p_locale.split("_");
+ lang_code->set_text(locale_elements[0]);
+ if (locale_elements.size() >= 2) {
+ if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ script_code->set_text(locale_elements[1]);
+ advanced->set_pressed(true);
+ }
+ if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ country_code->set_text(locale_elements[1]);
+ }
+ }
+ if (locale_elements.size() >= 3) {
+ if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ country_code->set_text(locale_elements[2]);
+ } else {
+ variant_code->set_text(locale_elements[2].to_lower());
+ advanced->set_pressed(true);
+ }
+ }
+ if (locale_elements.size() >= 4) {
+ variant_code->set_text(locale_elements[3].to_lower());
+ advanced->set_pressed(true);
+ }
+ }
+}
+
+void EditorLocaleDialog::popup_locale_dialog() {
+ popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8);
+}
+
+EditorLocaleDialog::EditorLocaleDialog() {
+ undo_redo = EditorNode::get_undo_redo();
+
+ set_title(TTR("Select a Locale"));
+
+ VBoxContainer *vb = memnew(VBoxContainer);
+ {
+ HBoxContainer *hb_filter = memnew(HBoxContainer);
+ {
+ filter_mode = memnew(OptionButton);
+ filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES);
+ filter_mode->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES);
+ filter_mode->select(0);
+ filter_mode->connect("item_selected", callable_mp(this, &EditorLocaleDialog::_filter_mode_changed));
+ hb_filter->add_child(filter_mode);
+ }
+ {
+ edit_filters = memnew(CheckButton);
+ edit_filters->set_text("Edit Filters");
+ edit_filters->set_toggle_mode(true);
+ edit_filters->set_pressed(false);
+ edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters));
+ hb_filter->add_child(edit_filters);
+ }
+ {
+ advanced = memnew(CheckButton);
+ advanced->set_text("Advanced");
+ advanced->set_toggle_mode(true);
+ advanced->set_pressed(false);
+ advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
+ hb_filter->add_child(advanced);
+ }
+ vb->add_child(hb_filter);
+ }
+ {
+ HBoxContainer *hb_lists = memnew(HBoxContainer);
+ hb_lists->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ VBoxContainer *vb_lang_list = memnew(VBoxContainer);
+ vb_lang_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *lang_lbl = memnew(Label);
+ lang_lbl->set_text(TTR("Language:"));
+ vb_lang_list->add_child(lang_lbl);
+ }
+ {
+ lang_list = memnew(Tree);
+ lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ lang_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
+ lang_list->set_columns(1);
+ lang_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_lang_option_changed));
+ vb_lang_list->add_child(lang_list);
+ }
+ hb_lists->add_child(vb_lang_list);
+ }
+ {
+ vb_script_list = memnew(VBoxContainer);
+ vb_script_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *script_lbl = memnew(Label);
+ script_lbl->set_text(TTR("Script:"));
+ vb_script_list->add_child(script_lbl);
+ }
+ {
+ script_list = memnew(Tree);
+ script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ script_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
+ script_list->set_columns(1);
+ script_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_script_option_changed));
+ vb_script_list->add_child(script_list);
+ }
+ hb_lists->add_child(vb_script_list);
+ }
+ {
+ VBoxContainer *vb_cnt_list = memnew(VBoxContainer);
+ vb_cnt_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *cnt_lbl = memnew(Label);
+ cnt_lbl->set_text(TTR("Country:"));
+ vb_cnt_list->add_child(cnt_lbl);
+ }
+ {
+ cnt_list = memnew(Tree);
+ cnt_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ cnt_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
+ cnt_list->set_columns(1);
+ cnt_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_cnt_option_changed));
+ vb_cnt_list->add_child(cnt_list);
+ }
+ hb_lists->add_child(vb_cnt_list);
+ }
+ vb->add_child(hb_lists);
+ }
+ {
+ hb_locale = memnew(HBoxContainer);
+ hb_locale->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ {
+ VBoxContainer *vb_language = memnew(VBoxContainer);
+ vb_language->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *language_lbl = memnew(Label);
+ language_lbl->set_text(TTR("Language"));
+ vb_language->add_child(language_lbl);
+ }
+ {
+ lang_code = memnew(LineEdit);
+ lang_code->set_max_length(3);
+ lang_code->set_tooltip("Language");
+ vb_language->add_child(lang_code);
+ }
+ hb_locale->add_child(vb_language);
+ }
+ {
+ VBoxContainer *vb_script = memnew(VBoxContainer);
+ vb_script->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *script_lbl = memnew(Label);
+ script_lbl->set_text(TTR("Script"));
+ vb_script->add_child(script_lbl);
+ }
+ {
+ script_code = memnew(LineEdit);
+ script_code->set_max_length(4);
+ script_code->set_tooltip("Script");
+ vb_script->add_child(script_code);
+ }
+ hb_locale->add_child(vb_script);
+ }
+ {
+ VBoxContainer *vb_country = memnew(VBoxContainer);
+ vb_country->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *country_lbl = memnew(Label);
+ country_lbl->set_text(TTR("Country"));
+ vb_country->add_child(country_lbl);
+ }
+ {
+ country_code = memnew(LineEdit);
+ country_code->set_max_length(2);
+ country_code->set_tooltip("Country");
+ vb_country->add_child(country_code);
+ }
+ hb_locale->add_child(vb_country);
+ }
+ {
+ VBoxContainer *vb_variant = memnew(VBoxContainer);
+ vb_variant->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ {
+ Label *variant_lbl = memnew(Label);
+ variant_lbl->set_text(TTR("Variant"));
+ vb_variant->add_child(variant_lbl);
+ }
+ {
+ variant_code = memnew(LineEdit);
+ variant_code->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ variant_code->set_placeholder("Variant");
+ variant_code->set_tooltip("Variant");
+ vb_variant->add_child(variant_code);
+ }
+ hb_locale->add_child(vb_variant);
+ }
+ }
+ vb->add_child(hb_locale);
+ }
+ add_child(vb);
+ _update_tree();
+
+ get_ok_button()->set_text(TTR("Select"));
+}
diff --git a/editor/editor_locale_dialog.h b/editor/editor_locale_dialog.h
new file mode 100644
index 0000000000..7a4828e83a
--- /dev/null
+++ b/editor/editor_locale_dialog.h
@@ -0,0 +1,93 @@
+/*************************************************************************/
+/* editor_locale_dialog.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 EDITOR_LOCALE_DIALOG_H
+#define EDITOR_LOCALE_DIALOG_H
+
+#include "core/string/translation.h"
+#include "scene/gui/dialogs.h"
+
+class Button;
+class HBoxContainer;
+class VBoxContainer;
+class LineEdit;
+class Tree;
+class OptionButton;
+class UndoRedo;
+
+class EditorLocaleDialog : public ConfirmationDialog {
+ GDCLASS(EditorLocaleDialog, ConfirmationDialog);
+
+ enum LocaleFilter {
+ SHOW_ALL_LOCALES,
+ SHOW_ONLY_SELECTED_LOCALES,
+ };
+
+ HBoxContainer *hb_locale = nullptr;
+ VBoxContainer *vb_script_list = nullptr;
+ OptionButton *filter_mode = nullptr;
+ Button *edit_filters = nullptr;
+ Button *advanced = nullptr;
+ LineEdit *lang_code = nullptr;
+ LineEdit *script_code = nullptr;
+ LineEdit *country_code = nullptr;
+ LineEdit *variant_code = nullptr;
+ Tree *lang_list = nullptr;
+ Tree *script_list = nullptr;
+ Tree *cnt_list = nullptr;
+
+ UndoRedo *undo_redo = nullptr;
+
+ bool locale_set = false;
+ bool updating_lists = false;
+
+protected:
+ static void _bind_methods();
+ virtual void _post_popup() override;
+ virtual void ok_pressed() override;
+
+ void _item_selected();
+ void _filter_lang_option_changed();
+ void _filter_script_option_changed();
+ void _filter_cnt_option_changed();
+ void _filter_mode_changed(int p_mode);
+ void _edit_filters(bool p_checked);
+ void _toggle_advanced(bool p_checked);
+
+ void _update_tree();
+
+public:
+ EditorLocaleDialog();
+
+ void set_locale(const String &p_locale);
+ void popup_locale_dialog();
+};
+
+#endif // EDITOR_LOCALE_DIALOG_H
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 5d868777e7..cc92d391d9 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -957,9 +957,10 @@ void EditorNode::_fs_changed() {
if (!export_error.is_empty()) {
ERR_PRINT(export_error);
- OS::get_singleton()->set_exit_code(EXIT_FAILURE);
+ _exit_editor(EXIT_FAILURE);
+ } else {
+ _exit_editor(EXIT_SUCCESS);
}
- _exit_editor();
}
}
@@ -1775,7 +1776,7 @@ void EditorNode::restart_editor() {
to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path();
}
- _exit_editor();
+ _exit_editor(EXIT_SUCCESS);
List<String> args;
args.push_back("--path");
@@ -2593,13 +2594,21 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
file->set_current_path(path.replacen("." + ext, "." + extensions.front()->get()));
}
}
- } else {
- String existing;
- if (extensions.size()) {
- String root_name(scene->get_name());
- existing = root_name + "." + extensions.front()->get().to_lower();
+ } else if (extensions.size()) {
+ String root_name = scene->get_name();
+ // Very similar to node naming logic.
+ switch (ProjectSettings::get_singleton()->get("editor/scene/scene_naming").operator int()) {
+ case SCENE_NAME_CASING_AUTO:
+ // Use casing of the root node.
+ break;
+ case SCENE_NAME_CASING_PASCAL_CASE: {
+ root_name = root_name.capitalize().replace(" ", "");
+ } break;
+ case SCENE_NAME_CASING_SNAKE_CASE:
+ root_name = root_name.capitalize().replace(" ", "").replace("-", "_").camelcase_to_underscore();
+ break;
}
- file->set_current_path(existing);
+ file->set_current_path(root_name + "." + extensions.front()->get().to_lower());
}
file->popup_file_dialog();
file->set_title(TTR("Save Scene As..."));
@@ -2992,7 +3001,7 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) {
return -1;
}
-void EditorNode::_exit_editor() {
+void EditorNode::_exit_editor(int p_exit_code) {
exiting = true;
resource_preview->stop(); // stop early to avoid crashes
_save_docks();
@@ -3000,7 +3009,7 @@ void EditorNode::_exit_editor() {
// Dim the editor window while it's quitting to make it clearer that it's busy
dim_editor(true);
- get_tree()->quit();
+ get_tree()->quit(p_exit_code);
}
void EditorNode::_discard_changes(const String &p_str) {
@@ -3050,12 +3059,12 @@ void EditorNode::_discard_changes(const String &p_str) {
} break;
case FILE_QUIT: {
_menu_option_confirm(RUN_STOP, true);
- _exit_editor();
+ _exit_editor(EXIT_SUCCESS);
} break;
case RUN_PROJECT_MANAGER: {
_menu_option_confirm(RUN_STOP, true);
- _exit_editor();
+ _exit_editor(EXIT_SUCCESS);
String exec = OS::get_singleton()->get_executable_path();
List<String> args;
@@ -5673,6 +5682,8 @@ void EditorNode::_feature_profile_changed() {
}
void EditorNode::_bind_methods() {
+ GLOBAL_DEF("editor/scene/scene_naming", SCENE_NAME_CASING_SNAKE_CASE);
+ ProjectSettings::get_singleton()->set_custom_property_info("editor/scene/scene_naming", PropertyInfo(Variant::INT, "editor/scene/scene_naming", PROPERTY_HINT_ENUM, "Auto,PascalCase,snake_case"));
ClassDB::bind_method("_editor_select", &EditorNode::_editor_select);
ClassDB::bind_method("_node_renamed", &EditorNode::_node_renamed);
ClassDB::bind_method("edit_node", &EditorNode::edit_node);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index e315f1f4b3..ff56040297 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -216,6 +216,12 @@ private:
TOOL_MENU_BASE = 1000
};
+ enum ScriptNameCasing {
+ SCENE_NAME_CASING_AUTO,
+ SCENE_NAME_CASING_PASCAL_CASE,
+ SCENE_NAME_CASING_SNAKE_CASE
+ };
+
SubViewport *scene_root; // root of the scene being edited
PanelContainer *scene_root_parent;
@@ -529,7 +535,7 @@ private:
void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
String _recent_scene;
- void _exit_editor();
+ void _exit_editor(int p_exit_code);
bool convert_old;
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index aeca340cb1..6579b1eb31 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -421,14 +421,10 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C
} break;
case CONTAINER_SPATIAL_EDITOR_SIDE_LEFT: {
- Node3DEditor::get_singleton()->get_palette_split()->add_child(p_control);
- Node3DEditor::get_singleton()->get_palette_split()->move_child(p_control, 0);
-
+ Node3DEditor::get_singleton()->add_control_to_left_panel(p_control);
} break;
case CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT: {
- Node3DEditor::get_singleton()->get_palette_split()->add_child(p_control);
- Node3DEditor::get_singleton()->get_palette_split()->move_child(p_control, 1);
-
+ Node3DEditor::get_singleton()->add_control_to_right_panel(p_control);
} break;
case CONTAINER_SPATIAL_EDITOR_BOTTOM: {
Node3DEditor::get_singleton()->get_shader_split()->add_child(p_control);
@@ -439,14 +435,10 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C
} break;
case CONTAINER_CANVAS_EDITOR_SIDE_LEFT: {
- CanvasItemEditor::get_singleton()->get_palette_split()->add_child(p_control);
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(p_control, 0);
-
+ CanvasItemEditor::get_singleton()->add_control_to_left_panel(p_control);
} break;
case CONTAINER_CANVAS_EDITOR_SIDE_RIGHT: {
- CanvasItemEditor::get_singleton()->get_palette_split()->add_child(p_control);
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(p_control, 1);
-
+ CanvasItemEditor::get_singleton()->add_control_to_right_panel(p_control);
} break;
case CONTAINER_CANVAS_EDITOR_BOTTOM: {
CanvasItemEditor::get_singleton()->get_bottom_split()->add_child(p_control);
@@ -481,10 +473,11 @@ void EditorPlugin::remove_control_from_container(CustomControlContainer p_locati
Node3DEditor::get_singleton()->remove_control_from_menu_panel(p_control);
} break;
- case CONTAINER_SPATIAL_EDITOR_SIDE_LEFT:
+ case CONTAINER_SPATIAL_EDITOR_SIDE_LEFT: {
+ Node3DEditor::get_singleton()->remove_control_from_left_panel(p_control);
+ } break;
case CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT: {
- Node3DEditor::get_singleton()->get_palette_split()->remove_child(p_control);
-
+ Node3DEditor::get_singleton()->remove_control_from_right_panel(p_control);
} break;
case CONTAINER_SPATIAL_EDITOR_BOTTOM: {
Node3DEditor::get_singleton()->get_shader_split()->remove_child(p_control);
@@ -494,10 +487,11 @@ void EditorPlugin::remove_control_from_container(CustomControlContainer p_locati
CanvasItemEditor::get_singleton()->remove_control_from_menu_panel(p_control);
} break;
- case CONTAINER_CANVAS_EDITOR_SIDE_LEFT:
+ case CONTAINER_CANVAS_EDITOR_SIDE_LEFT: {
+ CanvasItemEditor::get_singleton()->remove_control_from_left_panel(p_control);
+ } break;
case CONTAINER_CANVAS_EDITOR_SIDE_RIGHT: {
- CanvasItemEditor::get_singleton()->get_palette_split()->remove_child(p_control);
-
+ CanvasItemEditor::get_singleton()->remove_control_from_right_panel(p_control);
} break;
case CONTAINER_CANVAS_EDITOR_BOTTOM: {
CanvasItemEditor::get_singleton()->get_bottom_split()->remove_child(p_control);
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 97a38b9200..6bb4b5e81b 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -343,6 +343,64 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() {
add_focusable(cancel_button);
}
+//////////////////// LOCALE ////////////////////////
+
+void EditorPropertyLocale::_locale_selected(const String &p_locale) {
+ emit_changed(get_edited_property(), p_locale);
+ update_property();
+}
+
+void EditorPropertyLocale::_locale_pressed() {
+ if (!dialog) {
+ dialog = memnew(EditorLocaleDialog);
+ dialog->connect("locale_selected", callable_mp(this, &EditorPropertyLocale::_locale_selected));
+ add_child(dialog);
+ }
+
+ String locale_code = get_edited_object()->get(get_edited_property());
+ dialog->set_locale(locale_code);
+ dialog->popup_locale_dialog();
+}
+
+void EditorPropertyLocale::update_property() {
+ String locale_code = get_edited_object()->get(get_edited_property());
+ locale->set_text(locale_code);
+ locale->set_tooltip(locale_code);
+}
+
+void EditorPropertyLocale::setup(const String &p_hint_text) {
+}
+
+void EditorPropertyLocale::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ locale_edit->set_icon(get_theme_icon(SNAME("Translation"), SNAME("EditorIcons")));
+ }
+}
+
+void EditorPropertyLocale::_locale_focus_exited() {
+ _locale_selected(locale->get_text());
+}
+
+void EditorPropertyLocale::_bind_methods() {
+}
+
+EditorPropertyLocale::EditorPropertyLocale() {
+ HBoxContainer *locale_hb = memnew(HBoxContainer);
+ add_child(locale_hb);
+ locale = memnew(LineEdit);
+ locale_hb->add_child(locale);
+ locale->connect("text_submitted", callable_mp(this, &EditorPropertyLocale::_locale_selected));
+ locale->connect("focus_exited", callable_mp(this, &EditorPropertyLocale::_locale_focus_exited));
+ locale->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ locale_edit = memnew(Button);
+ locale_edit->set_clip_text(true);
+ locale_hb->add_child(locale_edit);
+ add_focusable(locale);
+ dialog = nullptr;
+ locale_edit->connect("pressed", callable_mp(this, &EditorPropertyLocale::_locale_pressed));
+}
+
///////////////////// PATH /////////////////////////
void EditorPropertyPath::_set_read_only(bool p_read_only) {
@@ -3379,6 +3437,10 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
EditorPropertyClassName *editor = memnew(EditorPropertyClassName);
editor->setup("Object", p_hint_text);
return editor;
+ } else if (p_hint == PROPERTY_HINT_LOCALE_ID) {
+ EditorPropertyLocale *editor = memnew(EditorPropertyLocale);
+ editor->setup(p_hint_text);
+ return editor;
} else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE) {
Vector<String> extensions = p_hint_text.split(",");
bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE;
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index e62f6823a3..fdb0360d6b 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -33,6 +33,7 @@
#include "editor/create_dialog.h"
#include "editor/editor_inspector.h"
+#include "editor/editor_locale_dialog.h"
#include "editor/editor_resource_picker.h"
#include "editor/editor_spin_slider.h"
#include "editor/property_selector.h"
@@ -153,6 +154,26 @@ public:
EditorPropertyPath();
};
+class EditorPropertyLocale : public EditorProperty {
+ GDCLASS(EditorPropertyLocale, EditorProperty);
+ EditorLocaleDialog *dialog;
+ LineEdit *locale;
+ Button *locale_edit;
+
+ void _locale_selected(const String &p_locale);
+ void _locale_pressed();
+ void _locale_focus_exited();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ void setup(const String &p_hit_string);
+ virtual void update_property() override;
+ EditorPropertyLocale();
+};
+
class EditorPropertyClassName : public EditorProperty {
GDCLASS(EditorPropertyClassName, EditorProperty);
diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp
index 3c1799d80c..574abf85ea 100644
--- a/editor/editor_run.cpp
+++ b/editor/editor_run.cpp
@@ -98,15 +98,15 @@ Error EditorRun::run(const String &p_scene) {
screen_rect.position = DisplayServer::get_singleton()->screen_get_position(screen);
screen_rect.size = DisplayServer::get_singleton()->screen_get_size(screen);
+ Size2 window_size;
+ window_size.x = ProjectSettings::get_singleton()->get("display/window/size/viewport_width");
+ window_size.y = ProjectSettings::get_singleton()->get("display/window/size/viewport_height");
+
Size2 desired_size;
- desired_size.x = ProjectSettings::get_singleton()->get("display/window/size/width");
- desired_size.y = ProjectSettings::get_singleton()->get("display/window/size/height");
-
- Size2 test_size;
- test_size.x = ProjectSettings::get_singleton()->get("display/window/size/test_width");
- test_size.y = ProjectSettings::get_singleton()->get("display/window/size/test_height");
- if (test_size.x > 0 && test_size.y > 0) {
- desired_size = test_size;
+ desired_size.x = ProjectSettings::get_singleton()->get("display/window/size/window_width_override");
+ desired_size.y = ProjectSettings::get_singleton()->get("display/window/size/window_height_override");
+ if (desired_size.x > 0 && desired_size.y > 0) {
+ window_size = desired_size;
}
int window_placement = EditorSettings::get_singleton()->get("run/window_placement/rect");
@@ -136,7 +136,7 @@ Error EditorRun::run(const String &p_scene) {
args.push_back(itos(screen_rect.position.x) + "," + itos(screen_rect.position.y));
} break;
case 1: { // centered
- Vector2 pos = (screen_rect.position) + ((screen_rect.size - desired_size) / 2).floor();
+ Vector2 pos = (screen_rect.position) + ((screen_rect.size - window_size) / 2).floor();
args.push_back("--position");
args.push_back(itos(pos.x) + "," + itos(pos.y));
} break;
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index b662eb8b1c..b9291bcd0f 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -339,7 +339,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
{
String lang_hint = "en";
String host_lang = OS::get_singleton()->get_locale();
- host_lang = TranslationServer::standardize_locale(host_lang);
// Skip locales if Text server lack required features.
Vector<String> locales_to_skip;
@@ -365,6 +364,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
}
String best;
+ int best_score = 0;
for (const String &locale : get_editor_locales()) {
// Skip locales which we can't render properly (see above comment).
// Test against language code without regional variants (e.g. ur_PK).
@@ -376,16 +376,16 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
lang_hint += ",";
lang_hint += locale;
- if (host_lang == locale) {
- best = locale;
- }
-
- if (best.is_empty() && host_lang.begins_with(locale)) {
+ int score = TranslationServer::get_singleton()->compare_locales(host_lang, locale);
+ if (score >= best_score) {
best = locale;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
}
}
-
- if (best.is_empty()) {
+ if (best_score == 0) {
best = "en";
}
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 5b7ea65b04..35aad36db5 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -1123,6 +1123,12 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("margin_bottom", "MarginContainer", 0);
theme->set_constant("hseparation", "GridContainer", default_margin_size * EDSCALE);
theme->set_constant("vseparation", "GridContainer", default_margin_size * EDSCALE);
+ theme->set_constant("hseparation", "FlowContainer", default_margin_size * EDSCALE);
+ theme->set_constant("vseparation", "FlowContainer", default_margin_size * EDSCALE);
+ theme->set_constant("hseparation", "HFlowContainer", default_margin_size * EDSCALE);
+ theme->set_constant("vseparation", "HFlowContainer", default_margin_size * EDSCALE);
+ theme->set_constant("hseparation", "VFlowContainer", default_margin_size * EDSCALE);
+ theme->set_constant("vseparation", "VFlowContainer", default_margin_size * EDSCALE);
// Window
@@ -1213,7 +1219,22 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("focus", "RichTextLabel", make_empty_stylebox());
theme->set_stylebox("normal", "RichTextLabel", style_tree_bg);
+ // Editor help.
+ theme->set_color("title_color", "EditorHelp", accent_color);
theme->set_color("headline_color", "EditorHelp", mono_color);
+ theme->set_color("text_color", "EditorHelp", font_color);
+ theme->set_color("comment_color", "EditorHelp", font_color * Color(1, 1, 1, 0.6));
+ theme->set_color("symbol_color", "EditorHelp", font_color * Color(1, 1, 1, 0.6));
+ theme->set_color("value_color", "EditorHelp", font_color * Color(1, 1, 1, 0.6));
+ theme->set_color("qualifier_color", "EditorHelp", font_color * Color(1, 1, 1, 0.8));
+ theme->set_color("type_color", "EditorHelp", accent_color.lerp(font_color, 0.5));
+ theme->set_color("selection_color", "EditorHelp", accent_color * Color(1, 1, 1, 0.4));
+ theme->set_color("link_color", "EditorHelp", accent_color.lerp(mono_color, 0.8));
+ theme->set_color("code_color", "EditorHelp", accent_color.lerp(mono_color, 0.6));
+ theme->set_color("kbd_color", "EditorHelp", accent_color.lerp(property_color, 0.6));
+ theme->set_constant("line_separation", "EditorHelp", Math::round(6 * EDSCALE));
+ theme->set_constant("table_hseparation", "EditorHelp", 16 * EDSCALE);
+ theme->set_constant("table_vseparation", "EditorHelp", 6 * EDSCALE);
// Panel
theme->set_stylebox("panel", "Panel", make_flat_stylebox(dark_color_1, 6, 4, 6, 4, corner_width));
diff --git a/editor/editor_toaster.cpp b/editor/editor_toaster.cpp
index df0588c641..6c9e4ab0fc 100644
--- a/editor/editor_toaster.cpp
+++ b/editor/editor_toaster.cpp
@@ -362,6 +362,7 @@ Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_
close_button->set_flat(true);
close_button->set_icon(get_theme_icon("Close", "EditorIcons"));
close_button->connect("pressed", callable_bind(callable_mp(this, &EditorToaster::close), panel));
+ close_button->connect("theme_changed", callable_bind(callable_mp(this, &EditorToaster::_close_button_theme_changed), close_button));
hbox_container->add_child(close_button);
}
@@ -438,6 +439,13 @@ void EditorToaster::close(Control *p_control) {
toasts[p_control].popped = false;
}
+void EditorToaster::_close_button_theme_changed(Control *p_close_button) {
+ Button *close_button = Object::cast_to<Button>(p_close_button);
+ if (close_button) {
+ close_button->set_icon(get_theme_icon("Close", "EditorIcons"));
+ }
+}
+
EditorToaster *EditorToaster::get_singleton() {
return singleton;
}
diff --git a/editor/editor_toaster.h b/editor/editor_toaster.h
index b626a47d0c..5f220e98c4 100644
--- a/editor/editor_toaster.h
+++ b/editor/editor_toaster.h
@@ -95,6 +95,7 @@ private:
void _set_notifications_enabled(bool p_enabled);
void _repop_old();
void _popup_str(String p_message, Severity p_severity, String p_tooltip);
+ void _close_button_theme_changed(Control *p_close_button);
protected:
static EditorToaster *singleton;
diff --git a/editor/icons/HFlowContainer.svg b/editor/icons/HFlowContainer.svg
new file mode 100644
index 0000000000..0ab03f686e
--- /dev/null
+++ b/editor/icons/HFlowContainer.svg
@@ -0,0 +1 @@
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm0 2h10v10H3V3zm2 1c-.554 0-1 .446-1 1s.446 1 1 1h2c.554 0 1-.446 1-1s-.446-1-1-1H5zm4.996 0c-.554 0-1 .446-1 1s.446 1 1 1H11c.554 0 1-.446 1-1s-.446-1-1-1H9.996zM5 7c-.554 0-1 .446-1 1s.446 1 1 1 1-.446 1-1-.446-1-1-1zm3 0c-.554 0-1 .446-1 1s.446 1 1 1h3c.554 0 1-.446 1-1s-.446-1-1-1H8zm-3.004 3c-.554 0-1 .446-1 1s.446 1 1 1H9c.554 0 1-.446 1-1s-.446-1-1-1H4.996z" style="fill:#8eef97;fill-opacity:1"/></svg>
diff --git a/editor/icons/Notification.svg b/editor/icons/Notification.svg
index 1f1f9c3e15..15695e22a8 100644
--- a/editor/icons/Notification.svg
+++ b/editor/icons/Notification.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m12.089506 1.2353795c-.498141-.2384823-1.095292-.027987-1.333775.4701537-.01372.028981-.02341.059557-.03428.089693-.01467.023016-.02777.046925-.04071.071459-.04899-.00527-.09728-.00862-.146087-.011473-1.4730257-.6255101-3.1777024.0153376-3.8738627 1.4563251l-1.7272425 3.6078572s-.3364181.7034345-.8079671 1.1268133c-.00105.0009371-.00239.00174-.00344.00268-.2721193.1337295-.5707545.185826-.8605632.0470816-.4981411-.2384824-1.0952924-.0279876-1.3337749.4701537-.01605.033526-.029907.066894-.041944.1011828-.018769.030371-.036749.06319-.052515.096122-.2384825.4981412-.027988 1.0952923.4701537 1.3337751l9.0196437 4.318106c.498141.238482 1.095292.02799 1.333775-.470154.01577-.03293.0301-.0675.04191-.1012.0192-.03086.0365-.06257.05255-.0961.238483-.498141.02799-1.095292-.470153-1.333775-.901965-.43181-.03834-2.235739-.03834-2.235739l1.727237-3.6078618c.715447-1.4944233.08396-3.2858776-1.410461-4.0013247.238482-.4981411.02799-1.0952926-.470154-1.333775zm-5.5145786 11.3714015c-.322341.673306-.037829 1.480435.6354753 1.802775.6733031.32234 1.4804334.03783 1.8027749-.635476z" fill="#e6e6e6"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m12.089506 1.2353795c-.498141-.2384823-1.095292-.027987-1.333775.4701537-.01372.028981-.02341.059557-.03428.089693-.01467.023016-.02777.046925-.04071.071459-.04899-.00527-.09728-.00862-.146087-.011473-1.4730257-.6255101-3.1777024.0153376-3.8738627 1.4563251l-1.7272425 3.6078572s-.3364181.7034345-.8079671 1.1268133c-.00105.0009371-.00239.00174-.00344.00268-.2721193.1337295-.5707545.185826-.8605632.0470816-.4981411-.2384824-1.0952924-.0279876-1.3337749.4701537-.01605.033526-.029907.066894-.041944.1011828-.018769.030371-.036749.06319-.052515.096122-.2384825.4981412-.027988 1.0952923.4701537 1.3337751l9.0196437 4.318106c.498141.238482 1.095292.02799 1.333775-.470154.01577-.03293.0301-.0675.04191-.1012.0192-.03086.0365-.06257.05255-.0961.238483-.498141.02799-1.095292-.470153-1.333775-.901965-.43181-.03834-2.235739-.03834-2.235739l1.727237-3.6078618c.715447-1.4944233.08396-3.2858776-1.410461-4.0013247.238482-.4981411.02799-1.0952926-.470154-1.333775zm-5.5145786 11.3714015c-.322341.673306-.037829 1.480435.6354753 1.802775.6733031.32234 1.4804334.03783 1.8027749-.635476z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/NotificationDisabled.svg b/editor/icons/NotificationDisabled.svg
index 0e4465bee8..294682a42c 100644
--- a/editor/icons/NotificationDisabled.svg
+++ b/editor/icons/NotificationDisabled.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m11.705078 1.1386719c-.389281-.0180576-.770356.1928007-.949219.5664062-.01372.028981-.024286.0597078-.035156.0898438-.01467.023016-.026122.0477316-.039062.0722656-.04899-.00527-.097678-.0088657-.146485-.0117187-1.4730253-.6255102-3.1788394.0160437-3.8749998 1.4570312l-1.7265624 3.6074219s-.3370448.7035743-.8085938 1.1269531l-.0019531.0019531c-.2721193.1337295-.5715196.1856194-.8613281.046875-.4981413-.2384824-1.0955019-.0274382-1.3339844.4707031-.01605.0335262-.0289787.0672737-.0410156.1015626-.0187691.0303709-.0369684.0627711-.0527344.0957031-.2384825.4981412-.0293917 1.0955019.46875 1.3339841l.3398437.16211 10.8984379-6.9394535c-.263272-.3070418-.592225-.5660832-.980469-.7519531.238482-.4981411.027441-1.0935489-.470703-1.3320313-.124536-.0596206-.255006-.091637-.384766-.0976562zm2.435547 2.8652343a.94188849 1 0 0 0 -.566406.1386719l-12.1171878 7.7148439a.94188849 1 0 0 0 -.3222656 1.373047.94188849 1 0 0 0 1.2910156.341797l12.1171878-7.7148441a.94188849 1 0 0 0 .322265-1.3710938.94188849 1 0 0 0 -.724609-.4824219zm-.509766 3.2753907-7.3867184 4.7050781 5.0781254 2.431641c.498141.238482 1.095501.027442 1.333984-.470704.01577-.03293.031159-.067862.042969-.101562.0192-.03086.036684-.062173.052734-.095703.238483-.498141.02744-1.095501-.470703-1.333985-.901965-.431809-.039063-2.236328-.039062-2.236328zm-7.0566402 5.3281251c-.322341.673306-.0365856 1.480394.6367187 1.802734.6733031.32234 1.4803929.036588 1.8027344-.636718z" fill="#e6e6e6"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m11.705078 1.1386719c-.389281-.0180576-.770356.1928007-.949219.5664062-.01372.028981-.024286.0597078-.035156.0898438-.01467.023016-.026122.0477316-.039062.0722656-.04899-.00527-.097678-.0088657-.146485-.0117187-1.4730253-.6255102-3.1788394.0160437-3.8749998 1.4570312l-1.7265624 3.6074219s-.3370448.7035743-.8085938 1.1269531l-.0019531.0019531c-.2721193.1337295-.5715196.1856194-.8613281.046875-.4981413-.2384824-1.0955019-.0274382-1.3339844.4707031-.01605.0335262-.0289787.0672737-.0410156.1015626-.0187691.0303709-.0369684.0627711-.0527344.0957031-.2384825.4981412-.0293917 1.0955019.46875 1.3339841l.3398437.16211 10.8984379-6.9394535c-.263272-.3070418-.592225-.5660832-.980469-.7519531.238482-.4981411.027441-1.0935489-.470703-1.3320313-.124536-.0596206-.255006-.091637-.384766-.0976562zm2.435547 2.8652343a.94188849 1 0 0 0 -.566406.1386719l-12.1171878 7.7148439a.94188849 1 0 0 0 -.3222656 1.373047.94188849 1 0 0 0 1.2910156.341797l12.1171878-7.7148441a.94188849 1 0 0 0 .322265-1.3710938.94188849 1 0 0 0 -.724609-.4824219zm-.509766 3.2753907-7.3867184 4.7050781 5.0781254 2.431641c.498141.238482 1.095501.027442 1.333984-.470704.01577-.03293.031159-.067862.042969-.101562.0192-.03086.036684-.062173.052734-.095703.238483-.498141.02744-1.095501-.470703-1.333985-.901965-.431809-.039063-2.236328-.039062-2.236328zm-7.0566402 5.3281251c-.322341.673306-.0365856 1.480394.6367187 1.802734.6733031.32234 1.4803929.036588 1.8027344-.636718z" fill="#e0e0e0"/></svg>
diff --git a/editor/icons/VFlowContainer.svg b/editor/icons/VFlowContainer.svg
new file mode 100644
index 0000000000..9023bf2245
--- /dev/null
+++ b/editor/icons/VFlowContainer.svg
@@ -0,0 +1 @@
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm0 2h10v10H3V3zm7.998.998c-.554 0-1 .446-1 1v4.004c0 .554.446 1 1 1s1-.446 1-1V4.998c0-.554-.446-1-1-1zm-6 .004c-.554 0-1 .446-1 1v2c0 .554.446 1 1 1s1-.446 1-1v-2c0-.554-.446-1-1-1zm3 0c-.554 0-1 .446-1 1s.446 1 1 1 1-.446 1-1-.446-1-1-1zm0 3c-.554 0-1 .446-1 1v3c0 .554.446 1 1 1s1-.446 1-1v-3c0-.554-.446-1-1-1zm-3 1.996c-.554 0-1 .446-1 1v1.004c0 .554.446 1 1 1s1-.446 1-1V9.998c0-.554-.446-1-1-1z" style="fill:#8eef97;fill-opacity:1"/></svg>
diff --git a/editor/import/dynamicfont_import_settings.cpp b/editor/import/dynamicfont_import_settings.cpp
index 3151496bec..f4b1468314 100644
--- a/editor/import/dynamicfont_import_settings.cpp
+++ b/editor/import/dynamicfont_import_settings.cpp
@@ -442,398 +442,6 @@ void DynamicFontImportSettings::_add_glyph_range_item(int32_t p_start, int32_t p
}
/*************************************************************************/
-/* Languages and scripts */
-/*************************************************************************/
-
-struct CodeInfo {
- String name;
- String code;
-};
-
-static CodeInfo langs[] = {
- { U"Custom", U"xx" },
- { U"-", U"-" },
- { U"Abkhazian", U"ab" },
- { U"Afar", U"aa" },
- { U"Afrikaans", U"af" },
- { U"Akan", U"ak" },
- { U"Albanian", U"sq" },
- { U"Amharic", U"am" },
- { U"Arabic", U"ar" },
- { U"Aragonese", U"an" },
- { U"Armenian", U"hy" },
- { U"Assamese", U"as" },
- { U"Avaric", U"av" },
- { U"Avestan", U"ae" },
- { U"Aymara", U"ay" },
- { U"Azerbaijani", U"az" },
- { U"Bambara", U"bm" },
- { U"Bashkir", U"ba" },
- { U"Basque", U"eu" },
- { U"Belarusian", U"be" },
- { U"Bengali", U"bn" },
- { U"Bihari", U"bh" },
- { U"Bislama", U"bi" },
- { U"Bosnian", U"bs" },
- { U"Breton", U"br" },
- { U"Bulgarian", U"bg" },
- { U"Burmese", U"my" },
- { U"Catalan", U"ca" },
- { U"Chamorro", U"ch" },
- { U"Chechen", U"ce" },
- { U"Chichewa", U"ny" },
- { U"Chinese", U"zh" },
- { U"Chuvash", U"cv" },
- { U"Cornish", U"kw" },
- { U"Corsican", U"co" },
- { U"Cree", U"cr" },
- { U"Croatian", U"hr" },
- { U"Czech", U"cs" },
- { U"Danish", U"da" },
- { U"Divehi", U"dv" },
- { U"Dutch", U"nl" },
- { U"Dzongkha", U"dz" },
- { U"English", U"en" },
- { U"Esperanto", U"eo" },
- { U"Estonian", U"et" },
- { U"Ewe", U"ee" },
- { U"Faroese", U"fo" },
- { U"Fijian", U"fj" },
- { U"Finnish", U"fi" },
- { U"French", U"fr" },
- { U"Fulah", U"ff" },
- { U"Galician", U"gl" },
- { U"Georgian", U"ka" },
- { U"German", U"de" },
- { U"Greek", U"el" },
- { U"Guarani", U"gn" },
- { U"Gujarati", U"gu" },
- { U"Haitian", U"ht" },
- { U"Hausa", U"ha" },
- { U"Hebrew", U"he" },
- { U"Herero", U"hz" },
- { U"Hindi", U"hi" },
- { U"Hiri Motu", U"ho" },
- { U"Hungarian", U"hu" },
- { U"Interlingua", U"ia" },
- { U"Indonesian", U"id" },
- { U"Interlingue", U"ie" },
- { U"Irish", U"ga" },
- { U"Igbo", U"ig" },
- { U"Inupiaq", U"ik" },
- { U"Ido", U"io" },
- { U"Icelandic", U"is" },
- { U"Italian", U"it" },
- { U"Inuktitut", U"iu" },
- { U"Japanese", U"ja" },
- { U"Javanese", U"jv" },
- { U"Kalaallisut", U"kl" },
- { U"Kannada", U"kn" },
- { U"Kanuri", U"kr" },
- { U"Kashmiri", U"ks" },
- { U"Kazakh", U"kk" },
- { U"Central Khmer", U"km" },
- { U"Kikuyu", U"ki" },
- { U"Kinyarwanda", U"rw" },
- { U"Kirghiz", U"ky" },
- { U"Komi", U"kv" },
- { U"Kongo", U"kg" },
- { U"Korean", U"ko" },
- { U"Kurdish", U"ku" },
- { U"Kuanyama", U"kj" },
- { U"Latin", U"la" },
- { U"Luxembourgish", U"lb" },
- { U"Ganda", U"lg" },
- { U"Limburgan", U"li" },
- { U"Lingala", U"ln" },
- { U"Lao", U"lo" },
- { U"Lithuanian", U"lt" },
- { U"Luba-Katanga", U"lu" },
- { U"Latvian", U"lv" },
- { U"Man", U"gv" },
- { U"Macedonian", U"mk" },
- { U"Malagasy", U"mg" },
- { U"Malay", U"ms" },
- { U"Malayalam", U"ml" },
- { U"Maltese", U"mt" },
- { U"Maori", U"mi" },
- { U"Marathi", U"mr" },
- { U"Marshallese", U"mh" },
- { U"Mongolian", U"mn" },
- { U"Nauru", U"na" },
- { U"Navajo", U"nv" },
- { U"North Ndebele", U"nd" },
- { U"Nepali", U"ne" },
- { U"Ndonga", U"ng" },
- { U"Norwegian Bokmål", U"nb" },
- { U"Norwegian Nynorsk", U"nn" },
- { U"Norwegian", U"no" },
- { U"Sichuan Yi, Nuosu", U"ii" },
- { U"South Ndebele", U"nr" },
- { U"Occitan", U"oc" },
- { U"Ojibwa", U"oj" },
- { U"Church Slavic", U"cu" },
- { U"Oromo", U"om" },
- { U"Oriya", U"or" },
- { U"Ossetian", U"os" },
- { U"Punjabi", U"pa" },
- { U"Pali", U"pi" },
- { U"Persian", U"fa" },
- { U"Polish", U"pl" },
- { U"Pashto", U"ps" },
- { U"Portuguese", U"pt" },
- { U"Quechua", U"qu" },
- { U"Romansh", U"rm" },
- { U"Rundi", U"rn" },
- { U"Romanian", U"ro" },
- { U"Russian", U"ru" },
- { U"Sanskrit", U"sa" },
- { U"Sardinian", U"sc" },
- { U"Sindhi", U"sd" },
- { U"Northern Sami", U"se" },
- { U"Samoan", U"sm" },
- { U"Sango", U"sg" },
- { U"Serbian", U"sr" },
- { U"Gaelic", U"gd" },
- { U"Shona", U"sn" },
- { U"Sinhala", U"si" },
- { U"Slovak", U"sk" },
- { U"Slovenian", U"sl" },
- { U"Somali", U"so" },
- { U"Southern Sotho", U"st" },
- { U"Spanish", U"es" },
- { U"Sundanese", U"su" },
- { U"Swahili", U"sw" },
- { U"Swati", U"ss" },
- { U"Swedish", U"sv" },
- { U"Tamil", U"ta" },
- { U"Telugu", U"te" },
- { U"Tajik", U"tg" },
- { U"Thai", U"th" },
- { U"Tigrinya", U"ti" },
- { U"Tibetan", U"bo" },
- { U"Turkmen", U"tk" },
- { U"Tagalog", U"tl" },
- { U"Tswana", U"tn" },
- { U"Tonga", U"to" },
- { U"Turkish", U"tr" },
- { U"Tsonga", U"ts" },
- { U"Tatar", U"tt" },
- { U"Twi", U"tw" },
- { U"Tahitian", U"ty" },
- { U"Uighur", U"ug" },
- { U"Ukrainian", U"uk" },
- { U"Urdu", U"ur" },
- { U"Uzbek", U"uz" },
- { U"Venda", U"ve" },
- { U"Vietnamese", U"vi" },
- { U"Volapük", U"vo" },
- { U"Walloon", U"wa" },
- { U"Welsh", U"cy" },
- { U"Wolof", U"wo" },
- { U"Western Frisian", U"fy" },
- { U"Xhosa", U"xh" },
- { U"Yiddish", U"yi" },
- { U"Yoruba", U"yo" },
- { U"Zhuang", U"za" },
- { U"Zulu", U"zu" },
- { String(), String() }
-};
-
-static CodeInfo scripts[] = {
- { U"Custom", U"Qaaa" },
- { U"-", U"-" },
- { U"Adlam", U"Adlm" },
- { U"Afaka", U"Afak" },
- { U"Caucasian Albanian", U"Aghb" },
- { U"Ahom", U"Ahom" },
- { U"Arabic", U"Arab" },
- { U"Imperial Aramaic", U"Armi" },
- { U"Armenian", U"Armn" },
- { U"Avestan", U"Avst" },
- { U"Balinese", U"Bali" },
- { U"Bamum", U"Bamu" },
- { U"Bassa Vah", U"Bass" },
- { U"Batak", U"Batk" },
- { U"Bengali", U"Beng" },
- { U"Bhaiksuki", U"Bhks" },
- { U"Blissymbols", U"Blis" },
- { U"Bopomofo", U"Bopo" },
- { U"Brahmi", U"Brah" },
- { U"Braille", U"Brai" },
- { U"Buginese", U"Bugi" },
- { U"Buhid", U"Buhd" },
- { U"Chakma", U"Cakm" },
- { U"Unified Canadian Aboriginal", U"Cans" },
- { U"Carian", U"Cari" },
- { U"Cham", U"Cham" },
- { U"Cherokee", U"Cher" },
- { U"Chorasmian", U"Chrs" },
- { U"Cirth", U"Cirt" },
- { U"Coptic", U"Copt" },
- { U"Cypro-Minoan", U"Cpmn" },
- { U"Cypriot", U"Cprt" },
- { U"Cyrillic", U"Cyrl" },
- { U"Devanagari", U"Deva" },
- { U"Dives Akuru", U"Diak" },
- { U"Dogra", U"Dogr" },
- { U"Deseret", U"Dsrt" },
- { U"Duployan", U"Dupl" },
- { U"Egyptian demotic", U"Egyd" },
- { U"Egyptian hieratic", U"Egyh" },
- { U"Egyptian hieroglyphs", U"Egyp" },
- { U"Elbasan", U"Elba" },
- { U"Elymaic", U"Elym" },
- { U"Ethiopic", U"Ethi" },
- { U"Khutsuri", U"Geok" },
- { U"Georgian", U"Geor" },
- { U"Glagolitic", U"Glag" },
- { U"Gunjala Gondi", U"Gong" },
- { U"Masaram Gondi", U"Gonm" },
- { U"Gothic", U"Goth" },
- { U"Grantha", U"Gran" },
- { U"Greek", U"Grek" },
- { U"Gujarati", U"Gujr" },
- { U"Gurmukhi", U"Guru" },
- { U"Hangul", U"Hang" },
- { U"Han", U"Hani" },
- { U"Hanunoo", U"Hano" },
- { U"Hatran", U"Hatr" },
- { U"Hebrew", U"Hebr" },
- { U"Hiragana", U"Hira" },
- { U"Anatolian Hieroglyphs", U"Hluw" },
- { U"Pahawh Hmong", U"Hmng" },
- { U"Nyiakeng Puachue Hmong", U"Hmnp" },
- { U"Old Hungarian", U"Hung" },
- { U"Indus", U"Inds" },
- { U"Old Italic", U"Ital" },
- { U"Javanese", U"Java" },
- { U"Jurchen", U"Jurc" },
- { U"Kayah Li", U"Kali" },
- { U"Katakana", U"Kana" },
- { U"Kharoshthi", U"Khar" },
- { U"Khmer", U"Khmr" },
- { U"Khojki", U"Khoj" },
- { U"Khitan large script", U"Kitl" },
- { U"Khitan small script", U"Kits" },
- { U"Kannada", U"Knda" },
- { U"Kpelle", U"Kpel" },
- { U"Kaithi", U"Kthi" },
- { U"Tai Tham", U"Lana" },
- { U"Lao", U"Laoo" },
- { U"Latin", U"Latn" },
- { U"Leke", U"Leke" },
- { U"Lepcha", U"Lepc" },
- { U"Limbu", U"Limb" },
- { U"Linear A", U"Lina" },
- { U"Linear B", U"Linb" },
- { U"Lisu", U"Lisu" },
- { U"Loma", U"Loma" },
- { U"Lycian", U"Lyci" },
- { U"Lydian", U"Lydi" },
- { U"Mahajani", U"Mahj" },
- { U"Makasar", U"Maka" },
- { U"Mandaic", U"Mand" },
- { U"Manichaean", U"Mani" },
- { U"Marchen", U"Marc" },
- { U"Mayan Hieroglyphs", U"Maya" },
- { U"Medefaidrin", U"Medf" },
- { U"Mende Kikakui", U"Mend" },
- { U"Meroitic Cursive", U"Merc" },
- { U"Meroitic Hieroglyphs", U"Mero" },
- { U"Malayalam", U"Mlym" },
- { U"Modi", U"Modi" },
- { U"Mongolian", U"Mong" },
- { U"Moon", U"Moon" },
- { U"Mro", U"Mroo" },
- { U"Meitei Mayek", U"Mtei" },
- { U"Multani", U"Mult" },
- { U"Myanmar (Burmese)", U"Mymr" },
- { U"Nandinagari", U"Nand" },
- { U"Old North Arabian", U"Narb" },
- { U"Nabataean", U"Nbat" },
- { U"Newa", U"Newa" },
- { U"Naxi Dongba", U"Nkdb" },
- { U"Nakhi Geba", U"Nkgb" },
- { U"N’Ko", U"Nkoo" },
- { U"Nüshu", U"Nshu" },
- { U"Ogham", U"Ogam" },
- { U"Ol Chiki", U"Olck" },
- { U"Old Turkic", U"Orkh" },
- { U"Oriya", U"Orya" },
- { U"Osage", U"Osge" },
- { U"Osmanya", U"Osma" },
- { U"Old Uyghur", U"Ougr" },
- { U"Palmyrene", U"Palm" },
- { U"Pau Cin Hau", U"Pauc" },
- { U"Proto-Cuneiform", U"Pcun" },
- { U"Proto-Elamite", U"Pelm" },
- { U"Old Permic", U"Perm" },
- { U"Phags-pa", U"Phag" },
- { U"Inscriptional Pahlavi", U"Phli" },
- { U"Psalter Pahlavi", U"Phlp" },
- { U"Book Pahlavi", U"Phlv" },
- { U"Phoenician", U"Phnx" },
- { U"Klingon", U"Piqd" },
- { U"Miao", U"Plrd" },
- { U"Inscriptional Parthian", U"Prti" },
- { U"Proto-Sinaitic", U"Psin" },
- { U"Ranjana", U"Ranj" },
- { U"Rejang", U"Rjng" },
- { U"Hanifi Rohingya", U"Rohg" },
- { U"Rongorongo", U"Roro" },
- { U"Runic", U"Runr" },
- { U"Samaritan", U"Samr" },
- { U"Sarati", U"Sara" },
- { U"Old South Arabian", U"Sarb" },
- { U"Saurashtra", U"Saur" },
- { U"SignWriting", U"Sgnw" },
- { U"Shavian", U"Shaw" },
- { U"Sharada", U"Shrd" },
- { U"Shuishu", U"Shui" },
- { U"Siddham", U"Sidd" },
- { U"Khudawadi", U"Sind" },
- { U"Sinhala", U"Sinh" },
- { U"Sogdian", U"Sogd" },
- { U"Old Sogdian", U"Sogo" },
- { U"Sora Sompeng", U"Sora" },
- { U"Soyombo", U"Soyo" },
- { U"Sundanese", U"Sund" },
- { U"Syloti Nagri", U"Sylo" },
- { U"Syriac", U"Syrc" },
- { U"Tagbanwa", U"Tagb" },
- { U"Takri", U"Takr" },
- { U"Tai Le", U"Tale" },
- { U"New Tai Lue", U"Talu" },
- { U"Tamil", U"Taml" },
- { U"Tangut", U"Tang" },
- { U"Tai Viet", U"Tavt" },
- { U"Telugu", U"Telu" },
- { U"Tengwar", U"Teng" },
- { U"Tifinagh", U"Tfng" },
- { U"Tagalog", U"Tglg" },
- { U"Thaana", U"Thaa" },
- { U"Thai", U"Thai" },
- { U"Tibetan", U"Tibt" },
- { U"Tirhuta", U"Tirh" },
- { U"Tangsa", U"Tnsa" },
- { U"Toto", U"Toto" },
- { U"Ugaritic", U"Ugar" },
- { U"Vai", U"Vaii" },
- { U"Visible Speech", U"Visp" },
- { U"Vithkuqi", U"Vith" },
- { U"Warang Citi", U"Wara" },
- { U"Wancho", U"Wcho" },
- { U"Woleai", U"Wole" },
- { U"Old Persian", U"Xpeo" },
- { U"Cuneiform", U"Xsux" },
- { U"Yezidi", U"Yezi" },
- { U"Yi", U"Yiii" },
- { U"Zanabazar Square", U"Zanb" },
- { String(), String() }
-};
-
-/*************************************************************************/
/* Page 1 callbacks: Rendering Options */
/*************************************************************************/
@@ -1159,19 +767,17 @@ void DynamicFontImportSettings::_range_update(int32_t p_start, int32_t p_end) {
/*************************************************************************/
void DynamicFontImportSettings::_lang_add() {
- menu_langs->set_position(lang_list->get_screen_position() + lang_list->get_local_mouse_position());
- menu_langs->reset_size();
- menu_langs->popup();
+ locale_select->popup_locale_dialog();
}
-void DynamicFontImportSettings::_lang_add_item(int p_option) {
+void DynamicFontImportSettings::_lang_add_item(const String &p_locale) {
TreeItem *lang_item = lang_list->create_item(lang_list_root);
ERR_FAIL_NULL(lang_item);
lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
lang_item->set_editable(0, true);
lang_item->set_checked(0, false);
- lang_item->set_text(1, langs[p_option].code);
+ lang_item->set_text(1, p_locale);
lang_item->set_editable(1, true);
lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
lang_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
@@ -1230,7 +836,7 @@ void DynamicFontImportSettings::_script_add_item(int p_option) {
script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
script_item->set_editable(0, true);
script_item->set_checked(0, false);
- script_item->set_text(1, scripts[p_option].code);
+ script_item->set_text(1, script_codes[p_option]);
script_item->set_editable(1, true);
script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
script_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
@@ -1688,26 +1294,15 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
// Popup menus
- menu_langs = memnew(PopupMenu);
- menu_langs->set_name("Language");
- for (int i = 0; !langs[i].name.is_empty(); i++) {
- if (langs[i].name == "-") {
- menu_langs->add_separator();
- } else {
- menu_langs->add_item(langs[i].name + " (" + langs[i].code + ")", i);
- }
- }
- add_child(menu_langs);
- menu_langs->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_lang_add_item));
+ locale_select = memnew(EditorLocaleDialog);
+ locale_select->connect("locale_selected", callable_mp(this, &DynamicFontImportSettings::_lang_add_item));
+ add_child(locale_select);
menu_scripts = memnew(PopupMenu);
menu_scripts->set_name("Script");
- for (int i = 0; !scripts[i].name.is_empty(); i++) {
- if (scripts[i].name == "-") {
- menu_scripts->add_separator();
- } else {
- menu_scripts->add_item(scripts[i].name + " (" + scripts[i].code + ")", i);
- }
+ script_codes = TranslationServer::get_singleton()->get_all_scripts();
+ for (int i = 0; i < script_codes.size(); i++) {
+ menu_scripts->add_item(TranslationServer::get_singleton()->get_script_name(script_codes[i]) + " (" + script_codes[i] + ")", i);
}
add_child(menu_scripts);
menu_scripts->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_script_add_item));
diff --git a/editor/import/dynamicfont_import_settings.h b/editor/import/dynamicfont_import_settings.h
index 89665ae476..5d37f58b9b 100644
--- a/editor/import/dynamicfont_import_settings.h
+++ b/editor/import/dynamicfont_import_settings.h
@@ -33,6 +33,7 @@
#include "editor/editor_file_dialog.h"
#include "editor/editor_inspector.h"
+#include "editor/editor_locale_dialog.h"
#include "editor/import/resource_importer_dynamicfont.h"
@@ -67,6 +68,9 @@ class DynamicFontImportSettings : public ConfirmationDialog {
List<ResourceImporter::ImportOption> options_variations;
List<ResourceImporter::ImportOption> options_general;
+ EditorLocaleDialog *locale_select;
+ Vector<String> script_codes;
+
// Root layout
Label *label_warn = nullptr;
TabContainer *main_pages = nullptr;
@@ -122,7 +126,6 @@ class DynamicFontImportSettings : public ConfirmationDialog {
Button *add_script = nullptr;
Button *add_ot = nullptr;
- PopupMenu *menu_langs = nullptr;
PopupMenu *menu_scripts = nullptr;
PopupMenu *menu_ot = nullptr;
PopupMenu *menu_ot_ss = nullptr;
@@ -142,7 +145,7 @@ class DynamicFontImportSettings : public ConfirmationDialog {
Label *label_ot = nullptr;
void _lang_add();
- void _lang_add_item(int p_option);
+ void _lang_add_item(const String &p_locale);
void _lang_remove(Object *p_item, int p_column, int p_id);
void _script_add();
diff --git a/editor/import/resource_importer_csv_translation.cpp b/editor/import/resource_importer_csv_translation.cpp
index 448b318c64..f0ee14bdcb 100644
--- a/editor/import/resource_importer_csv_translation.cpp
+++ b/editor/import/resource_importer_csv_translation.cpp
@@ -99,8 +99,7 @@ Error ResourceImporterCSVTranslation::import(const String &p_source_file, const
Vector<Ref<Translation>> translations;
for (int i = 1; i < line.size(); i++) {
- String locale = line[i];
- ERR_FAIL_COND_V_MSG(!TranslationServer::is_locale_valid(locale), ERR_PARSE_ERROR, "Error importing CSV translation: '" + locale + "' is not a valid locale.");
+ String locale = TranslationServer::get_singleton()->standardize_locale(line[i]);
locales.push_back(locale);
Ref<Translation> translation;
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index e801cd4553..b6624a8cfa 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -370,16 +370,17 @@ static void _pre_gen_shape_list(Ref<ImporterMesh> &mesh, Vector<Ref<Shape3D>> &r
}
}
-Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map) {
- // children first
+Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, List<Pair<NodePath, Node *>> &r_node_renames) {
+ // Children first.
for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map);
+ Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map, r_node_renames);
if (!r) {
- i--; //was erased
+ i--; // Was erased.
}
}
String name = p_node->get_name();
+ NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints.
bool isroot = p_node == p_root;
@@ -414,14 +415,21 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
}
if (Object::cast_to<AnimationPlayer>(p_node)) {
- //remove animations referencing non-importable nodes
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
+ // Node paths in animation tracks are relative to the following path (this is used to fix node paths below).
+ Node *ap_root = ap->get_node(ap->get_root());
+ NodePath path_prefix = p_root->get_path_to(ap_root);
+
+ bool nodes_were_renamed = r_node_renames.size() != 0;
+
List<StringName> anims;
ap->get_animation_list(&anims);
for (const StringName &E : anims) {
Ref<Animation> anim = ap->get_animation(E);
ERR_CONTINUE(anim.is_null());
+
+ // Remove animation tracks referencing non-importable nodes.
for (int i = 0; i < anim->get_track_count(); i++) {
NodePath path = anim->track_get_path(i);
@@ -435,6 +443,27 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
}
}
+ // Fix node paths in animations, in case nodes were renamed earlier due to import hints.
+ if (nodes_were_renamed) {
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ NodePath path = anim->track_get_path(i);
+ // Convert track path to absolute node path without subnames (some manual work because we are not in the scene tree).
+ Vector<StringName> absolute_path_names = path_prefix.get_names();
+ absolute_path_names.append_array(path.get_names());
+ NodePath absolute_path(absolute_path_names, false);
+ absolute_path.simplify();
+ // Fix paths to renamed nodes.
+ for (const Pair<NodePath, Node *> &F : r_node_renames) {
+ if (F.first == absolute_path) {
+ NodePath new_path(ap_root->get_path_to(F.second).get_names(), path.get_subnames(), false);
+ print_verbose(vformat("Fix: Correcting node path in animation track: %s should be %s", path, new_path));
+ anim->track_set_path(i, new_path);
+ break; // Only one match is possible.
+ }
+ }
+ }
+ }
+
String animname = E;
const int loop_string_count = 3;
static const char *loop_strings[loop_string_count] = { "loop_mode", "loop", "cycle" };
@@ -452,13 +481,22 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
if (isroot) {
return p_node;
}
+
+ String fixed_name;
+ if (_teststr(name, "colonly")) {
+ fixed_name = _fixstr(name, "colonly");
+ } else if (_teststr(name, "convcolonly")) {
+ fixed_name = _fixstr(name, "convcolonly");
+ }
+
+ ERR_FAIL_COND_V(fixed_name.is_empty(), nullptr);
+
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node);
if (mi) {
Ref<ImporterMesh> mesh = mi->get_mesh();
if (mesh.is_valid()) {
Vector<Ref<Shape3D>> shapes;
- String fixed_name;
if (collision_map.has(mesh)) {
shapes = collision_map[mesh];
} else if (_teststr(name, "colonly")) {
@@ -469,14 +507,6 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
collision_map[mesh] = shapes;
}
- if (_teststr(name, "colonly")) {
- fixed_name = _fixstr(name, "colonly");
- } else if (_teststr(name, "convcolonly")) {
- fixed_name = _fixstr(name, "convcolonly");
- }
-
- ERR_FAIL_COND_V(fixed_name.is_empty(), nullptr);
-
if (shapes.size()) {
StaticBody3D *col = memnew(StaticBody3D);
col->set_transform(mi->get_transform());
@@ -492,11 +522,11 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
} else if (p_node->has_meta("empty_draw_type")) {
String empty_draw_type = String(p_node->get_meta("empty_draw_type"));
StaticBody3D *sb = memnew(StaticBody3D);
- sb->set_name(_fixstr(name, "colonly"));
+ sb->set_name(fixed_name);
Object::cast_to<Node3D>(sb)->set_transform(Object::cast_to<Node3D>(p_node)->get_transform());
p_node->replace_by(sb);
memdelete(p_node);
- p_node = nullptr;
+ p_node = sb;
CollisionShape3D *colshape = memnew(CollisionShape3D);
if (empty_draw_type == "CUBE") {
BoxShape3D *boxShape = memnew(BoxShape3D);
@@ -635,6 +665,14 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
}
}
+ if (p_node) {
+ NodePath new_path = p_root->get_path_to(p_node);
+ if (new_path != original_path) {
+ print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path));
+ r_node_renames.push_back({ original_path, p_node });
+ }
+ }
+
return p_node;
}
@@ -1828,8 +1866,8 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) {
}
Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
-
- _pre_fix_node(scene, scene, collision_map);
+ List<Pair<NodePath, Node *>> node_renames;
+ _pre_fix_node(scene, scene, collision_map, node_renames);
return scene;
}
@@ -1904,8 +1942,9 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
Set<Ref<ImporterMesh>> scanned_meshes;
Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
+ List<Pair<NodePath, Node *>> node_renames;
- _pre_fix_node(scene, scene, collision_map);
+ _pre_fix_node(scene, scene, collision_map, node_renames);
for (int i = 0; i < post_importer_plugins.size(); i++) {
post_importer_plugins.write[i]->pre_process(scene, p_options);
diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h
index 00d095eac1..066e8b603b 100644
--- a/editor/import/resource_importer_scene.h
+++ b/editor/import/resource_importer_scene.h
@@ -261,7 +261,7 @@ public:
// Import scenes *after* everything else (such as textures).
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
- Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map);
+ Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, List<Pair<NodePath, Node *>> &r_node_renames);
Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
diff --git a/editor/localization_editor.cpp b/editor/localization_editor.cpp
index a902b070f4..1e9e2fc09b 100644
--- a/editor/localization_editor.cpp
+++ b/editor/localization_editor.cpp
@@ -32,6 +32,7 @@
#include "core/string/translation.h"
#include "editor_node.h"
+#include "editor_scale.h"
#include "editor_translation_parser.h"
#include "pot_generator.h"
#include "scene/gui/control.h"
@@ -175,10 +176,27 @@ void LocalizationEditor::_translation_res_select() {
if (updating_translations) {
return;
}
-
call_deferred(SNAME("update_translations"));
}
+void LocalizationEditor::_translation_res_option_popup(bool p_arrow_clicked) {
+ TreeItem *ed = translation_remap_options->get_edited();
+ ERR_FAIL_COND(!ed);
+
+ locale_select->set_locale(ed->get_tooltip(1));
+ locale_select->popup_locale_dialog();
+}
+
+void LocalizationEditor::_translation_res_option_selected(const String &p_locale) {
+ TreeItem *ed = translation_remap_options->get_edited();
+ ERR_FAIL_COND(!ed);
+
+ ed->set_text(1, TranslationServer::get_singleton()->get_locale_name(p_locale));
+ ed->set_tooltip(1, p_locale);
+
+ LocalizationEditor::_translation_res_option_changed();
+}
+
void LocalizationEditor::_translation_res_option_changed() {
if (updating_translations) {
return;
@@ -198,20 +216,11 @@ void LocalizationEditor::_translation_res_option_changed() {
String key = k->get_metadata(0);
int idx = ed->get_metadata(0);
String path = ed->get_metadata(1);
- int which = ed->get_range(1);
-
- Vector<String> langs = TranslationServer::get_all_locales();
-
- ERR_FAIL_INDEX(which, langs.size());
+ String locale = ed->get_tooltip(1);
ERR_FAIL_COND(!remaps.has(key));
PackedStringArray r = remaps[key];
- ERR_FAIL_INDEX(idx, r.size());
- if (translation_locales_idxs_remap.size() > which) {
- r.set(idx, path + ":" + langs[translation_locales_idxs_remap[which]]);
- } else {
- r.set(idx, path + ":" + langs[which]);
- }
+ r.set(idx, path + ":" + locale);
remaps[key] = r;
updating_translations = true;
@@ -289,86 +298,6 @@ void LocalizationEditor::_translation_res_option_delete(Object *p_item, int p_co
undo_redo->commit_action();
}
-void LocalizationEditor::_translation_filter_option_changed() {
- int sel_id = translation_locale_filter_mode->get_selected_id();
- TreeItem *t = translation_filter->get_edited();
- String locale = t->get_tooltip(0);
- bool checked = t->is_checked(0);
-
- Variant prev;
- Array f_locales_all;
-
- if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter")) {
- f_locales_all = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter");
- prev = f_locales_all;
-
- if (f_locales_all.size() != 2) {
- f_locales_all.clear();
- f_locales_all.append(sel_id);
- f_locales_all.append(Array());
- }
- } else {
- f_locales_all.append(sel_id);
- f_locales_all.append(Array());
- }
-
- Array f_locales = f_locales_all[1];
- int l_idx = f_locales.find(locale);
-
- if (checked) {
- if (l_idx == -1) {
- f_locales.append(locale);
- }
- } else {
- if (l_idx != -1) {
- f_locales.remove_at(l_idx);
- }
- }
-
- f_locales.sort();
-
- undo_redo->create_action(TTR("Changed Locale Filter"));
- undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter", f_locales_all);
- undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter", prev);
- undo_redo->add_do_method(this, "update_translations");
- undo_redo->add_undo_method(this, "update_translations");
- undo_redo->add_do_method(this, "emit_signal", localization_changed);
- undo_redo->add_undo_method(this, "emit_signal", localization_changed);
- undo_redo->commit_action();
-}
-
-void LocalizationEditor::_translation_filter_mode_changed(int p_mode) {
- int sel_id = translation_locale_filter_mode->get_selected_id();
-
- Variant prev;
- Array f_locales_all;
-
- if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter")) {
- f_locales_all = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter");
- prev = f_locales_all;
-
- if (f_locales_all.size() != 2) {
- f_locales_all.clear();
- f_locales_all.append(sel_id);
- f_locales_all.append(Array());
- } else {
- f_locales_all[0] = sel_id;
- }
- } else {
- f_locales_all.append(sel_id);
- f_locales_all.append(Array());
- }
-
- undo_redo->create_action(TTR("Changed Locale Filter Mode"));
- undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter", f_locales_all);
- undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter", prev);
- undo_redo->add_do_method(this, "update_translations");
- undo_redo->add_undo_method(this, "update_translations");
- undo_redo->add_do_method(this, "emit_signal", localization_changed);
- undo_redo->add_undo_method(this, "emit_signal", localization_changed);
- undo_redo->commit_action();
-}
-
void LocalizationEditor::_pot_add(const PackedStringArray &p_paths) {
PackedStringArray pot_translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations_pot_files");
for (int i = 0; i < p_paths.size(); i++) {
@@ -452,64 +381,6 @@ void LocalizationEditor::update_translations() {
}
}
- Vector<String> langs = TranslationServer::get_all_locales();
- Vector<String> names = TranslationServer::get_all_locale_names();
-
- // Update filter tab
- Array l_filter_all;
-
- bool is_arr_empty = true;
- if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter")) {
- l_filter_all = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter");
-
- if (l_filter_all.size() == 2) {
- translation_locale_filter_mode->select(l_filter_all[0]);
- is_arr_empty = false;
- }
- }
- if (is_arr_empty) {
- l_filter_all.append(0);
- l_filter_all.append(Array());
- translation_locale_filter_mode->select(0);
- }
-
- int filter_mode = l_filter_all[0];
- Array l_filter = l_filter_all[1];
-
- int s = names.size();
- bool is_short_list_when_show_all_selected = filter_mode == SHOW_ALL_LOCALES && translation_filter_treeitems.size() < s;
- bool is_full_list_when_show_only_selected = filter_mode == SHOW_ONLY_SELECTED_LOCALES && translation_filter_treeitems.size() == s;
- bool should_recreate_locales_list = is_short_list_when_show_all_selected || is_full_list_when_show_only_selected;
-
- if (!translation_locales_list_created || should_recreate_locales_list) {
- translation_locales_list_created = true;
- translation_filter->clear();
- root = translation_filter->create_item(nullptr);
- translation_filter->set_hide_root(true);
- translation_filter_treeitems.clear();
- for (int i = 0; i < s; i++) {
- String n = names[i];
- String l = langs[i];
- bool is_checked = l_filter.has(l);
- if (filter_mode == SHOW_ONLY_SELECTED_LOCALES && !is_checked) {
- continue;
- }
-
- TreeItem *t = translation_filter->create_item(root);
- t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
- t->set_text(0, vformat("[%s] %s", l, n));
- t->set_editable(0, true);
- t->set_tooltip(0, l);
- t->set_checked(0, is_checked);
- translation_filter_treeitems.push_back(t);
- }
- } else {
- for (int i = 0; i < translation_filter_treeitems.size(); i++) {
- TreeItem *t = translation_filter_treeitems[i];
- t->set_checked(0, l_filter.has(t->get_tooltip(0)));
- }
- }
-
// Update translation remaps.
String remap_selected;
if (translation_remap->get_selected()) {
@@ -524,32 +395,6 @@ void LocalizationEditor::update_translations() {
translation_remap_options->set_hide_root(true);
translation_res_option_add_button->set_disabled(true);
- translation_locales_idxs_remap.clear();
- translation_locales_idxs_remap.resize(l_filter.size());
- int fl_idx_count = translation_locales_idxs_remap.size();
-
- String langnames = "";
- int l_idx = 0;
- for (int i = 0; i < names.size(); i++) {
- if (filter_mode == SHOW_ONLY_SELECTED_LOCALES && fl_idx_count != 0) {
- if (l_filter.size() > 0) {
- if (l_filter.find(langs[i]) != -1) {
- if (langnames.length() > 0) {
- langnames += ",";
- }
- langnames += vformat("[%s] %s", langs[i], names[i]);
- translation_locales_idxs_remap.write[l_idx] = i;
- l_idx++;
- }
- }
- } else {
- if (i > 0) {
- langnames += ",";
- }
- langnames += vformat("[%s] %s", langs[i], names[i]);
- }
- }
-
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
Dictionary remaps = ProjectSettings::get_singleton()->get("internationalization/locale/translation_remaps");
List<Variant> rk;
@@ -584,21 +429,11 @@ void LocalizationEditor::update_translations() {
t2->set_tooltip(0, path);
t2->set_metadata(0, j);
t2->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), 0, false, TTR("Remove"));
- t2->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
- t2->set_text(1, langnames);
+ t2->set_cell_mode(1, TreeItem::CELL_MODE_CUSTOM);
+ t2->set_text(1, TranslationServer::get_singleton()->get_locale_name(locale));
t2->set_editable(1, true);
t2->set_metadata(1, path);
- int idx = langs.find(locale);
- if (idx < 0) {
- idx = 0;
- }
-
- int f_idx = translation_locales_idxs_remap.find(idx);
- if (f_idx != -1 && fl_idx_count > 0 && filter_mode == SHOW_ONLY_SELECTED_LOCALES) {
- t2->set_range(1, f_idx);
- } else {
- t2->set_range(1, idx);
- }
+ t2->set_tooltip(1, locale);
}
}
}
@@ -637,9 +472,6 @@ LocalizationEditor::LocalizationEditor() {
updating_translations = false;
localization_changed = "localization_changed";
- translation_locales_idxs_remap = Vector<int>();
- translation_locales_list_created = false;
-
TabContainer *translations = memnew(TabContainer);
translations->set_tab_alignment(TabContainer::ALIGNMENT_LEFT);
translations->set_v_size_flags(Control::SIZE_EXPAND_FILL);
@@ -669,6 +501,10 @@ LocalizationEditor::LocalizationEditor() {
translation_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tmc->add_child(translation_list);
+ locale_select = memnew(EditorLocaleDialog);
+ locale_select->connect("locale_selected", callable_mp(this, &LocalizationEditor::_translation_res_option_selected));
+ add_child(locale_select);
+
translation_file_open = memnew(EditorFileDialog);
translation_file_open->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
translation_file_open->connect("files_selected", callable_mp(this, &LocalizationEditor::_translation_add));
@@ -731,10 +567,11 @@ LocalizationEditor::LocalizationEditor() {
translation_remap_options->set_column_expand(0, true);
translation_remap_options->set_column_clip_content(0, true);
translation_remap_options->set_column_expand(1, false);
- translation_remap_options->set_column_clip_content(1, true);
- translation_remap_options->set_column_custom_minimum_width(1, 200);
+ translation_remap_options->set_column_clip_content(1, false);
+ translation_remap_options->set_column_custom_minimum_width(1, 250);
translation_remap_options->connect("item_edited", callable_mp(this, &LocalizationEditor::_translation_res_option_changed));
translation_remap_options->connect("button_pressed", callable_mp(this, &LocalizationEditor::_translation_res_option_delete));
+ translation_remap_options->connect("custom_popup_edited", callable_mp(this, &LocalizationEditor::_translation_res_option_popup));
tmc->add_child(translation_remap_options);
translation_res_option_file_open_dialog = memnew(EditorFileDialog);
@@ -745,32 +582,6 @@ LocalizationEditor::LocalizationEditor() {
{
VBoxContainer *tvb = memnew(VBoxContainer);
- tvb->set_name(TTR("Locales Filter"));
- translations->add_child(tvb);
-
- VBoxContainer *tmc = memnew(VBoxContainer);
- tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- tvb->add_child(tmc);
-
- translation_locale_filter_mode = memnew(OptionButton);
- translation_locale_filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES);
- translation_locale_filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES);
- translation_locale_filter_mode->select(0);
- translation_locale_filter_mode->connect("item_selected", callable_mp(this, &LocalizationEditor::_translation_filter_mode_changed));
- tmc->add_margin_child(TTR("Filter mode:"), translation_locale_filter_mode);
-
- Label *l = memnew(Label(TTR("Locales:")));
- l->set_theme_type_variation("HeaderSmall");
- tmc->add_child(l);
- translation_filter = memnew(Tree);
- translation_filter->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- translation_filter->set_columns(1);
- translation_filter->connect("item_edited", callable_mp(this, &LocalizationEditor::_translation_filter_option_changed));
- tmc->add_child(translation_filter);
- }
-
- {
- VBoxContainer *tvb = memnew(VBoxContainer);
tvb->set_name(TTR("POT Generation"));
translations->add_child(tvb);
diff --git a/editor/localization_editor.h b/editor/localization_editor.h
index 4c77aca397..cad07dd336 100644
--- a/editor/localization_editor.h
+++ b/editor/localization_editor.h
@@ -33,18 +33,15 @@
#include "core/object/undo_redo.h"
#include "editor_file_dialog.h"
+#include "editor_locale_dialog.h"
#include "scene/gui/tree.h"
class LocalizationEditor : public VBoxContainer {
GDCLASS(LocalizationEditor, VBoxContainer);
- enum LocaleFilter {
- SHOW_ALL_LOCALES,
- SHOW_ONLY_SELECTED_LOCALES,
- };
-
Tree *translation_list;
+ EditorLocaleDialog *locale_select;
EditorFileDialog *translation_file_open;
Button *translation_res_option_add_button;
@@ -52,11 +49,6 @@ class LocalizationEditor : public VBoxContainer {
EditorFileDialog *translation_res_option_file_open_dialog;
Tree *translation_remap;
Tree *translation_remap_options;
- Tree *translation_filter;
- bool translation_locales_list_created;
- OptionButton *translation_locale_filter_mode;
- Vector<TreeItem *> translation_filter_treeitems;
- Vector<int> translation_locales_idxs_remap;
Tree *translation_pot_list;
EditorFileDialog *pot_file_open_dialog;
@@ -78,9 +70,8 @@ class LocalizationEditor : public VBoxContainer {
void _translation_res_option_add(const PackedStringArray &p_paths);
void _translation_res_option_changed();
void _translation_res_option_delete(Object *p_item, int p_column, int p_button);
-
- void _translation_filter_option_changed();
- void _translation_filter_mode_changed(int p_mode);
+ void _translation_res_option_popup(bool p_arrow_clicked);
+ void _translation_res_option_selected(const String &p_locale);
void _pot_add(const PackedStringArray &p_paths);
void _pot_delete(Object *p_item, int p_column, int p_button);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index cb84e7ea65..14f488f096 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3517,7 +3517,7 @@ void CanvasItemEditor::_draw_axis() {
Color area_axis_color = EditorSettings::get_singleton()->get("editors/2d/viewport_border_color");
- Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
Vector2 screen_endpoints[4] = {
transform.xform(Vector2(0, 0)),
@@ -4010,7 +4010,7 @@ void CanvasItemEditor::_update_scrollbars() {
Size2 vmin = v_scroll->get_minimum_size();
// Get the visible frame.
- Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height));
// Calculate scrollable area.
@@ -5129,8 +5129,22 @@ void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) {
hbc_context_menu->remove_child(p_control);
}
-HSplitContainer *CanvasItemEditor::get_palette_split() {
- return palette_split;
+void CanvasItemEditor::add_control_to_left_panel(Control *p_control) {
+ left_panel_split->add_child(p_control);
+ left_panel_split->move_child(p_control, 0);
+}
+
+void CanvasItemEditor::add_control_to_right_panel(Control *p_control) {
+ right_panel_split->add_child(p_control);
+ right_panel_split->move_child(p_control, 1);
+}
+
+void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) {
+ left_panel_split->remove_child(p_control);
+}
+
+void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) {
+ right_panel_split->remove_child(p_control);
}
VSplitContainer *CanvasItemEditor::get_bottom_split() {
@@ -5221,12 +5235,16 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
add_child(bottom_split);
bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- palette_split = memnew(HSplitContainer);
- bottom_split->add_child(palette_split);
- palette_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ left_panel_split = memnew(HSplitContainer);
+ bottom_split->add_child(left_panel_split);
+ left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ right_panel_split = memnew(HSplitContainer);
+ left_panel_split->add_child(right_panel_split);
+ right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
viewport_scrollable = memnew(Control);
- palette_split->add_child(viewport_scrollable);
+ right_panel_split->add_child(viewport_scrollable);
viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS);
viewport_scrollable->set_clip_contents(true);
viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 8bba5130d4..d58fb17356 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -527,7 +527,8 @@ private:
void _update_override_camera_button(bool p_game_running);
- HSplitContainer *palette_split;
+ HSplitContainer *left_panel_split;
+ HSplitContainer *right_panel_split;
VSplitContainer *bottom_split;
void _update_context_menu_stylebox();
@@ -571,7 +572,12 @@ public:
void add_control_to_menu_panel(Control *p_control);
void remove_control_from_menu_panel(Control *p_control);
- HSplitContainer *get_palette_split();
+ void add_control_to_left_panel(Control *p_control);
+ void remove_control_from_left_panel(Control *p_control);
+
+ void add_control_to_right_panel(Control *p_control);
+ void remove_control_from_right_panel(Control *p_control);
+
VSplitContainer *get_bottom_split();
Control *get_viewport_control() { return viewport; }
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 6ea8fba9b5..20f86c6a81 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -3064,7 +3064,7 @@ void Node3DEditorViewport::_draw() {
Math::round(2 * EDSCALE));
}
if (previewing) {
- Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
float aspect = ss.aspect();
Size2 s = get_size();
@@ -6880,8 +6880,46 @@ VSplitContainer *Node3DEditor::get_shader_split() {
return shader_split;
}
-HSplitContainer *Node3DEditor::get_palette_split() {
- return palette_split;
+void Node3DEditor::add_control_to_left_panel(Control *p_control) {
+ left_panel_split->add_child(p_control);
+ left_panel_split->move_child(p_control, 0);
+}
+
+void Node3DEditor::add_control_to_right_panel(Control *p_control) {
+ right_panel_split->add_child(p_control);
+ right_panel_split->move_child(p_control, 1);
+}
+
+void Node3DEditor::remove_control_from_left_panel(Control *p_control) {
+ left_panel_split->remove_child(p_control);
+}
+
+void Node3DEditor::remove_control_from_right_panel(Control *p_control) {
+ right_panel_split->remove_child(p_control);
+}
+
+void Node3DEditor::move_control_to_left_panel(Control *p_control) {
+ ERR_FAIL_NULL(p_control);
+ if (p_control->get_parent() == left_panel_split) {
+ return;
+ }
+
+ ERR_FAIL_COND(p_control->get_parent() != right_panel_split);
+ right_panel_split->remove_child(p_control);
+
+ add_control_to_left_panel(p_control);
+}
+
+void Node3DEditor::move_control_to_right_panel(Control *p_control) {
+ ERR_FAIL_NULL(p_control);
+ if (p_control->get_parent() == right_panel_split) {
+ return;
+ }
+
+ ERR_FAIL_COND(p_control->get_parent() != left_panel_split);
+ left_panel_split->remove_child(p_control);
+
+ add_control_to_right_panel(p_control);
}
void Node3DEditor::_request_gizmo(Object *p_obj) {
@@ -7534,13 +7572,17 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
/* REST OF MENU */
- palette_split = memnew(HSplitContainer);
- palette_split->set_v_size_flags(SIZE_EXPAND_FILL);
- vbc->add_child(palette_split);
+ left_panel_split = memnew(HSplitContainer);
+ left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
+ vbc->add_child(left_panel_split);
+
+ right_panel_split = memnew(HSplitContainer);
+ right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
+ left_panel_split->add_child(right_panel_split);
shader_split = memnew(VSplitContainer);
shader_split->set_h_size_flags(SIZE_EXPAND_FILL);
- palette_split->add_child(shader_split);
+ right_panel_split->add_child(shader_split);
viewport_base = memnew(Node3DEditorViewportContainer);
shader_split->add_child(viewport_base);
viewport_base->set_v_size_flags(SIZE_EXPAND_FILL);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index da560f4d83..20a782c8a8 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -527,7 +527,8 @@ private:
Node3DEditorViewportContainer *viewport_base;
Node3DEditorViewport *viewports[VIEWPORTS_COUNT];
VSplitContainer *shader_split;
- HSplitContainer *palette_split;
+ HSplitContainer *left_panel_split;
+ HSplitContainer *right_panel_split;
/////
@@ -801,8 +802,16 @@ public:
void add_control_to_menu_panel(Control *p_control);
void remove_control_from_menu_panel(Control *p_control);
+ void add_control_to_left_panel(Control *p_control);
+ void remove_control_from_left_panel(Control *p_control);
+
+ void add_control_to_right_panel(Control *p_control);
+ void remove_control_from_right_panel(Control *p_control);
+
+ void move_control_to_left_panel(Control *p_control);
+ void move_control_to_right_panel(Control *p_control);
+
VSplitContainer *get_shader_split();
- HSplitContainer *get_palette_split();
Node3D *get_single_selected_node() { return selected; }
bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo);
diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp
index 481ff1a781..c971bb6473 100644
--- a/editor/property_editor.cpp
+++ b/editor/property_editor.cpp
@@ -506,12 +506,16 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
} break;
case Variant::STRING: {
- if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) {
+ if (hint == PROPERTY_HINT_LOCALE_ID) {
+ List<String> names;
+ names.push_back(TTR("Locale..."));
+ names.push_back(TTR("Clear"));
+ config_action_buttons(names);
+ } else if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) {
List<String> names;
names.push_back(TTR("File..."));
names.push_back(TTR("Clear"));
config_action_buttons(names);
-
} else if (hint == PROPERTY_HINT_DIR || hint == PROPERTY_HINT_GLOBAL_DIR) {
List<String> names;
names.push_back(TTR("Dir..."));
@@ -1034,6 +1038,14 @@ void CustomPropertyEditor::_file_selected(String p_file) {
}
}
+void CustomPropertyEditor::_locale_selected(String p_locale) {
+ if (type == Variant::STRING && hint == PROPERTY_HINT_LOCALE_ID) {
+ v = p_locale;
+ emit_signal(SNAME("variant_changed"));
+ hide();
+ }
+}
+
void CustomPropertyEditor::_type_create_selected(int p_idx) {
if (type == Variant::INT || type == Variant::FLOAT) {
float newval = 0;
@@ -1177,7 +1189,8 @@ void CustomPropertyEditor::_action_pressed(int p_which) {
case Variant::STRING: {
if (hint == PROPERTY_HINT_MULTILINE_TEXT) {
hide();
-
+ } else if (hint == PROPERTY_HINT_LOCALE_ID) {
+ locale->popup_locale_dialog();
} else if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) {
if (p_which == 0) {
if (hint == PROPERTY_HINT_FILE) {
@@ -1821,6 +1834,12 @@ CustomPropertyEditor::CustomPropertyEditor() {
file->connect("file_selected", callable_mp(this, &CustomPropertyEditor::_file_selected));
file->connect("dir_selected", callable_mp(this, &CustomPropertyEditor::_file_selected));
+ locale = memnew(EditorLocaleDialog);
+ value_vbox->add_child(locale);
+ locale->hide();
+
+ locale->connect("locale_selected", callable_mp(this, &CustomPropertyEditor::_locale_selected));
+
error = memnew(ConfirmationDialog);
error->set_title(TTR("Error!"));
value_vbox->add_child(error);
diff --git a/editor/property_editor.h b/editor/property_editor.h
index 9d88aaf26d..298acb3c01 100644
--- a/editor/property_editor.h
+++ b/editor/property_editor.h
@@ -32,6 +32,7 @@
#define PROPERTY_EDITOR_H
#include "editor/editor_file_dialog.h"
+#include "editor/editor_locale_dialog.h"
#include "editor/scene_tree_editor.h"
#include "scene/gui/button.h"
#include "scene/gui/check_box.h"
@@ -97,6 +98,7 @@ class CustomPropertyEditor : public PopupPanel {
PopupMenu *menu;
SceneTreeDialog *scene_tree;
EditorFileDialog *file;
+ EditorLocaleDialog *locale;
ConfirmationDialog *error;
String name;
Variant::Type type;
@@ -136,6 +138,7 @@ class CustomPropertyEditor : public PopupPanel {
void _text_edit_changed();
void _file_selected(String p_file);
+ void _locale_selected(String p_locale);
void _modified(String p_string);
real_t _parse_real_expression(String text);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 9d382e160c..c9acb7b668 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -445,8 +445,11 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
}
+ bool was_empty = false;
if (!node_clipboard.is_empty()) {
_clear_clipboard();
+ } else {
+ was_empty = true;
}
clipboard_source_scene = editor->get_edited_scene()->get_scene_file_path();
@@ -464,81 +467,13 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
if (p_tool == TOOL_CUT) {
_delete_confirm(true);
}
- } break;
- case TOOL_PASTE: {
- if (node_clipboard.is_empty() || !edited_scene) {
- break;
- }
-
- bool has_cycle = false;
- if (!edited_scene->get_scene_file_path().is_empty()) {
- for (Node *E : node_clipboard) {
- if (edited_scene->get_scene_file_path() == E->get_scene_file_path()) {
- has_cycle = true;
- break;
- }
- }
- }
-
- if (has_cycle) {
- current_option = -1;
- accept->set_text(TTR("Can't paste root node into the same scene."));
- accept->popup_centered();
- break;
- }
- Node *paste_parent = edited_scene;
- List<Node *> selection = editor_selection->get_selected_node_list();
- if (selection.size() > 0) {
- paste_parent = selection.back()->get();
+ if (was_empty) {
+ _update_create_root_dialog();
}
-
- Node *owner = paste_parent->get_owner();
- if (!owner) {
- owner = paste_parent;
- }
-
- editor_data->get_undo_redo().create_action(TTR("Paste Node(s)"));
- editor_data->get_undo_redo().add_do_method(editor_selection, "clear");
-
- Map<RES, RES> resource_remap;
- String target_scene = editor->get_edited_scene()->get_scene_file_path();
- if (target_scene != clipboard_source_scene) {
- if (!clipboard_resource_remap.has(target_scene)) {
- Map<RES, RES> remap;
- for (Node *E : node_clipboard) {
- _create_remap_for_node(E, remap);
- }
- clipboard_resource_remap[target_scene] = remap;
- }
- resource_remap = clipboard_resource_remap[target_scene];
- }
-
- for (Node *node : node_clipboard) {
- Map<const Node *, Node *> duplimap;
-
- Node *dup = node->duplicate_from_editor(duplimap, resource_remap);
-
- ERR_CONTINUE(!dup);
-
- editor_data->get_undo_redo().add_do_method(paste_parent, "add_child", dup, true);
-
- for (KeyValue<const Node *, Node *> &E2 : duplimap) {
- Node *d = E2.value;
- editor_data->get_undo_redo().add_do_method(d, "set_owner", owner);
- }
-
- editor_data->get_undo_redo().add_do_method(dup, "set_owner", owner);
- editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", dup);
- editor_data->get_undo_redo().add_undo_method(paste_parent, "remove_child", dup);
- editor_data->get_undo_redo().add_do_reference(dup);
-
- if (node_clipboard.size() == 1) {
- editor_data->get_undo_redo().add_do_method(editor, "push_item", dup);
- }
- }
-
- editor_data->get_undo_redo().commit_action();
+ } break;
+ case TOOL_PASTE: {
+ paste_nodes();
} break;
case TOOL_REPLACE: {
if (!profile_allow_editing) {
@@ -1306,6 +1241,12 @@ void SceneTreeDock::_notification(int p_what) {
button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
button_custom->connect("pressed", callable_bind(callable_mp(this, &SceneTreeDock::_tool_selected), TOOL_NEW, false));
+ button_clipboard = memnew(Button);
+ node_shortcuts->add_child(button_clipboard);
+ button_clipboard->set_text(TTR("Paste From Clipboard"));
+ button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")));
+ button_clipboard->connect("pressed", callable_bind(callable_mp(this, &SceneTreeDock::_tool_selected), TOOL_PASTE, false));
+
node_shortcuts->add_spacer();
create_root_dialog->add_child(node_shortcuts);
_update_create_root_dialog();
@@ -1330,6 +1271,7 @@ void SceneTreeDock::_notification(int p_what) {
button_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons")));
button_ui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons")));
button_custom->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ button_clipboard->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")));
filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
filter->set_clear_button_enabled(true);
@@ -2740,10 +2682,10 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
}
if (profile_allow_editing) {
- menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT);
- menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY);
+ menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCut"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT);
+ menu->add_icon_shortcut(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY);
if (selection.size() == 1 && !node_clipboard.is_empty()) {
- menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE);
+ menu->add_icon_shortcut(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE);
}
menu->add_separator();
}
@@ -3022,6 +2964,108 @@ void SceneTreeDock::open_instance_child_dialog() {
_tool_selected(TOOL_INSTANTIATE, true);
}
+List<Node *> SceneTreeDock::paste_nodes() {
+ List<Node *> pasted_nodes;
+
+ if (node_clipboard.is_empty()) {
+ return pasted_nodes;
+ }
+
+ bool has_cycle = false;
+ if (edited_scene && !edited_scene->get_scene_file_path().is_empty()) {
+ for (Node *E : node_clipboard) {
+ if (edited_scene->get_scene_file_path() == E->get_scene_file_path()) {
+ has_cycle = true;
+ break;
+ }
+ }
+ }
+
+ if (has_cycle) {
+ current_option = -1;
+ accept->set_text(TTR("Can't paste root node into the same scene."));
+ accept->popup_centered();
+ return pasted_nodes;
+ }
+
+ Node *paste_parent = edited_scene;
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ if (selection.size() > 0) {
+ paste_parent = selection.back()->get();
+ }
+
+ Node *owner = nullptr;
+ if (paste_parent) {
+ owner = paste_parent->get_owner();
+ }
+ if (!owner) {
+ owner = paste_parent;
+ }
+
+ UndoRedo &ur = editor_data->get_undo_redo();
+ ur.create_action(TTR("Paste Node(s)"));
+ ur.add_do_method(editor_selection, "clear");
+
+ Map<RES, RES> resource_remap;
+ String target_scene;
+ if (edited_scene) {
+ target_scene = edited_scene->get_scene_file_path();
+ }
+ if (target_scene != clipboard_source_scene) {
+ if (!clipboard_resource_remap.has(target_scene)) {
+ Map<RES, RES> remap;
+ for (Node *E : node_clipboard) {
+ _create_remap_for_node(E, remap);
+ }
+ clipboard_resource_remap[target_scene] = remap;
+ }
+ resource_remap = clipboard_resource_remap[target_scene];
+ }
+
+ for (Node *node : node_clipboard) {
+ Map<const Node *, Node *> duplimap;
+
+ Node *dup = node->duplicate_from_editor(duplimap, resource_remap);
+ ERR_CONTINUE(!dup);
+
+ pasted_nodes.push_back(dup);
+
+ if (!paste_parent) {
+ paste_parent = dup;
+ owner = dup;
+ ur.add_do_method(editor, "set_edited_scene", dup);
+ } else {
+ ur.add_do_method(paste_parent, "add_child", dup, true);
+ }
+
+ for (KeyValue<const Node *, Node *> &E2 : duplimap) {
+ Node *d = E2.value;
+ if (d != dup) {
+ ur.add_do_method(d, "set_owner", owner);
+ }
+ }
+
+ if (dup != owner) {
+ ur.add_do_method(dup, "set_owner", owner);
+ }
+ ur.add_do_method(editor_selection, "add_node", dup);
+
+ if (dup == paste_parent) {
+ ur.add_undo_method(editor, "set_edited_scene", (Object *)nullptr);
+ } else {
+ ur.add_undo_method(paste_parent, "remove_child", dup);
+ }
+ ur.add_do_reference(dup);
+
+ if (node_clipboard.size() == 1) {
+ ur.add_do_method(editor, "push_item", dup);
+ }
+ }
+
+ ur.commit_action();
+ return pasted_nodes;
+}
+
void SceneTreeDock::add_remote_tree_editor(Control *p_remote) {
ERR_FAIL_COND(remote_tree != nullptr);
add_child(p_remote);
@@ -3121,6 +3165,7 @@ void SceneTreeDock::_update_create_root_dialog() {
beginner_nodes->show();
favorite_nodes->hide();
}
+ button_clipboard->set_visible(!node_clipboard.is_empty());
}
}
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index ffaf34cfdc..f442d3fc6b 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -123,6 +123,7 @@ class SceneTreeDock : public VBoxContainer {
Button *button_3d;
Button *button_ui;
Button *button_custom;
+ Button *button_clipboard;
HBoxContainer *button_hb;
Button *edit_local, *edit_remote;
@@ -308,6 +309,8 @@ public:
void open_add_child_dialog();
void open_instance_child_dialog();
+ List<Node *> paste_nodes();
+
ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; }
SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSelection *p_editor_selection, EditorData &p_editor_data);
diff --git a/main/main.cpp b/main/main.cpp
index 7350e15ef5..8b58641461 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1306,47 +1306,47 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// always convert to lower case for consistency in the code
rendering_driver = rendering_driver.to_lower();
- GLOBAL_DEF_BASIC("display/window/size/width", 1024);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width",
- PropertyInfo(Variant::INT, "display/window/size/width",
+ GLOBAL_DEF_BASIC("display/window/size/viewport_width", 1024);
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/viewport_width",
+ PropertyInfo(Variant::INT, "display/window/size/viewport_width",
PROPERTY_HINT_RANGE,
"0,7680,or_greater")); // 8K resolution
- GLOBAL_DEF_BASIC("display/window/size/height", 600);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/height",
- PropertyInfo(Variant::INT, "display/window/size/height",
+ GLOBAL_DEF_BASIC("display/window/size/viewport_height", 600);
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/viewport_height",
+ PropertyInfo(Variant::INT, "display/window/size/viewport_height",
PROPERTY_HINT_RANGE,
"0,4320,or_greater")); // 8K resolution
GLOBAL_DEF_BASIC("display/window/size/resizable", true);
GLOBAL_DEF_BASIC("display/window/size/borderless", false);
GLOBAL_DEF_BASIC("display/window/size/fullscreen", false);
GLOBAL_DEF("display/window/size/always_on_top", false);
- GLOBAL_DEF("display/window/size/test_width", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width",
+ GLOBAL_DEF("display/window/size/window_width_override", 0);
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/window_width_override",
PropertyInfo(Variant::INT,
- "display/window/size/test_width",
+ "display/window/size/window_width_override",
PROPERTY_HINT_RANGE,
"0,7680,or_greater")); // 8K resolution
- GLOBAL_DEF("display/window/size/test_height", 0);
- ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_height",
+ GLOBAL_DEF("display/window/size/window_height_override", 0);
+ ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/window_height_override",
PropertyInfo(Variant::INT,
- "display/window/size/test_height",
+ "display/window/size/window_height_override",
PROPERTY_HINT_RANGE,
"0,4320,or_greater")); // 8K resolution
if (use_custom_res) {
if (!force_res) {
- window_size.width = GLOBAL_GET("display/window/size/width");
- window_size.height = GLOBAL_GET("display/window/size/height");
-
- if (globals->has_setting("display/window/size/test_width") &&
- globals->has_setting("display/window/size/test_height")) {
- int tw = globals->get("display/window/size/test_width");
- if (tw > 0) {
- window_size.width = tw;
+ window_size.width = GLOBAL_GET("display/window/size/viewport_width");
+ window_size.height = GLOBAL_GET("display/window/size/viewport_height");
+
+ if (globals->has_setting("display/window/size/window_width_override") &&
+ globals->has_setting("display/window/size/window_height_override")) {
+ int desired_width = globals->get("display/window/size/window_width_override");
+ if (desired_width > 0) {
+ window_size.width = desired_width;
}
- int th = globals->get("display/window/size/test_height");
- if (th > 0) {
- window_size.height = th;
+ int desired_height = globals->get("display/window/size/window_height_override");
+ if (desired_height > 0) {
+ window_size.height = desired_height;
}
}
}
@@ -2348,8 +2348,8 @@ bool Main::start() {
String stretch_mode = GLOBAL_DEF_BASIC("display/window/stretch/mode", "disabled");
String stretch_aspect = GLOBAL_DEF_BASIC("display/window/stretch/aspect", "keep");
- Size2i stretch_size = Size2i(GLOBAL_DEF_BASIC("display/window/size/width", 0),
- GLOBAL_DEF_BASIC("display/window/size/height", 0));
+ Size2i stretch_size = Size2i(GLOBAL_DEF_BASIC("display/window/size/viewport_width", 0),
+ GLOBAL_DEF_BASIC("display/window/size/viewport_height", 0));
real_t stretch_scale = GLOBAL_DEF_BASIC("display/window/stretch/scale", 1.0);
Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED;
diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h
index eea898475b..879291c2e0 100644
--- a/modules/gdnative/include/nativescript/godot_nativescript.h
+++ b/modules/gdnative/include/nativescript/godot_nativescript.h
@@ -79,6 +79,7 @@ typedef enum {
GODOT_PROPERTY_HINT_PROPERTY_OF_BASE_TYPE, ///< a property of a base type
GODOT_PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance
GODOT_PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base
+ GODOT_PROPERTY_HINT_LOCALE_ID,
GODOT_PROPERTY_HINT_MAX,
} godot_nativescript_property_hint;
diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp
index 5ef48f8645..320901787d 100644
--- a/modules/gridmap/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/grid_map_editor_plugin.cpp
@@ -1459,10 +1459,10 @@ void GridMapEditorPlugin::_notification(int p_what) {
if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) {
case 0: { // Left.
- Node3DEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 0);
+ Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor);
} break;
case 1: { // Right.
- Node3DEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 1);
+ Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor);
} break;
}
}
@@ -1498,10 +1498,10 @@ GridMapEditorPlugin::GridMapEditorPlugin(EditorNode *p_node) {
grid_map_editor = memnew(GridMapEditor(editor));
switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) {
case 0: { // Left.
- add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, grid_map_editor);
+ Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor);
} break;
case 1: { // Right.
- add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT, grid_map_editor);
+ Node3DEditor::get_singleton()->add_control_to_right_panel(grid_map_editor);
} break;
}
grid_map_editor->hide();
diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
index 618fe14137..2f2c2c379d 100644
--- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
+++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml
@@ -33,8 +33,7 @@
<method name="create_data_channel">
<return type="WebRTCDataChannel" />
<argument index="0" name="label" type="String" />
- <argument index="1" name="options" type="Dictionary" default="{
-}" />
+ <argument index="1" name="options" type="Dictionary" default="{}" />
<description>
Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW].
There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code].
@@ -70,8 +69,7 @@
</method>
<method name="initialize">
<return type="int" enum="Error" />
- <argument index="0" name="configuration" type="Dictionary" default="{
-}" />
+ <argument index="0" name="configuration" type="Dictionary" default="{}" />
<description>
Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection.
Valid [code]options[/code] are:
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index aa44183329..78155fbef3 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1514,7 +1514,7 @@ String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, R
}
if (scale_splash) {
- Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
int width, height;
if (screen_size.width > screen_size.height) {
// scale horizontally
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index f4c0665f36..e8dfaf9c2e 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -530,7 +530,7 @@ Point2 Camera2D::get_camera_screen_center() const {
Size2 Camera2D::_get_camera_screen_size() const {
// special case if the camera2D is in the root viewport
if (Engine::get_singleton()->is_editor_hint() && get_viewport()->get_parent_viewport() == get_tree()->get_root()) {
- return Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ return Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
}
return get_viewport_rect().size;
}
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 046b9c18c7..552dd28513 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -569,7 +569,7 @@ void Button::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 69e6d74292..11d7946866 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1306,7 +1306,7 @@ Rect2 Control::get_parent_anchorable_rect() const {
#ifdef TOOLS_ENABLED
Node *edited_root = get_tree()->get_edited_scene_root();
if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) {
- parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
} else {
parent_rect = get_viewport()->get_visible_rect();
}
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
new file mode 100644
index 0000000000..d1ac60b325
--- /dev/null
+++ b/scene/gui/flow_container.cpp
@@ -0,0 +1,252 @@
+/*************************************************************************/
+/* flow_container.cpp */
+/*************************************************************************/
+/* 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. */
+/*************************************************************************/
+
+#include "scene/gui/container.h"
+
+#include "flow_container.h"
+
+struct _LineData {
+ int child_count = 0;
+ int min_line_height = 0;
+ int min_line_length = 0;
+ int stretch_avail = 0;
+ float stretch_ratio_total = 0;
+};
+
+void FlowContainer::_resort() {
+ int separation_horizontal = get_theme_constant(SNAME("hseparation"));
+ int separation_vertical = get_theme_constant(SNAME("vseparation"));
+
+ bool rtl = is_layout_rtl();
+
+ Map<Control *, Size2i> children_minsize_cache;
+
+ Vector<_LineData> lines_data;
+
+ Vector2i ofs;
+ int line_height = 0;
+ int line_length = 0;
+ float line_stretch_ratio_total = 0;
+ int current_container_size = vertical ? get_rect().size.y : get_rect().size.x;
+ int children_in_current_line = 0;
+
+ // First pass for line wrapping and minimum size calculation.
+ for (int i = 0; i < get_child_count(); i++) {
+ Control *child = Object::cast_to<Control>(get_child(i));
+ if (!child || !child->is_visible_in_tree()) {
+ continue;
+ }
+ if (child->is_set_as_top_level()) {
+ continue;
+ }
+
+ Size2i child_msc = child->get_combined_minimum_size();
+
+ if (vertical) { /* VERTICAL */
+ if (children_in_current_line > 0) {
+ ofs.y += separation_vertical;
+ }
+ if (ofs.y + child_msc.y > current_container_size) {
+ line_length = ofs.y - separation_vertical;
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+
+ // Move in new column (vertical line).
+ ofs.x += line_height + separation_horizontal;
+ ofs.y = 0;
+ line_height = 0;
+ line_stretch_ratio_total = 0;
+ children_in_current_line = 0;
+ }
+
+ line_height = MAX(line_height, child_msc.x);
+ if (child->get_v_size_flags() & SIZE_EXPAND) {
+ line_stretch_ratio_total += child->get_stretch_ratio();
+ }
+ ofs.y += child_msc.y;
+
+ } else { /* HORIZONTAL */
+ if (children_in_current_line > 0) {
+ ofs.x += separation_horizontal;
+ }
+ if (ofs.x + child_msc.x > current_container_size) {
+ line_length = ofs.x - separation_horizontal;
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+
+ // Move in new line.
+ ofs.y += line_height + separation_vertical;
+ ofs.x = 0;
+ line_height = 0;
+ line_stretch_ratio_total = 0;
+ children_in_current_line = 0;
+ }
+
+ line_height = MAX(line_height, child_msc.y);
+ if (child->get_h_size_flags() & SIZE_EXPAND) {
+ line_stretch_ratio_total += child->get_stretch_ratio();
+ }
+ ofs.x += child_msc.x;
+ }
+
+ children_minsize_cache[child] = child_msc;
+ children_in_current_line++;
+ }
+ line_length = vertical ? (ofs.y) : (ofs.x);
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+
+ // Second pass for in-line expansion and alignment.
+
+ int current_line_idx = 0;
+ int child_idx_in_line = 0;
+
+ ofs.x = 0;
+ ofs.y = 0;
+
+ for (int i = 0; i < get_child_count(); i++) {
+ Control *child = Object::cast_to<Control>(get_child(i));
+ if (!child || !child->is_visible_in_tree()) {
+ continue;
+ }
+ if (child->is_set_as_top_level()) {
+ continue;
+ }
+ Size2i child_size = children_minsize_cache[child];
+
+ _LineData line_data = lines_data[current_line_idx];
+ if (child_idx_in_line >= lines_data[current_line_idx].child_count) {
+ current_line_idx++;
+ child_idx_in_line = 0;
+ if (vertical) {
+ ofs.x += line_data.min_line_height + separation_horizontal;
+ ofs.y = 0;
+ } else {
+ ofs.x = 0;
+ ofs.y += line_data.min_line_height + separation_vertical;
+ }
+ line_data = lines_data[current_line_idx];
+ }
+
+ if (vertical) { /* VERTICAL */
+ if (child->get_h_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) {
+ child_size.width = line_data.min_line_height;
+ }
+
+ if (child->get_v_size_flags() & SIZE_EXPAND) {
+ int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
+ child_size.height += stretch;
+ }
+
+ } else { /* HORIZONTAL */
+ if (child->get_v_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) {
+ child_size.height = line_data.min_line_height;
+ }
+
+ if (child->get_h_size_flags() & SIZE_EXPAND) {
+ int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
+ child_size.width += stretch;
+ }
+ }
+
+ Rect2 child_rect = Rect2(ofs, child_size);
+ if (rtl) {
+ child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width;
+ }
+
+ fit_child_in_rect(child, child_rect);
+
+ if (vertical) { /* VERTICAL */
+ ofs.y += child_size.height + separation_vertical;
+ } else { /* HORIZONTAL */
+ ofs.x += child_size.width + separation_horizontal;
+ }
+
+ child_idx_in_line++;
+ }
+ cached_size = (vertical ? ofs.x : ofs.y) + line_height;
+ cached_line_count = lines_data.size();
+}
+
+Size2 FlowContainer::get_minimum_size() const {
+ Size2i minimum;
+
+ for (int i = 0; i < get_child_count(); i++) {
+ Control *c = Object::cast_to<Control>(get_child(i));
+ if (!c) {
+ continue;
+ }
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
+
+ if (!c->is_visible()) {
+ continue;
+ }
+
+ Size2i size = c->get_combined_minimum_size();
+
+ if (vertical) { /* VERTICAL */
+ minimum.height = MAX(minimum.height, size.height);
+ minimum.width = cached_size;
+
+ } else { /* HORIZONTAL */
+ minimum.width = MAX(minimum.width, size.width);
+ minimum.height = cached_size;
+ }
+ }
+
+ return minimum;
+}
+
+void FlowContainer::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_SORT_CHILDREN: {
+ _resort();
+ update_minimum_size();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ update_minimum_size();
+ } break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ queue_sort();
+ } break;
+ }
+}
+
+int FlowContainer::get_line_count() const {
+ return cached_line_count;
+}
+
+FlowContainer::FlowContainer(bool p_vertical) {
+ vertical = p_vertical;
+}
+
+void FlowContainer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count);
+}
diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h
new file mode 100644
index 0000000000..e3ed423ae1
--- /dev/null
+++ b/scene/gui/flow_container.h
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* flow_container.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 FLOW_CONTAINER_H
+#define FLOW_CONTAINER_H
+
+class Container;
+
+class FlowContainer : public Container {
+ GDCLASS(FlowContainer, Container);
+
+private:
+ int cached_size = 0;
+ int cached_line_count = 0;
+
+ bool vertical = false;
+
+ void _resort();
+
+protected:
+ void _notification(int p_what);
+
+ static void _bind_methods();
+
+public:
+ int get_line_count() const;
+
+ virtual Size2 get_minimum_size() const override;
+
+ FlowContainer(bool p_vertical = false);
+};
+
+class HFlowContainer : public FlowContainer {
+ GDCLASS(HFlowContainer, FlowContainer);
+
+public:
+ HFlowContainer() :
+ FlowContainer(false) {}
+};
+
+class VFlowContainer : public FlowContainer {
+ GDCLASS(VFlowContainer, FlowContainer);
+
+public:
+ VFlowContainer() :
+ FlowContainer(true) {}
+};
+
+#endif // FLOW_CONTAINER_H
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index d6569e3de4..30f6cf4a14 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -1022,7 +1022,7 @@ void GraphNode::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable");
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index a3878fe7ed..fab420d593 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -916,7 +916,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
ADD_GROUP("Locale", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index f7d6850a88..88953fa7db 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -2344,7 +2344,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_GROUP("Structured Text", "structured_text_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 67ffa2c7ed..0ff05faf85 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -308,7 +308,7 @@ void LinkButton::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode");
ADD_GROUP("Structured Text", "structured_text_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 863555120d..87f3a1c599 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -325,13 +325,16 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y;
+ frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
} else {
frame->lines.write[i].offset.y = 0;
}
frame->lines.write[i].offset += offset;
- float h = frame->lines[i].text_buf->get_size().y;
+ float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
+ if (i > 0) {
+ h += get_theme_constant(SNAME("line_separation"));
+ }
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
@@ -570,13 +573,16 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y;
+ frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
} else {
frame->lines.write[i].offset.y = 0;
}
frame->lines.write[i].offset += offset;
- float h = frame->lines[i].text_buf->get_size().y;
+ float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
+ if (i > 0) {
+ h += get_theme_constant(SNAME("line_separation"));
+ }
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
@@ -622,11 +628,12 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) {
- Vector2 off;
-
ERR_FAIL_COND_V(p_frame == nullptr, 0);
ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), 0);
+ Vector2 off;
+ int line_spacing = get_theme_constant(SNAME("line_separation"));
+
Line &l = p_frame->lines.write[p_line];
Item *it_from = l.from;
@@ -712,6 +719,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Size2 ctrl_size = get_size();
// Draw text.
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
+ if (line > 0) {
+ off.y += line_spacing;
+ }
+
RID rid = l.text_buf->get_line_rid(line);
if (p_ofs.y + off.y >= ctrl_size.height) {
break;
@@ -4207,7 +4218,7 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_GROUP("Structured Text", "structured_text_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 2b10ead8f6..671c2d951a 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -5171,7 +5171,7 @@ void TextEdit::_bind_methods() {
/* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index e6b73b7780..1a3a25c2fc 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -91,6 +91,7 @@
#include "scene/gui/control.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/file_dialog.h"
+#include "scene/gui/flow_container.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/graph_node.h"
#include "scene/gui/grid_container.h"
@@ -353,6 +354,9 @@ void register_scene_types() {
GDREGISTER_CLASS(CenterContainer);
GDREGISTER_CLASS(ScrollContainer);
GDREGISTER_CLASS(PanelContainer);
+ GDREGISTER_VIRTUAL_CLASS(FlowContainer);
+ GDREGISTER_CLASS(HFlowContainer);
+ GDREGISTER_CLASS(VFlowContainer);
OS::get_singleton()->yield(); // may take time to init
@@ -390,6 +394,7 @@ void register_scene_types() {
GDREGISTER_VIRTUAL_CLASS(SplitContainer);
GDREGISTER_CLASS(HSplitContainer);
GDREGISTER_CLASS(VSplitContainer);
+
GDREGISTER_CLASS(GraphNode);
GDREGISTER_CLASS(GraphEdit);
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 61cd834867..c22f16cbde 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -974,6 +974,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("separation", "VSplitContainer", 12 * scale);
theme->set_constant("autohide", "HSplitContainer", 1 * scale);
theme->set_constant("autohide", "VSplitContainer", 1 * scale);
+ theme->set_constant("hseparation", "HFlowContainer", 4 * scale);
+ theme->set_constant("vseparation", "HFlowContainer", 4 * scale);
+ theme->set_constant("hseparation", "VFlowContainer", 4 * scale);
+ theme->set_constant("vseparation", "VFlowContainer", 4 * scale);
Ref<StyleBoxTexture> sb_pc = make_stylebox(tab_container_bg_png, 4, 4, 4, 4, 7, 7, 7, 7);
theme->set_stylebox("panel", "PanelContainer", sb_pc);
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index 7e71ad8986..0afe040f33 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -496,13 +496,13 @@ bool Environment::is_sdfgi_enabled() const {
return sdfgi_enabled;
}
-void Environment::set_sdfgi_cascades(SDFGICascades p_cascades) {
- ERR_FAIL_INDEX(p_cascades, SDFGI_CASCADES_8 + 1);
+void Environment::set_sdfgi_cascades(int p_cascades) {
+ ERR_FAIL_COND_MSG(p_cascades < 1 || p_cascades > 8, "Invalid number of SDFGI cascades (must be between 1 and 8).");
sdfgi_cascades = p_cascades;
_update_sdfgi();
}
-Environment::SDFGICascades Environment::get_sdfgi_cascades() const {
+int Environment::get_sdfgi_cascades() const {
return sdfgi_cascades;
}
@@ -517,9 +517,7 @@ float Environment::get_sdfgi_min_cell_size() const {
void Environment::set_sdfgi_max_distance(float p_distance) {
p_distance /= 64.0;
- int cc[3] = { 4, 6, 8 };
- int cascades = cc[sdfgi_cascades];
- for (int i = 0; i < cascades; i++) {
+ for (int i = 0; i < sdfgi_cascades; i++) {
p_distance *= 0.5; //halve for each cascade
}
sdfgi_min_cell_size = p_distance;
@@ -529,9 +527,7 @@ void Environment::set_sdfgi_max_distance(float p_distance) {
float Environment::get_sdfgi_max_distance() const {
float md = sdfgi_min_cell_size;
md *= 64.0;
- int cc[3] = { 4, 6, 8 };
- int cascades = cc[sdfgi_cascades];
- for (int i = 0; i < cascades; i++) {
+ for (int i = 0; i < sdfgi_cascades; i++) {
md *= 2.0;
}
return md;
@@ -612,7 +608,7 @@ void Environment::_update_sdfgi() {
RS::get_singleton()->environment_set_sdfgi(
environment,
sdfgi_enabled,
- RS::EnvironmentSDFGICascades(sdfgi_cascades),
+ sdfgi_cascades,
sdfgi_min_cell_size,
RS::EnvironmentSDFGIYScale(sdfgi_y_scale),
sdfgi_use_occlusion,
@@ -1303,7 +1299,7 @@ void Environment::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sdfgi_use_occlusion"), "set_sdfgi_use_occlusion", "is_sdfgi_using_occlusion");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sdfgi_read_sky_light"), "set_sdfgi_read_sky_light", "is_sdfgi_reading_sky_light");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_bounce_feedback", PROPERTY_HINT_RANGE, "0,1.99,0.01"), "set_sdfgi_bounce_feedback", "get_sdfgi_bounce_feedback");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "sdfgi_cascades", PROPERTY_HINT_ENUM, "4 Cascades,6 Cascades,8 Cascades"), "set_sdfgi_cascades", "get_sdfgi_cascades");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sdfgi_cascades", PROPERTY_HINT_RANGE, "1,8,1"), "set_sdfgi_cascades", "get_sdfgi_cascades");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_min_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_sdfgi_min_cell_size", "get_sdfgi_min_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_cascade0_distance", PROPERTY_HINT_RANGE, "0.1,16384,0.1,or_greater"), "set_sdfgi_cascade0_distance", "get_sdfgi_cascade0_distance");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_max_distance", PROPERTY_HINT_RANGE, "0.1,16384,0.1,or_greater"), "set_sdfgi_max_distance", "get_sdfgi_max_distance");
@@ -1480,10 +1476,6 @@ void Environment::_bind_methods() {
BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_REPLACE);
BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_MIX);
- BIND_ENUM_CONSTANT(SDFGI_CASCADES_4);
- BIND_ENUM_CONSTANT(SDFGI_CASCADES_6);
- BIND_ENUM_CONSTANT(SDFGI_CASCADES_8);
-
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_DISABLED);
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_75_PERCENT);
BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_50_PERCENT);
diff --git a/scene/resources/environment.h b/scene/resources/environment.h
index 98e755c336..3f05315013 100644
--- a/scene/resources/environment.h
+++ b/scene/resources/environment.h
@@ -70,12 +70,6 @@ public:
TONE_MAPPER_ACES,
};
- enum SDFGICascades {
- SDFGI_CASCADES_4,
- SDFGI_CASCADES_6,
- SDFGI_CASCADES_8,
- };
-
enum SDFGIYScale {
SDFGI_Y_SCALE_DISABLED,
SDFGI_Y_SCALE_75_PERCENT,
@@ -153,7 +147,7 @@ private:
// SDFGI
bool sdfgi_enabled = false;
- SDFGICascades sdfgi_cascades = SDFGI_CASCADES_6;
+ int sdfgi_cascades = 6;
float sdfgi_min_cell_size = 0.2;
SDFGIYScale sdfgi_y_scale = SDFGI_Y_SCALE_DISABLED;
bool sdfgi_use_occlusion = false;
@@ -320,8 +314,8 @@ public:
// SDFGI
void set_sdfgi_enabled(bool p_enabled);
bool is_sdfgi_enabled() const;
- void set_sdfgi_cascades(SDFGICascades p_cascades);
- SDFGICascades get_sdfgi_cascades() const;
+ void set_sdfgi_cascades(int p_cascades);
+ int get_sdfgi_cascades() const;
void set_sdfgi_min_cell_size(float p_size);
float get_sdfgi_min_cell_size() const;
void set_sdfgi_max_distance(float p_distance);
@@ -433,7 +427,6 @@ VARIANT_ENUM_CAST(Environment::BGMode)
VARIANT_ENUM_CAST(Environment::AmbientSource)
VARIANT_ENUM_CAST(Environment::ReflectionSource)
VARIANT_ENUM_CAST(Environment::ToneMapper)
-VARIANT_ENUM_CAST(Environment::SDFGICascades)
VARIANT_ENUM_CAST(Environment::SDFGIYScale)
VARIANT_ENUM_CAST(Environment::GlowBlendMode)
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 402e67a0f1..f9a4eba978 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -175,14 +175,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
#endif
}
} else {
- Object *obj = nullptr;
+ //node belongs to this scene and must be created
+ Object *obj = ClassDB::instantiate(snames[n.type]);
- if (ClassDB::is_class_enabled(snames[n.type])) {
- //node belongs to this scene and must be created
- obj = ClassDB::instantiate(snames[n.type]);
- }
+ node = Object::cast_to<Node>(obj);
- if (!Object::cast_to<Node>(obj)) {
+ if (!node) {
if (obj) {
memdelete(obj);
obj = nullptr;
@@ -203,9 +201,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
if (!obj) {
obj = memnew(Node);
}
- }
- node = Object::cast_to<Node>(obj);
+ node = Object::cast_to<Node>(obj);
+ }
}
if (node) {
diff --git a/servers/rendering/rasterizer_dummy.h b/servers/rendering/rasterizer_dummy.h
index f02a01c97d..83da8388e4 100644
--- a/servers/rendering/rasterizer_dummy.h
+++ b/servers/rendering/rasterizer_dummy.h
@@ -120,7 +120,7 @@ public:
void environment_set_ssil(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_sharpness, float p_normal_rejection) override {}
void environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) override {}
- void environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override {}
+ void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override {}
void environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) override {}
void environment_set_sdfgi_frames_to_converge(RS::EnvironmentSDFGIFramesToConverge p_frames) override {}
diff --git a/servers/rendering/renderer_rd/renderer_scene_environment_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_environment_rd.cpp
index 7ea117ef33..0d0d7513d0 100644
--- a/servers/rendering/renderer_rd/renderer_scene_environment_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_environment_rd.cpp
@@ -68,7 +68,7 @@ void RendererSceneEnvironmentRD::set_glow(bool p_enable, Vector<float> p_levels,
glow_hdr_luminance_cap = p_hdr_luminance_cap;
}
-void RendererSceneEnvironmentRD::set_sdfgi(bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
+void RendererSceneEnvironmentRD::set_sdfgi(bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
sdfgi_enabled = p_enable;
sdfgi_cascades = p_cascades;
sdfgi_min_cell_size = p_min_cell_size;
diff --git a/servers/rendering/renderer_rd/renderer_scene_environment_rd.h b/servers/rendering/renderer_rd/renderer_scene_environment_rd.h
index 9e36a61870..629d224b49 100644
--- a/servers/rendering/renderer_rd/renderer_scene_environment_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_environment_rd.h
@@ -133,7 +133,7 @@ public:
/// SDFGI
bool sdfgi_enabled = false;
- RS::EnvironmentSDFGICascades sdfgi_cascades;
+ int sdfgi_cascades = 6;
float sdfgi_min_cell_size = 0.2;
bool sdfgi_use_occlusion = false;
float sdfgi_bounce_feedback = 0.0;
@@ -155,7 +155,7 @@ public:
void set_ambient_light(const Color &p_color, RS::EnvironmentAmbientSource p_ambient, float p_energy, float p_sky_contribution, RS::EnvironmentReflectionSource p_reflection_source);
void set_tonemap(RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale);
void set_glow(bool p_enable, Vector<float> p_levels, float p_intensity, float p_strength, float p_mix, float p_bloom_threshold, RS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap);
- void set_sdfgi(bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias);
+ void set_sdfgi(bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias);
void set_fog(bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_fog_aerial_perspective);
void set_volumetric_fog(bool p_enable, float p_density, const Color &p_scatterin, const Color &p_emission, float p_emission_energy, float p_anisotropy, float p_length, float p_detail_spread, float p_gi_inject, bool p_temporal_reprojection, float p_temporal_reprojection_amount, float p_ambient_inject);
void set_ssr(bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance);
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
index 73cb088f6a..3069b1c379 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp
@@ -42,14 +42,13 @@ const Vector3i RendererSceneGIRD::SDFGI::Cascade::DIRTY_ALL = Vector3i(0x7FFFFFF
void RendererSceneGIRD::SDFGI::create(RendererSceneEnvironmentRD *p_env, const Vector3 &p_world_position, uint32_t p_requested_history_size, RendererSceneGIRD *p_gi) {
storage = p_gi->storage;
gi = p_gi;
- cascade_mode = p_env->sdfgi_cascades;
+ num_cascades = p_env->sdfgi_cascades;
min_cell_size = p_env->sdfgi_min_cell_size;
uses_occlusion = p_env->sdfgi_use_occlusion;
y_scale_mode = p_env->sdfgi_y_scale;
static const float y_scale[3] = { 1.0, 1.5, 2.0 };
y_mult = y_scale[y_scale_mode];
- static const int cascasde_size[3] = { 4, 6, 8 };
- cascades.resize(cascasde_size[cascade_mode]);
+ cascades.resize(num_cascades);
probe_axis_count = SDFGI::PROBE_DIVISOR + 1;
solid_cell_ratio = gi->sdfgi_solid_cell_ratio;
solid_cell_count = uint32_t(float(cascade_size * cascade_size * cascade_size) * solid_cell_ratio);
@@ -716,7 +715,10 @@ void RendererSceneGIRD::SDFGI::create(RendererSceneEnvironmentRD *p_env, const V
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 13;
RID parent_average;
- if (i < cascades.size() - 1) {
+ if (cascades.size() == 1) {
+ // If there is only one SDFGI cascade, we can't use the previous cascade for blending.
+ parent_average = cascades[i].lightprobe_average_tex;
+ } else if (i < cascades.size() - 1) {
parent_average = cascades[i + 1].lightprobe_average_tex;
} else {
parent_average = cascades[i - 1].lightprobe_average_tex; //to use something, but it won't be used
diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
index a407199d0a..5e55262798 100644
--- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h
@@ -495,7 +495,7 @@ public:
float solid_cell_ratio = 0;
uint32_t solid_cell_count = 0;
- RS::EnvironmentSDFGICascades cascade_mode;
+ int num_cascades = 6;
float min_cell_size = 0;
uint32_t probe_axis_count = 0; //amount of probes per axis, this is an odd number because it encloses endpoints
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index f6d58cb094..3a01c3377a 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -65,7 +65,7 @@ void RendererSceneRenderRD::sdfgi_update(RID p_render_buffers, RID p_environment
static const uint32_t history_frames_to_converge[RS::ENV_SDFGI_CONVERGE_MAX] = { 5, 10, 15, 20, 25, 30 };
uint32_t requested_history_size = history_frames_to_converge[gi.sdfgi_frames_to_converge];
- if (rb->sdfgi && (rb->sdfgi->cascade_mode != env->sdfgi_cascades || rb->sdfgi->min_cell_size != env->sdfgi_min_cell_size || requested_history_size != rb->sdfgi->history_size || rb->sdfgi->uses_occlusion != env->sdfgi_use_occlusion || rb->sdfgi->y_scale_mode != env->sdfgi_y_scale)) {
+ if (rb->sdfgi && (rb->sdfgi->num_cascades != env->sdfgi_cascades || rb->sdfgi->min_cell_size != env->sdfgi_min_cell_size || requested_history_size != rb->sdfgi->history_size || rb->sdfgi->uses_occlusion != env->sdfgi_use_occlusion || rb->sdfgi->y_scale_mode != env->sdfgi_y_scale)) {
//configuration changed, erase
rb->sdfgi->erase();
memdelete(rb->sdfgi);
@@ -303,7 +303,7 @@ void RendererSceneRenderRD::environment_glow_set_use_high_quality(bool p_enable)
glow_high_quality = p_enable;
}
-void RendererSceneRenderRD::environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
+void RendererSceneRenderRD::environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) {
RendererSceneEnvironmentRD *env = environment_owner.get_or_null(p_env);
ERR_FAIL_COND(!env);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
index 6432ca99f0..08e084f5cc 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
@@ -1093,7 +1093,7 @@ public:
bool environment_is_ssr_enabled(RID p_env) const;
bool environment_is_sdfgi_enabled(RID p_env) const;
- virtual void environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override;
+ virtual void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) override;
virtual void environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) override;
virtual void environment_set_sdfgi_frames_to_converge(RS::EnvironmentSDFGIFramesToConverge p_frames) override;
virtual void environment_set_sdfgi_frames_to_update_light(RS::EnvironmentSDFGIFramesToUpdateLight p_update) override;
diff --git a/servers/rendering/renderer_scene.h b/servers/rendering/renderer_scene.h
index 20ca49cd71..426d22f83e 100644
--- a/servers/rendering/renderer_scene.h
+++ b/servers/rendering/renderer_scene.h
@@ -149,7 +149,7 @@ public:
virtual void environment_set_ssil(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_sharpness, float p_normal_rejection) = 0;
virtual void environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) = 0;
- virtual void environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
+ virtual void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
virtual void environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) = 0;
virtual void environment_set_sdfgi_frames_to_converge(RS::EnvironmentSDFGIFramesToConverge p_frames) = 0;
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 071d88233f..1e770ef66c 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1127,7 +1127,7 @@ public:
PASS2(environment_set_volumetric_fog_volume_size, int, int)
PASS1(environment_set_volumetric_fog_filter_active, bool)
- PASS11(environment_set_sdfgi, RID, bool, RS::EnvironmentSDFGICascades, float, RS::EnvironmentSDFGIYScale, bool, float, bool, float, float, float)
+ PASS11(environment_set_sdfgi, RID, bool, int, float, RS::EnvironmentSDFGIYScale, bool, float, bool, float, float, float)
PASS1(environment_set_sdfgi_ray_count, RS::EnvironmentSDFGIRayCount)
PASS1(environment_set_sdfgi_frames_to_converge, RS::EnvironmentSDFGIFramesToConverge)
PASS1(environment_set_sdfgi_frames_to_update_light, RS::EnvironmentSDFGIFramesToUpdateLight)
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index f99d34d292..c34a46d166 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -140,7 +140,7 @@ public:
virtual void environment_set_ssil(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_sharpness, float p_normal_rejection) = 0;
virtual void environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) = 0;
- virtual void environment_set_sdfgi(RID p_env, bool p_enable, RS::EnvironmentSDFGICascades p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
+ virtual void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, RS::EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
virtual void environment_set_sdfgi_ray_count(RS::EnvironmentSDFGIRayCount p_ray_count) = 0;
virtual void environment_set_sdfgi_frames_to_converge(RS::EnvironmentSDFGIFramesToConverge p_frames) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index ead49f053c..ee684c69ed 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -643,7 +643,7 @@ public:
FUNC2(environment_set_volumetric_fog_volume_size, int, int)
FUNC1(environment_set_volumetric_fog_filter_active, bool)
- FUNC11(environment_set_sdfgi, RID, bool, EnvironmentSDFGICascades, float, EnvironmentSDFGIYScale, bool, float, bool, float, float, float)
+ FUNC11(environment_set_sdfgi, RID, bool, int, float, EnvironmentSDFGIYScale, bool, float, bool, float, float, float)
FUNC1(environment_set_sdfgi_ray_count, EnvironmentSDFGIRayCount)
FUNC1(environment_set_sdfgi_frames_to_converge, EnvironmentSDFGIFramesToConverge)
FUNC1(environment_set_sdfgi_frames_to_update_light, EnvironmentSDFGIFramesToUpdateLight)
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 020c850f46..863aae6e4c 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2388,10 +2388,6 @@ void RenderingServer::_bind_methods() {
BIND_ENUM_CONSTANT(ENV_SSIL_QUALITY_HIGH);
BIND_ENUM_CONSTANT(ENV_SSIL_QUALITY_ULTRA);
- BIND_ENUM_CONSTANT(ENV_SDFGI_CASCADES_4);
- BIND_ENUM_CONSTANT(ENV_SDFGI_CASCADES_6);
- BIND_ENUM_CONSTANT(ENV_SDFGI_CASCADES_8);
-
BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_DISABLED);
BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_75_PERCENT);
BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_50_PERCENT);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 5dbec04665..945fd052c6 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -1041,19 +1041,13 @@ public:
virtual void environment_set_ssil_quality(EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) = 0;
- enum EnvironmentSDFGICascades {
- ENV_SDFGI_CASCADES_4,
- ENV_SDFGI_CASCADES_6,
- ENV_SDFGI_CASCADES_8,
- };
-
enum EnvironmentSDFGIYScale {
ENV_SDFGI_Y_SCALE_DISABLED,
ENV_SDFGI_Y_SCALE_75_PERCENT,
ENV_SDFGI_Y_SCALE_50_PERCENT
};
- virtual void environment_set_sdfgi(RID p_env, bool p_enable, EnvironmentSDFGICascades p_cascades, float p_min_cell_size, EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
+ virtual void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0;
enum EnvironmentSDFGIRayCount {
ENV_SDFGI_RAY_COUNT_4,
@@ -1608,7 +1602,6 @@ VARIANT_ENUM_CAST(RenderingServer::EnvironmentToneMapper);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSSRRoughnessQuality);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSSAOQuality);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSSILQuality);
-VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGICascades);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIFramesToConverge);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIRayCount);
VARIANT_ENUM_CAST(RenderingServer::EnvironmentSDFGIFramesToUpdateLight);
diff --git a/tests/core/templates/test_vector.h b/tests/core/templates/test_vector.h
index 24b3547256..f27d6a332e 100644
--- a/tests/core/templates/test_vector.h
+++ b/tests/core/templates/test_vector.h
@@ -257,27 +257,42 @@ TEST_CASE("[Vector] Slice") {
vector.push_back(3);
vector.push_back(4);
+ Vector<int> slice0 = vector.slice(0, 0);
+ CHECK(slice0.size() == 0);
+
Vector<int> slice1 = vector.slice(1, 3);
CHECK(slice1.size() == 2);
CHECK(slice1[0] == 1);
CHECK(slice1[1] == 2);
Vector<int> slice2 = vector.slice(1, -1);
- CHECK(slice2.size() == 4);
+ CHECK(slice2.size() == 3);
CHECK(slice2[0] == 1);
CHECK(slice2[1] == 2);
CHECK(slice2[2] == 3);
- CHECK(slice2[3] == 4);
- Vector<int> slice3 = vector.slice(3, -1);
+ Vector<int> slice3 = vector.slice(3);
CHECK(slice3.size() == 2);
CHECK(slice3[0] == 3);
CHECK(slice3[1] == 4);
Vector<int> slice4 = vector.slice(2, -2);
- CHECK(slice4.size() == 2);
+ CHECK(slice4.size() == 1);
CHECK(slice4[0] == 2);
- CHECK(slice4[1] == 3);
+
+ Vector<int> slice5 = vector.slice(-2);
+ CHECK(slice5.size() == 2);
+ CHECK(slice5[0] == 3);
+ CHECK(slice5[1] == 4);
+
+ Vector<int> slice6 = vector.slice(2, 42);
+ CHECK(slice6.size() == 3);
+ CHECK(slice6[0] == 2);
+ CHECK(slice6[1] == 3);
+ CHECK(slice6[2] == 4);
+
+ Vector<int> slice7 = vector.slice(5, 1);
+ CHECK(slice7.size() == 0);
}
TEST_CASE("[Vector] Find, has") {
diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h
index 205e34daea..6093048307 100644
--- a/tests/core/variant/test_array.h
+++ b/tests/core/variant/test_array.h
@@ -254,27 +254,52 @@ TEST_CASE("[Array] slice()") {
array.push_back(3);
array.push_back(4);
+ Array slice0 = array.slice(0, 0);
+ CHECK(slice0.size() == 0);
+
Array slice1 = array.slice(1, 3);
CHECK(slice1.size() == 2);
CHECK(slice1[0] == Variant(1));
CHECK(slice1[1] == Variant(2));
Array slice2 = array.slice(1, -1);
- CHECK(slice2.size() == 4);
+ CHECK(slice2.size() == 3);
CHECK(slice2[0] == Variant(1));
CHECK(slice2[1] == Variant(2));
CHECK(slice2[2] == Variant(3));
- CHECK(slice2[3] == Variant(4));
- Array slice3 = array.slice(3, -1);
+ Array slice3 = array.slice(3);
CHECK(slice3.size() == 2);
CHECK(slice3[0] == Variant(3));
CHECK(slice3[1] == Variant(4));
Array slice4 = array.slice(2, -2);
- CHECK(slice4.size() == 2);
+ CHECK(slice4.size() == 1);
CHECK(slice4[0] == Variant(2));
- CHECK(slice4[1] == Variant(3));
+
+ Array slice5 = array.slice(-2);
+ CHECK(slice5.size() == 2);
+ CHECK(slice5[0] == Variant(3));
+ CHECK(slice5[1] == Variant(4));
+
+ Array slice6 = array.slice(2, 42);
+ CHECK(slice6.size() == 3);
+ CHECK(slice6[0] == Variant(2));
+ CHECK(slice6[1] == Variant(3));
+ CHECK(slice6[2] == Variant(4));
+
+ Array slice7 = array.slice(4, 0, -2);
+ CHECK(slice7.size() == 2);
+ CHECK(slice7[0] == Variant(4));
+ CHECK(slice7[1] == Variant(2));
+
+ ERR_PRINT_OFF;
+ Array slice8 = array.slice(4, 1);
+ CHECK(slice8.size() == 0);
+
+ Array slice9 = array.slice(3, -4);
+ CHECK(slice9.size() == 0);
+ ERR_PRINT_ON;
}
TEST_CASE("[Array] Duplicate array") {
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 6183aaa03c..2cb5458952 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -613,6 +613,8 @@ Files extracted from upstream source:
See `thorvg/update-thorvg.sh` for extraction instructions. Set the version
number and run the script.
+Patches in the `patches` directory should be re-applied after updates.
+
## vhacd
diff --git a/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch b/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch
new file mode 100644
index 0000000000..a174880306
--- /dev/null
+++ b/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch
@@ -0,0 +1,73 @@
+diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+index def8ae169a..cf103774c5 100644
+--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
++++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+@@ -51,6 +51,7 @@
+
+ #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
++#include <cstring>
+ #include <fstream>
+ #include <float.h>
+ #include <math.h>
+diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
+index 2b62315de8..32685ee620 100644
+--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
++++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
+@@ -50,6 +50,7 @@
+
+ #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
++#include <cstring>
+ #include <math.h>
+ #include <clocale>
+ #include <ctype.h>
+diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
+index 8701fe32b1..ae17634f31 100644
+--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
++++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
+@@ -49,6 +49,7 @@
+ */
+
+
++#include <cstring>
+ #include <string>
+ #include "tvgMath.h"
+ #include "tvgSvgLoaderCommon.h"
+diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
+index d5b9cdcf7b..9f269b29a2 100644
+--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
++++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
+@@ -20,6 +20,7 @@
+ * SOFTWARE.
+ */
+
++#include <cstring>
+ #include <math.h>
+ #include <memory.h>
+ #include "tvgSvgUtil.h"
+diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
+index 2e3d5928d9..1571aa4e25 100644
+--- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
++++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
+@@ -20,6 +20,7 @@
+ * SOFTWARE.
+ */
+
++#include <cstring>
+ #include <ctype.h>
+ #include <string>
+
+diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
+index 9450d80e88..9dd57e5a89 100644
+--- a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
++++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
+@@ -24,6 +24,8 @@
+ #include "tvgTvgSaver.h"
+ #include "tvgLzw.h"
+
++#include <cstring>
++
+ #ifdef _WIN32
+ #include <malloc.h>
+ #else
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index def8ae169a..cf103774c5 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -51,6 +51,7 @@
#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+#include <cstring>
#include <fstream>
#include <float.h>
#include <math.h>
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
index 2b62315de8..32685ee620 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
@@ -50,6 +50,7 @@
#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+#include <cstring>
#include <math.h>
#include <clocale>
#include <ctype.h>
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
index 8701fe32b1..ae17634f31 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
@@ -49,6 +49,7 @@
*/
+#include <cstring>
#include <string>
#include "tvgMath.h"
#include "tvgSvgLoaderCommon.h"
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
index d5b9cdcf7b..9f269b29a2 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
@@ -20,6 +20,7 @@
* SOFTWARE.
*/
+#include <cstring>
#include <math.h>
#include <memory.h>
#include "tvgSvgUtil.h"
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
index 2e3d5928d9..1571aa4e25 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
@@ -20,6 +20,7 @@
* SOFTWARE.
*/
+#include <cstring>
#include <ctype.h>
#include <string>
diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
index 9450d80e88..9dd57e5a89 100644
--- a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
+++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
@@ -24,6 +24,8 @@
#include "tvgTvgSaver.h"
#include "tvgLzw.h"
+#include <cstring>
+
#ifdef _WIN32
#include <malloc.h>
#else