diff options
231 files changed, 7204 insertions, 5418 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index b0ed71a7b5..3a92b8ffb4 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -215,7 +215,7 @@ jobs: if: ${{ matrix.godot-cpp-test }} run: | cd godot-cpp/test - scons target=debug + scons target=template_debug dev_build=yes cd ../.. - name: Prepare artifact diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index b35c811763..507bb21a46 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -27,7 +27,7 @@ jobs: target: editor tests: true # Skip debug symbols, they're way too big with MSVC. - sconsflags: debug_symbols=no + sconsflags: debug_symbols=no vsproj=yes bin: "./bin/godot.windows.editor.x86_64.exe" - name: Template (target=template_release, tools=no) diff --git a/.gitignore b/.gitignore index 539003ca6b..1a83e4707e 100644 --- a/.gitignore +++ b/.gitignore @@ -82,9 +82,9 @@ platform/android/java/*/libs/ # iOS *.dSYM -# Javascript +# Web platform *.bc -platform/javascript/node_modules/ +platform/web/node_modules/ # Misc *.debug diff --git a/core/error/error_macros.h b/core/error/error_macros.h index 2cfb5421c6..6901548cca 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -137,8 +137,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. - * If not, prints `m_msg`, notifies in the editor, and the current function returns. + * Same as `ERR_FAIL_INDEX_MSG` but also notifies the editor. */ #define ERR_FAIL_INDEX_EDMSG(m_index, m_size, m_msg) \ if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ @@ -173,8 +172,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. - * If not, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + * Same as `ERR_FAIL_INDEX_V_MSG` but also notifies the editor. */ #define ERR_FAIL_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \ if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ @@ -240,9 +238,9 @@ void _err_flush_stdout(); return; \ } else \ ((void)0) + /** - * Ensures an unsigned integer index `m_index` is less than `m_size`. - * If not, prints `m_msg`, notifies in the editor, and the current function returns. + * Same as `ERR_FAIL_UNSIGNED_INDEX_MSG` but also notifies the editor. */ #define ERR_FAIL_UNSIGNED_INDEX_EDMSG(m_index, m_size, m_msg) \ if (unlikely((m_index) >= (m_size))) { \ @@ -277,8 +275,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures an unsigned integer index `m_index` is less than `m_size`. - * If not, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + * Same as `ERR_FAIL_UNSIGNED_INDEX_V_EDMSG` but also notifies the editor. */ #define ERR_FAIL_UNSIGNED_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \ if (unlikely((m_index) >= (m_size))) { \ @@ -346,8 +343,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures a pointer `m_param` is not null. - * If it is null, prints `m_msg`, notifies in the editor, and the current function returns. + * Same as `ERR_FAIL_NULL_MSG` but also notifies the editor. */ #define ERR_FAIL_NULL_EDMSG(m_param, m_msg) \ if (unlikely(m_param == nullptr)) { \ @@ -382,8 +378,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures a pointer `m_param` is not null. - * If it is null, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + * Same as `ERR_FAIL_NULL_V_MSG` but also notifies the editor. */ #define ERR_FAIL_NULL_V_EDMSG(m_param, m_retval, m_msg) \ if (unlikely(m_param == nullptr)) { \ @@ -423,11 +418,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures `m_cond` is false. - * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current function returns. - * - * If checking for null use ERR_FAIL_NULL_MSG instead. - * If checking index bounds use ERR_FAIL_INDEX_MSG instead. + * Same as `ERR_FAIL_COND_MSG` but also notifies the editor. */ #define ERR_FAIL_COND_EDMSG(m_cond, m_msg) \ if (unlikely(m_cond)) { \ @@ -467,11 +458,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures `m_cond` is false. - * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. - * - * If checking for null use ERR_FAIL_NULL_V_MSG instead. - * If checking index bounds use ERR_FAIL_INDEX_V_MSG instead. + * Same as `ERR_FAIL_COND_V_MSG` but also notifies the editor. */ #define ERR_FAIL_COND_V_EDMSG(m_cond, m_retval, m_msg) \ if (unlikely(m_cond)) { \ @@ -506,8 +493,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures `m_cond` is false. - * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current loop continues. + * Same as `ERR_CONTINUE_MSG` but also notifies the editor. */ #define ERR_CONTINUE_EDMSG(m_cond, m_msg) \ if (unlikely(m_cond)) { \ @@ -542,8 +528,7 @@ void _err_flush_stdout(); ((void)0) /** - * Ensures `m_cond` is false. - * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current loop breaks. + * Same as `ERR_BREAK_MSG` but also notifies the editor. */ #define ERR_BREAK_EDMSG(m_cond, m_msg) \ if (unlikely(m_cond)) { \ @@ -613,10 +598,7 @@ void _err_flush_stdout(); ((void)0) /** - * Try using `ERR_FAIL_COND_MSG`. - * Only use this macro if more complex error detection or recovery is required. - * - * Prints `m_msg`, notifies in the editor, and the current function returns. + * Same as `ERR_FAIL_MSG` but also notifies the editor. */ #define ERR_FAIL_EDMSG(m_msg) \ if (true) { \ @@ -653,10 +635,7 @@ void _err_flush_stdout(); ((void)0) /** - * Try using `ERR_FAIL_COND_V_MSG`. - * Only use this macro if more complex error detection or recovery is required. - * - * Prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + * Same as `ERR_FAIL_V_MSG` but also notifies the editor. */ #define ERR_FAIL_V_EDMSG(m_retval, m_msg) \ if (true) { \ @@ -666,7 +645,7 @@ void _err_flush_stdout(); ((void)0) /** - * Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or ERR_BREAK_MSG. + * Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or `ERR_BREAK_MSG`. * Only use this macro at the start of a function that has not been implemented yet, or * if more complex error detection or recovery is required. * @@ -676,14 +655,10 @@ void _err_flush_stdout(); _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg) /** - * Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or ERR_BREAK_MSG. - * Only use this macro at the start of a function that has not been implemented yet, or - * if more complex error detection or recovery is required. - * - * Prints `m_msg` and notifies the editor. + * Same as `ERR_PRINT` but also notifies the editor. */ #define ERR_PRINT_ED(m_msg) \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, ) + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true) /** * Prints `m_msg` once during the application lifetime. @@ -699,7 +674,7 @@ void _err_flush_stdout(); ((void)0) /** - * Prints `m_msg` and notifies the editor once during the application lifetime. + * Same as `ERR_PRINT_ONCE` but also notifies the editor. */ #define ERR_PRINT_ONCE_ED(m_msg) \ if (true) { \ @@ -722,9 +697,7 @@ void _err_flush_stdout(); _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, false, ERR_HANDLER_WARNING) /** - * Prints `m_msg` and notifies the editor. - * - * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. + * Same as `WARN_PRINT` but also notifies the editor. */ #define WARN_PRINT_ED(m_msg) \ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true, ERR_HANDLER_WARNING) @@ -745,9 +718,7 @@ void _err_flush_stdout(); ((void)0) /** - * Prints `m_msg` and notifies the editor once during the application lifetime. - * - * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. + * Same as `WARN_PRINT_ONCE` but also notifies the editor. */ #define WARN_PRINT_ONCE_ED(m_msg) \ if (true) { \ diff --git a/core/io/logger.cpp b/core/io/logger.cpp index b0f74f8db5..288e53d075 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -87,7 +87,7 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c } else { logf_error("USER %s: %s\n", err_type, err_details); } - logf_error(" at: %s (%s:%i) - %s\n", p_function, p_file, p_line, p_code); + logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line); } void Logger::logf(const char *p_format, ...) { diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index b24c49f58d..9ba653e1a9 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -503,7 +503,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int ERR_FAIL_COND_V((size_t)len < sizeof(double) * 16, ERR_INVALID_DATA); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - val.matrix[i][j] = decode_double(&buf[(i * 4 + j) * sizeof(double)]); + val.columns[i][j] = decode_double(&buf[(i * 4 + j) * sizeof(double)]); } } if (r_len) { @@ -513,7 +513,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int ERR_FAIL_COND_V((size_t)len < sizeof(float) * 16, ERR_INVALID_DATA); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - val.matrix[i][j] = decode_float(&buf[(i * 4 + j) * sizeof(float)]); + val.columns[i][j] = decode_float(&buf[(i * 4 + j) * sizeof(float)]); } } @@ -1450,7 +1450,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Projection val = p_variant; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - memcpy(&buf[(i * 4 + j) * sizeof(real_t)], &val.matrix[i][j], sizeof(real_t)); + memcpy(&buf[(i * 4 + j) * sizeof(real_t)], &val.columns[i][j], sizeof(real_t)); } } } diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 36fa77626e..9ab45f85b8 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -327,22 +327,22 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } break; case VARIANT_PROJECTION: { Projection v; - v.matrix[0].x = f->get_real(); - v.matrix[0].y = f->get_real(); - v.matrix[0].z = f->get_real(); - v.matrix[0].w = f->get_real(); - v.matrix[1].x = f->get_real(); - v.matrix[1].y = f->get_real(); - v.matrix[1].z = f->get_real(); - v.matrix[1].w = f->get_real(); - v.matrix[2].x = f->get_real(); - v.matrix[2].y = f->get_real(); - v.matrix[2].z = f->get_real(); - v.matrix[2].w = f->get_real(); - v.matrix[3].x = f->get_real(); - v.matrix[3].y = f->get_real(); - v.matrix[3].z = f->get_real(); - v.matrix[3].w = f->get_real(); + v.columns[0].x = f->get_real(); + v.columns[0].y = f->get_real(); + v.columns[0].z = f->get_real(); + v.columns[0].w = f->get_real(); + v.columns[1].x = f->get_real(); + v.columns[1].y = f->get_real(); + v.columns[1].z = f->get_real(); + v.columns[1].w = f->get_real(); + v.columns[2].x = f->get_real(); + v.columns[2].y = f->get_real(); + v.columns[2].z = f->get_real(); + v.columns[2].w = f->get_real(); + v.columns[3].x = f->get_real(); + v.columns[3].y = f->get_real(); + v.columns[3].z = f->get_real(); + v.columns[3].w = f->get_real(); r_v = v; } break; case VARIANT_COLOR: { @@ -1630,22 +1630,22 @@ void ResourceFormatSaverBinaryInstance::write_variant(Ref<FileAccess> f, const V case Variant::PROJECTION: { f->store_32(VARIANT_PROJECTION); Projection val = p_property; - f->store_real(val.matrix[0].x); - f->store_real(val.matrix[0].y); - f->store_real(val.matrix[0].z); - f->store_real(val.matrix[0].w); - f->store_real(val.matrix[1].x); - f->store_real(val.matrix[1].y); - f->store_real(val.matrix[1].z); - f->store_real(val.matrix[1].w); - f->store_real(val.matrix[2].x); - f->store_real(val.matrix[2].y); - f->store_real(val.matrix[2].z); - f->store_real(val.matrix[2].w); - f->store_real(val.matrix[3].x); - f->store_real(val.matrix[3].y); - f->store_real(val.matrix[3].z); - f->store_real(val.matrix[3].w); + f->store_real(val.columns[0].x); + f->store_real(val.columns[0].y); + f->store_real(val.columns[0].z); + f->store_real(val.columns[0].w); + f->store_real(val.columns[1].x); + f->store_real(val.columns[1].y); + f->store_real(val.columns[1].z); + f->store_real(val.columns[1].w); + f->store_real(val.columns[2].x); + f->store_real(val.columns[2].y); + f->store_real(val.columns[2].z); + f->store_real(val.columns[2].w); + f->store_real(val.columns[3].x); + f->store_real(val.columns[3].y); + f->store_real(val.columns[3].z); + f->store_real(val.columns[3].w); } break; case Variant::COLOR: { diff --git a/core/math/aabb.cpp b/core/math/aabb.cpp index 4c89be7f4d..483b0d10ec 100644 --- a/core/math/aabb.cpp +++ b/core/math/aabb.cpp @@ -403,6 +403,7 @@ Variant AABB::intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to } return Variant(); } + Variant AABB::intersects_ray_bind(const Vector3 &p_from, const Vector3 &p_dir) const { Vector3 inters; if (intersects_ray(p_from, p_dir, &inters)) { diff --git a/core/math/aabb.h b/core/math/aabb.h index acf903eeba..dfeb5b3291 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -101,7 +101,7 @@ struct _NO_DISCARD_ AABB { _FORCE_INLINE_ void expand_to(const Vector3 &p_vector); /** expand to contain a point if necessary */ _FORCE_INLINE_ AABB abs() const { - return AABB(Vector3(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0), position.z + MIN(size.z, 0)), size.abs()); + return AABB(Vector3(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0), position.z + MIN(size.z, (real_t)0)), size.abs()); } Variant intersects_segment_bind(const Vector3 &p_from, const Vector3 &p_to) const; diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 4b163409ce..743a206ae7 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -142,8 +142,8 @@ bool Basis::is_symmetric() const { #endif Basis Basis::diagonalize() { -//NOTE: only implemented for symmetric matrices -//with the Jacobi iterative method +// NOTE: only implemented for symmetric matrices +// with the Jacobi iterative method #ifdef MATH_CHECKS ERR_FAIL_COND_V(!is_symmetric(), Basis()); #endif diff --git a/core/math/color.h b/core/math/color.h index 65036f74cc..bb8aa9a529 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -32,7 +32,8 @@ #define COLOR_H #include "core/math/math_funcs.h" -#include "core/string/ustring.h" + +class String; struct _NO_DISCARD_ Color { union { diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h index 898c3c2d91..13d93d7f67 100644 --- a/core/math/delaunay_3d.h +++ b/core/math/delaunay_3d.h @@ -186,25 +186,25 @@ class Delaunay3D { Projection cm; - cm.matrix[0][0] = p_points[p_simplex.points[0]].x; - cm.matrix[0][1] = p_points[p_simplex.points[1]].x; - cm.matrix[0][2] = p_points[p_simplex.points[2]].x; - cm.matrix[0][3] = p_points[p_simplex.points[3]].x; - - cm.matrix[1][0] = p_points[p_simplex.points[0]].y; - cm.matrix[1][1] = p_points[p_simplex.points[1]].y; - cm.matrix[1][2] = p_points[p_simplex.points[2]].y; - cm.matrix[1][3] = p_points[p_simplex.points[3]].y; - - cm.matrix[2][0] = p_points[p_simplex.points[0]].z; - cm.matrix[2][1] = p_points[p_simplex.points[1]].z; - cm.matrix[2][2] = p_points[p_simplex.points[2]].z; - cm.matrix[2][3] = p_points[p_simplex.points[3]].z; - - cm.matrix[3][0] = 1.0; - cm.matrix[3][1] = 1.0; - cm.matrix[3][2] = 1.0; - cm.matrix[3][3] = 1.0; + cm.columns[0][0] = p_points[p_simplex.points[0]].x; + cm.columns[0][1] = p_points[p_simplex.points[1]].x; + cm.columns[0][2] = p_points[p_simplex.points[2]].x; + cm.columns[0][3] = p_points[p_simplex.points[3]].x; + + cm.columns[1][0] = p_points[p_simplex.points[0]].y; + cm.columns[1][1] = p_points[p_simplex.points[1]].y; + cm.columns[1][2] = p_points[p_simplex.points[2]].y; + cm.columns[1][3] = p_points[p_simplex.points[3]].y; + + cm.columns[2][0] = p_points[p_simplex.points[0]].z; + cm.columns[2][1] = p_points[p_simplex.points[1]].z; + cm.columns[2][2] = p_points[p_simplex.points[2]].z; + cm.columns[2][3] = p_points[p_simplex.points[3]].z; + + cm.columns[3][0] = 1.0; + cm.columns[3][1] = 1.0; + cm.columns[3][2] = 1.0; + cm.columns[3][3] = 1.0; return ABS(cm.determinant()) <= CMP_EPSILON; } diff --git a/core/math/math_fieldwise.cpp b/core/math/math_fieldwise.cpp index 726a2aeb97..7b30b9a98c 100644 --- a/core/math/math_fieldwise.cpp +++ b/core/math/math_fieldwise.cpp @@ -215,22 +215,22 @@ Variant fieldwise_assign(const Variant &p_target, const Variant &p_source, const case Variant::PROJECTION: { SETUP_TYPE(Projection) - /**/ TRY_TRANSFER_FIELD("xx", matrix[0].x) - else TRY_TRANSFER_FIELD("xy", matrix[0].y) - else TRY_TRANSFER_FIELD("xz", matrix[0].z) - else TRY_TRANSFER_FIELD("xw", matrix[0].w) - else TRY_TRANSFER_FIELD("yx", matrix[1].x) - else TRY_TRANSFER_FIELD("yy", matrix[1].y) - else TRY_TRANSFER_FIELD("yz", matrix[1].z) - else TRY_TRANSFER_FIELD("yw", matrix[1].w) - else TRY_TRANSFER_FIELD("zx", matrix[2].x) - else TRY_TRANSFER_FIELD("zy", matrix[2].y) - else TRY_TRANSFER_FIELD("zz", matrix[2].z) - else TRY_TRANSFER_FIELD("zw", matrix[2].w) - else TRY_TRANSFER_FIELD("xo", matrix[3].x) - else TRY_TRANSFER_FIELD("yo", matrix[3].y) - else TRY_TRANSFER_FIELD("zo", matrix[3].z) - else TRY_TRANSFER_FIELD("wo", matrix[3].w) + /**/ TRY_TRANSFER_FIELD("xx", columns[0].x) + else TRY_TRANSFER_FIELD("xy", columns[0].y) + else TRY_TRANSFER_FIELD("xz", columns[0].z) + else TRY_TRANSFER_FIELD("xw", columns[0].w) + else TRY_TRANSFER_FIELD("yx", columns[1].x) + else TRY_TRANSFER_FIELD("yy", columns[1].y) + else TRY_TRANSFER_FIELD("yz", columns[1].z) + else TRY_TRANSFER_FIELD("yw", columns[1].w) + else TRY_TRANSFER_FIELD("zx", columns[2].x) + else TRY_TRANSFER_FIELD("zy", columns[2].y) + else TRY_TRANSFER_FIELD("zz", columns[2].z) + else TRY_TRANSFER_FIELD("zw", columns[2].w) + else TRY_TRANSFER_FIELD("xo", columns[3].x) + else TRY_TRANSFER_FIELD("yo", columns[3].y) + else TRY_TRANSFER_FIELD("zo", columns[3].z) + else TRY_TRANSFER_FIELD("wo", columns[3].w) return target; } diff --git a/core/math/plane.cpp b/core/math/plane.cpp index 6881ad4014..3b2eab4ae2 100644 --- a/core/math/plane.cpp +++ b/core/math/plane.cpp @@ -147,6 +147,7 @@ Variant Plane::intersect_3_bind(const Plane &p_plane1, const Plane &p_plane2) co return Variant(); } } + Variant Plane::intersects_ray_bind(const Vector3 &p_from, const Vector3 &p_dir) const { Vector3 inters; if (intersects_ray(p_from, p_dir, &inters)) { @@ -155,6 +156,7 @@ Variant Plane::intersects_ray_bind(const Vector3 &p_from, const Vector3 &p_dir) return Variant(); } } + Variant Plane::intersects_segment_bind(const Vector3 &p_begin, const Vector3 &p_end) const { Vector3 inters; if (intersects_segment(p_begin, p_end, &inters)) { diff --git a/core/math/projection.cpp b/core/math/projection.cpp index 863fe6ee79..c628bf0bde 100644 --- a/core/math/projection.cpp +++ b/core/math/projection.cpp @@ -38,24 +38,24 @@ #include "core/string/print_string.h" float Projection::determinant() const { - return matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] - matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] - - matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] + matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] + - matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] - matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] - - matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] + matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] + - matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] - matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] - - matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] + matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] + - matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] - matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] - - matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] + matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] + - matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] - matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] - - matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] + matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] + - matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] - matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] - - matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] + matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3]; + return columns[0][3] * columns[1][2] * columns[2][1] * columns[3][0] - columns[0][2] * columns[1][3] * columns[2][1] * columns[3][0] - + columns[0][3] * columns[1][1] * columns[2][2] * columns[3][0] + columns[0][1] * columns[1][3] * columns[2][2] * columns[3][0] + + columns[0][2] * columns[1][1] * columns[2][3] * columns[3][0] - columns[0][1] * columns[1][2] * columns[2][3] * columns[3][0] - + columns[0][3] * columns[1][2] * columns[2][0] * columns[3][1] + columns[0][2] * columns[1][3] * columns[2][0] * columns[3][1] + + columns[0][3] * columns[1][0] * columns[2][2] * columns[3][1] - columns[0][0] * columns[1][3] * columns[2][2] * columns[3][1] - + columns[0][2] * columns[1][0] * columns[2][3] * columns[3][1] + columns[0][0] * columns[1][2] * columns[2][3] * columns[3][1] + + columns[0][3] * columns[1][1] * columns[2][0] * columns[3][2] - columns[0][1] * columns[1][3] * columns[2][0] * columns[3][2] - + columns[0][3] * columns[1][0] * columns[2][1] * columns[3][2] + columns[0][0] * columns[1][3] * columns[2][1] * columns[3][2] + + columns[0][1] * columns[1][0] * columns[2][3] * columns[3][2] - columns[0][0] * columns[1][1] * columns[2][3] * columns[3][2] - + columns[0][2] * columns[1][1] * columns[2][0] * columns[3][3] + columns[0][1] * columns[1][2] * columns[2][0] * columns[3][3] + + columns[0][2] * columns[1][0] * columns[2][1] * columns[3][3] - columns[0][0] * columns[1][2] * columns[2][1] * columns[3][3] - + columns[0][1] * columns[1][0] * columns[2][2] * columns[3][3] + columns[0][0] * columns[1][1] * columns[2][2] * columns[3][3]; } void Projection::set_identity() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - matrix[i][j] = (i == j) ? 1 : 0; + columns[i][j] = (i == j) ? 1 : 0; } } } @@ -63,7 +63,7 @@ void Projection::set_identity() { void Projection::set_zero() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - matrix[i][j] = 0; + columns[i][j] = 0; } } } @@ -71,26 +71,26 @@ void Projection::set_zero() { Plane Projection::xform4(const Plane &p_vec4) const { Plane ret; - ret.normal.x = matrix[0][0] * p_vec4.normal.x + matrix[1][0] * p_vec4.normal.y + matrix[2][0] * p_vec4.normal.z + matrix[3][0] * p_vec4.d; - ret.normal.y = matrix[0][1] * p_vec4.normal.x + matrix[1][1] * p_vec4.normal.y + matrix[2][1] * p_vec4.normal.z + matrix[3][1] * p_vec4.d; - ret.normal.z = matrix[0][2] * p_vec4.normal.x + matrix[1][2] * p_vec4.normal.y + matrix[2][2] * p_vec4.normal.z + matrix[3][2] * p_vec4.d; - ret.d = matrix[0][3] * p_vec4.normal.x + matrix[1][3] * p_vec4.normal.y + matrix[2][3] * p_vec4.normal.z + matrix[3][3] * p_vec4.d; + ret.normal.x = columns[0][0] * p_vec4.normal.x + columns[1][0] * p_vec4.normal.y + columns[2][0] * p_vec4.normal.z + columns[3][0] * p_vec4.d; + ret.normal.y = columns[0][1] * p_vec4.normal.x + columns[1][1] * p_vec4.normal.y + columns[2][1] * p_vec4.normal.z + columns[3][1] * p_vec4.d; + ret.normal.z = columns[0][2] * p_vec4.normal.x + columns[1][2] * p_vec4.normal.y + columns[2][2] * p_vec4.normal.z + columns[3][2] * p_vec4.d; + ret.d = columns[0][3] * p_vec4.normal.x + columns[1][3] * p_vec4.normal.y + columns[2][3] * p_vec4.normal.z + columns[3][3] * p_vec4.d; return ret; } Vector4 Projection::xform(const Vector4 &p_vec4) const { return Vector4( - matrix[0][0] * p_vec4.x + matrix[1][0] * p_vec4.y + matrix[2][0] * p_vec4.z + matrix[3][0] * p_vec4.w, - matrix[0][1] * p_vec4.x + matrix[1][1] * p_vec4.y + matrix[2][1] * p_vec4.z + matrix[3][1] * p_vec4.w, - matrix[0][2] * p_vec4.x + matrix[1][2] * p_vec4.y + matrix[2][2] * p_vec4.z + matrix[3][2] * p_vec4.w, - matrix[0][3] * p_vec4.x + matrix[1][3] * p_vec4.y + matrix[2][3] * p_vec4.z + matrix[3][3] * p_vec4.w); + columns[0][0] * p_vec4.x + columns[1][0] * p_vec4.y + columns[2][0] * p_vec4.z + columns[3][0] * p_vec4.w, + columns[0][1] * p_vec4.x + columns[1][1] * p_vec4.y + columns[2][1] * p_vec4.z + columns[3][1] * p_vec4.w, + columns[0][2] * p_vec4.x + columns[1][2] * p_vec4.y + columns[2][2] * p_vec4.z + columns[3][2] * p_vec4.w, + columns[0][3] * p_vec4.x + columns[1][3] * p_vec4.y + columns[2][3] * p_vec4.z + columns[3][3] * p_vec4.w); } Vector4 Projection::xform_inv(const Vector4 &p_vec4) const { return Vector4( - matrix[0][0] * p_vec4.x + matrix[0][1] * p_vec4.y + matrix[0][2] * p_vec4.z + matrix[0][3] * p_vec4.w, - matrix[1][0] * p_vec4.x + matrix[1][1] * p_vec4.y + matrix[1][2] * p_vec4.z + matrix[1][3] * p_vec4.w, - matrix[2][0] * p_vec4.x + matrix[2][1] * p_vec4.y + matrix[2][2] * p_vec4.z + matrix[2][3] * p_vec4.w, - matrix[3][0] * p_vec4.x + matrix[3][1] * p_vec4.y + matrix[3][2] * p_vec4.z + matrix[3][3] * p_vec4.w); + columns[0][0] * p_vec4.x + columns[0][1] * p_vec4.y + columns[0][2] * p_vec4.z + columns[0][3] * p_vec4.w, + columns[1][0] * p_vec4.x + columns[1][1] * p_vec4.y + columns[1][2] * p_vec4.z + columns[1][3] * p_vec4.w, + columns[2][0] * p_vec4.x + columns[2][1] * p_vec4.y + columns[2][2] * p_vec4.z + columns[2][3] * p_vec4.w, + columns[3][0] * p_vec4.x + columns[3][1] * p_vec4.y + columns[3][2] * p_vec4.z + columns[3][3] * p_vec4.w); } void Projection::adjust_perspective_znear(real_t p_new_znear) { @@ -98,8 +98,8 @@ void Projection::adjust_perspective_znear(real_t p_new_znear) { real_t znear = p_new_znear; real_t deltaZ = zfar - znear; - matrix[2][2] = -(zfar + znear) / deltaZ; - matrix[3][2] = -2 * znear * zfar / deltaZ; + columns[2][2] = -(zfar + znear) / deltaZ; + columns[3][2] = -2 * znear * zfar / deltaZ; } Projection Projection::create_depth_correction(bool p_flip_y) { @@ -169,7 +169,7 @@ Projection Projection::perspective_znear_adjusted(real_t p_new_znear) const { } Plane Projection::get_projection_plane(Planes p_plane) const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; switch (p_plane) { case PLANE_NEAR: { @@ -267,12 +267,12 @@ void Projection::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t set_identity(); - matrix[0][0] = cotangent / p_aspect; - matrix[1][1] = cotangent; - matrix[2][2] = -(p_z_far + p_z_near) / deltaZ; - matrix[2][3] = -1; - matrix[3][2] = -2 * p_z_near * p_z_far / deltaZ; - matrix[3][3] = 0; + columns[0][0] = cotangent / p_aspect; + columns[1][1] = cotangent; + columns[2][2] = -(p_z_far + p_z_near) / deltaZ; + columns[2][3] = -1; + columns[3][2] = -2 * p_z_near * p_z_far / deltaZ; + columns[3][3] = 0; } void Projection::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov, int p_eye, real_t p_intraocular_dist, real_t p_convergence_dist) { @@ -309,7 +309,7 @@ void Projection::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t // translate matrix by (modeltranslation, 0.0, 0.0) Projection cm; cm.set_identity(); - cm.matrix[3][0] = modeltranslation; + cm.columns[3][0] = modeltranslation; *this = *this * cm; } @@ -344,13 +344,13 @@ void Projection::set_for_hmd(int p_eye, real_t p_aspect, real_t p_intraocular_di void Projection::set_orthogonal(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_znear, real_t p_zfar) { set_identity(); - matrix[0][0] = 2.0 / (p_right - p_left); - matrix[3][0] = -((p_right + p_left) / (p_right - p_left)); - matrix[1][1] = 2.0 / (p_top - p_bottom); - matrix[3][1] = -((p_top + p_bottom) / (p_top - p_bottom)); - matrix[2][2] = -2.0 / (p_zfar - p_znear); - matrix[3][2] = -((p_zfar + p_znear) / (p_zfar - p_znear)); - matrix[3][3] = 1.0; + columns[0][0] = 2.0 / (p_right - p_left); + columns[3][0] = -((p_right + p_left) / (p_right - p_left)); + columns[1][1] = 2.0 / (p_top - p_bottom); + columns[3][1] = -((p_top + p_bottom) / (p_top - p_bottom)); + columns[2][2] = -2.0 / (p_zfar - p_znear); + columns[3][2] = -((p_zfar + p_znear) / (p_zfar - p_znear)); + columns[3][3] = 1.0; } void Projection::set_orthogonal(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov) { @@ -366,7 +366,7 @@ void Projection::set_frustum(real_t p_left, real_t p_right, real_t p_bottom, rea ERR_FAIL_COND(p_top <= p_bottom); ERR_FAIL_COND(p_far <= p_near); - real_t *te = &matrix[0][0]; + real_t *te = &columns[0][0]; real_t x = 2 * p_near / (p_right - p_left); real_t y = 2 * p_near / (p_top - p_bottom); @@ -402,7 +402,7 @@ void Projection::set_frustum(real_t p_size, real_t p_aspect, Vector2 p_offset, r } real_t Projection::get_z_far() const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; Plane new_plane = Plane(matrix[3] - matrix[2], matrix[7] - matrix[6], matrix[11] - matrix[10], @@ -415,7 +415,7 @@ real_t Projection::get_z_far() const { } real_t Projection::get_z_near() const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; Plane new_plane = Plane(matrix[3] + matrix[2], matrix[7] + matrix[6], matrix[11] + matrix[10], @@ -426,7 +426,7 @@ real_t Projection::get_z_near() const { } Vector2 Projection::get_viewport_half_extents() const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; ///////--- Near Plane ---/////// Plane near_plane = Plane(matrix[3] + matrix[2], matrix[7] + matrix[6], @@ -454,7 +454,7 @@ Vector2 Projection::get_viewport_half_extents() const { } Vector2 Projection::get_far_plane_half_extents() const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; ///////--- Far Plane ---/////// Plane far_plane = Plane(matrix[3] - matrix[2], matrix[7] - matrix[6], @@ -514,7 +514,7 @@ Vector<Plane> Projection::get_projection_planes(const Transform3D &p_transform) Vector<Plane> planes; planes.resize(6); - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; Plane new_plane; @@ -601,15 +601,15 @@ void Projection::invert() { real_t determinant = 1.0f; for (k = 0; k < 4; k++) { /** Locate k'th pivot element **/ - pvt_val = matrix[k][k]; /** Initialize for search **/ + pvt_val = columns[k][k]; /** Initialize for search **/ pvt_i[k] = k; pvt_j[k] = k; for (i = k; i < 4; i++) { for (j = k; j < 4; j++) { - if (Math::abs(matrix[i][j]) > Math::abs(pvt_val)) { + if (Math::abs(columns[i][j]) > Math::abs(pvt_val)) { pvt_i[k] = i; pvt_j[k] = j; - pvt_val = matrix[i][j]; + pvt_val = columns[i][j]; } } } @@ -624,9 +624,9 @@ void Projection::invert() { i = pvt_i[k]; if (i != k) { /** If rows are different **/ for (j = 0; j < 4; j++) { - hold = -matrix[k][j]; - matrix[k][j] = matrix[i][j]; - matrix[i][j] = hold; + hold = -columns[k][j]; + columns[k][j] = columns[i][j]; + columns[i][j] = hold; } } @@ -634,25 +634,25 @@ void Projection::invert() { j = pvt_j[k]; if (j != k) { /** If columns are different **/ for (i = 0; i < 4; i++) { - hold = -matrix[i][k]; - matrix[i][k] = matrix[i][j]; - matrix[i][j] = hold; + hold = -columns[i][k]; + columns[i][k] = columns[i][j]; + columns[i][j] = hold; } } /** Divide column by minus pivot value **/ for (i = 0; i < 4; i++) { if (i != k) { - matrix[i][k] /= (-pvt_val); + columns[i][k] /= (-pvt_val); } } /** Reduce the matrix **/ for (i = 0; i < 4; i++) { - hold = matrix[i][k]; + hold = columns[i][k]; for (j = 0; j < 4; j++) { if (i != k && j != k) { - matrix[i][j] += hold * matrix[k][j]; + columns[i][j] += hold * columns[k][j]; } } } @@ -660,12 +660,12 @@ void Projection::invert() { /** Divide row by pivot **/ for (j = 0; j < 4; j++) { if (j != k) { - matrix[k][j] /= pvt_val; + columns[k][j] /= pvt_val; } } /** Replace pivot by reciprocal (at last we can touch it). **/ - matrix[k][k] = 1.0 / pvt_val; + columns[k][k] = 1.0 / pvt_val; } /* That was most of the work, one final pass of row/column interchange */ @@ -674,18 +674,18 @@ void Projection::invert() { i = pvt_j[k]; /* Rows to swap correspond to pivot COLUMN */ if (i != k) { /* If rows are different */ for (j = 0; j < 4; j++) { - hold = matrix[k][j]; - matrix[k][j] = -matrix[i][j]; - matrix[i][j] = hold; + hold = columns[k][j]; + columns[k][j] = -columns[i][j]; + columns[i][j] = hold; } } j = pvt_i[k]; /* Columns to swap correspond to pivot ROW */ if (j != k) { /* If columns are different */ for (i = 0; i < 4; i++) { - hold = matrix[i][k]; - matrix[i][k] = -matrix[i][j]; - matrix[i][j] = hold; + hold = columns[i][k]; + columns[i][k] = -columns[i][j]; + columns[i][j] = hold; } } } @@ -693,7 +693,7 @@ void Projection::invert() { void Projection::flip_y() { for (int i = 0; i < 4; i++) { - matrix[1][i] = -matrix[1][i]; + columns[1][i] = -columns[1][i]; } } @@ -708,9 +708,9 @@ Projection Projection::operator*(const Projection &p_matrix) const { for (int i = 0; i < 4; i++) { real_t ab = 0; for (int k = 0; k < 4; k++) { - ab += matrix[k][i] * p_matrix.matrix[j][k]; + ab += columns[k][i] * p_matrix.columns[j][k]; } - new_matrix.matrix[j][i] = ab; + new_matrix.columns[j][i] = ab; } } @@ -718,7 +718,7 @@ Projection Projection::operator*(const Projection &p_matrix) const { } void Projection::set_depth_correction(bool p_flip_y) { - real_t *m = &matrix[0][0]; + real_t *m = &columns[0][0]; m[0] = 1; m[1] = 0.0; @@ -739,7 +739,7 @@ void Projection::set_depth_correction(bool p_flip_y) { } void Projection::set_light_bias() { - real_t *m = &matrix[0][0]; + real_t *m = &columns[0][0]; m[0] = 0.5; m[1] = 0.0; @@ -760,7 +760,7 @@ void Projection::set_light_bias() { } void Projection::set_light_atlas_rect(const Rect2 &p_rect) { - real_t *m = &matrix[0][0]; + real_t *m = &columns[0][0]; m[0] = p_rect.size.width; m[1] = 0.0; @@ -784,7 +784,7 @@ Projection::operator String() const { String str; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - str += String((j > 0) ? ", " : "\n") + rtos(matrix[i][j]); + str += String((j > 0) ? ", " : "\n") + rtos(columns[i][j]); } } @@ -803,11 +803,11 @@ int Projection::get_pixels_per_meter(int p_for_pixel_width) const { } bool Projection::is_orthogonal() const { - return matrix[3][3] == 1.0; + return columns[3][3] == 1.0; } real_t Projection::get_fov() const { - const real_t *matrix = (const real_t *)this->matrix; + const real_t *matrix = (const real_t *)this->columns; Plane right_plane = Plane(matrix[3] - matrix[0], matrix[7] - matrix[4], @@ -838,48 +838,49 @@ float Projection::get_lod_multiplier() const { return 1.0 / (zn / width); } - //usage is lod_size / (lod_distance * multiplier) < threshold + // Usage is lod_size / (lod_distance * multiplier) < threshold } + void Projection::make_scale(const Vector3 &p_scale) { set_identity(); - matrix[0][0] = p_scale.x; - matrix[1][1] = p_scale.y; - matrix[2][2] = p_scale.z; + columns[0][0] = p_scale.x; + columns[1][1] = p_scale.y; + columns[2][2] = p_scale.z; } void Projection::scale_translate_to_fit(const AABB &p_aabb) { Vector3 min = p_aabb.position; Vector3 max = p_aabb.position + p_aabb.size; - matrix[0][0] = 2 / (max.x - min.x); - matrix[1][0] = 0; - matrix[2][0] = 0; - matrix[3][0] = -(max.x + min.x) / (max.x - min.x); + columns[0][0] = 2 / (max.x - min.x); + columns[1][0] = 0; + columns[2][0] = 0; + columns[3][0] = -(max.x + min.x) / (max.x - min.x); - matrix[0][1] = 0; - matrix[1][1] = 2 / (max.y - min.y); - matrix[2][1] = 0; - matrix[3][1] = -(max.y + min.y) / (max.y - min.y); + columns[0][1] = 0; + columns[1][1] = 2 / (max.y - min.y); + columns[2][1] = 0; + columns[3][1] = -(max.y + min.y) / (max.y - min.y); - matrix[0][2] = 0; - matrix[1][2] = 0; - matrix[2][2] = 2 / (max.z - min.z); - matrix[3][2] = -(max.z + min.z) / (max.z - min.z); + columns[0][2] = 0; + columns[1][2] = 0; + columns[2][2] = 2 / (max.z - min.z); + columns[3][2] = -(max.z + min.z) / (max.z - min.z); - matrix[0][3] = 0; - matrix[1][3] = 0; - matrix[2][3] = 0; - matrix[3][3] = 1; + columns[0][3] = 0; + columns[1][3] = 0; + columns[2][3] = 0; + columns[3][3] = 1; } void Projection::add_jitter_offset(const Vector2 &p_offset) { - matrix[3][0] += p_offset.x; - matrix[3][1] += p_offset.y; + columns[3][0] += p_offset.x; + columns[3][1] += p_offset.y; } Projection::operator Transform3D() const { Transform3D tr; - const real_t *m = &matrix[0][0]; + const real_t *m = &columns[0][0]; tr.basis.rows[0][0] = m[0]; tr.basis.rows[1][0] = m[1]; @@ -899,15 +900,17 @@ Projection::operator Transform3D() const { return tr; } + Projection::Projection(const Vector4 &p_x, const Vector4 &p_y, const Vector4 &p_z, const Vector4 &p_w) { - matrix[0] = p_x; - matrix[1] = p_y; - matrix[2] = p_z; - matrix[3] = p_w; + columns[0] = p_x; + columns[1] = p_y; + columns[2] = p_z; + columns[3] = p_w; } + Projection::Projection(const Transform3D &p_transform) { const Transform3D &tr = p_transform; - real_t *m = &matrix[0][0]; + real_t *m = &columns[0][0]; m[0] = tr.basis.rows[0][0]; m[1] = tr.basis.rows[1][0]; diff --git a/core/math/projection.h b/core/math/projection.h index f149d307b3..38fb9781ae 100644 --- a/core/math/projection.h +++ b/core/math/projection.h @@ -31,10 +31,11 @@ #ifndef PROJECTION_H #define PROJECTION_H -#include "core/math/math_defs.h" #include "core/math/vector3.h" #include "core/math/vector4.h" -#include "core/templates/vector.h" + +template <class T> +class Vector; struct AABB; struct Plane; @@ -42,7 +43,7 @@ struct Rect2; struct Transform3D; struct Vector2; -struct Projection { +struct _NO_DISCARD_ Projection { enum Planes { PLANE_NEAR, PLANE_FAR, @@ -52,16 +53,16 @@ struct Projection { PLANE_BOTTOM }; - Vector4 matrix[4]; + Vector4 columns[4]; _FORCE_INLINE_ const Vector4 &operator[](const int p_axis) const { DEV_ASSERT((unsigned int)p_axis < 4); - return matrix[p_axis]; + return columns[p_axis]; } _FORCE_INLINE_ Vector4 &operator[](const int p_axis) { DEV_ASSERT((unsigned int)p_axis < 4); - return matrix[p_axis]; + return columns[p_axis]; } float determinant() const; @@ -135,7 +136,7 @@ struct Projection { bool operator==(const Projection &p_cam) const { for (uint32_t i = 0; i < 4; i++) { for (uint32_t j = 0; j < 4; j++) { - if (matrix[i][j] != p_cam.matrix[i][j]) { + if (columns[i][j] != p_cam.columns[i][j]) { return false; } } @@ -157,10 +158,10 @@ struct Projection { Vector3 Projection::xform(const Vector3 &p_vec3) const { Vector3 ret; - ret.x = matrix[0][0] * p_vec3.x + matrix[1][0] * p_vec3.y + matrix[2][0] * p_vec3.z + matrix[3][0]; - ret.y = matrix[0][1] * p_vec3.x + matrix[1][1] * p_vec3.y + matrix[2][1] * p_vec3.z + matrix[3][1]; - ret.z = matrix[0][2] * p_vec3.x + matrix[1][2] * p_vec3.y + matrix[2][2] * p_vec3.z + matrix[3][2]; - real_t w = matrix[0][3] * p_vec3.x + matrix[1][3] * p_vec3.y + matrix[2][3] * p_vec3.z + matrix[3][3]; + ret.x = columns[0][0] * p_vec3.x + columns[1][0] * p_vec3.y + columns[2][0] * p_vec3.z + columns[3][0]; + ret.y = columns[0][1] * p_vec3.x + columns[1][1] * p_vec3.y + columns[2][1] * p_vec3.z + columns[3][1]; + ret.z = columns[0][2] * p_vec3.x + columns[1][2] * p_vec3.y + columns[2][2] * p_vec3.z + columns[3][2]; + real_t w = columns[0][3] * p_vec3.x + columns[1][3] * p_vec3.y + columns[2][3] * p_vec3.z + columns[3][3]; return ret / w; } diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 43d7bffcfc..077fe5f189 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -31,10 +31,10 @@ #ifndef QUATERNION_H #define QUATERNION_H -#include "core/math/math_defs.h" #include "core/math/math_funcs.h" #include "core/math/vector3.h" -#include "core/string/ustring.h" + +class String; struct _NO_DISCARD_ Quaternion { union { diff --git a/core/math/rect2.h b/core/math/rect2.h index 2d1be3d4f3..5ed2f8236c 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -281,7 +281,7 @@ struct _NO_DISCARD_ Rect2 { } _FORCE_INLINE_ Rect2 abs() const { - return Rect2(Point2(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs()); + return Rect2(Point2(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0)), size.abs()); } Vector2 get_support(const Vector2 &p_normal) const { diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index c62e4a7b0e..44d6d826f3 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -34,6 +34,7 @@ #include "core/math/aabb.h" #include "core/math/basis.h" #include "core/math/plane.h" +#include "core/templates/vector.h" struct _NO_DISCARD_ Transform3D { Basis basis; diff --git a/core/math/vector4.h b/core/math/vector4.h index 426c473e13..ac7b6c3aee 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -31,10 +31,10 @@ #ifndef VECTOR4_H #define VECTOR4_H -#include "core/math/math_defs.h" +#include "core/error/error_macros.h" #include "core/math/math_funcs.h" -#include "core/math/vector3.h" -#include "core/string/ustring.h" + +class String; struct _NO_DISCARD_ Vector4 { static const int AXIS_COUNT = 4; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 99b20560da..ca56add2ab 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1538,7 +1538,11 @@ void ClassDB::register_extension_class(ObjectNativeExtension *p_extension) { } void ClassDB::unregister_extension_class(const StringName &p_class) { - ERR_FAIL_COND(!classes.has(p_class)); + ClassInfo *c = classes.getptr(p_class); + ERR_FAIL_COND_MSG(!c, "Class " + p_class + "does not exist"); + for (KeyValue<StringName, MethodBind *> &F : c->method_map) { + memdelete(F.value); + } classes.erase(p_class); } diff --git a/core/object/object.h b/core/object/object.h index 33a5afc9aa..5ba5453b31 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -50,7 +50,7 @@ class TypedArray; enum PropertyHint { PROPERTY_HINT_NONE, ///< no hint provided. - PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,no_slider][,radians][,degrees][,exp][,suffix:<keyword>] range. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,hide_slider][,radians][,degrees][,exp][,suffix:<keyword>] range. PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "positive_only" to exclude in-out and out-in. (ie: "attenuation,positive_only") diff --git a/core/os/os.cpp b/core/os/os.cpp index 72d68893f3..bbb2a94fe7 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -38,6 +38,7 @@ #include "core/version_generated.gen.h" #include <stdarg.h> +#include <thread> OS *OS::singleton = nullptr; uint64_t OS::target_ticks = 0; @@ -321,7 +322,7 @@ String OS::get_unique_id() const { } int OS::get_processor_count() const { - return 1; + return std::thread::hardware_concurrency(); } String OS::get_processor_name() const { diff --git a/core/os/thread.cpp b/core/os/thread.cpp index e8ebb4597f..712f4793eb 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -36,10 +36,7 @@ #include "core/object/script_language.h" #include "core/templates/safe_refcount.h" -Error (*Thread::set_name_func)(const String &) = nullptr; -void (*Thread::set_priority_func)(Thread::Priority) = nullptr; -void (*Thread::init_func)() = nullptr; -void (*Thread::term_func)() = nullptr; +Thread::PlatformFunctions Thread::platform_functions; uint64_t Thread::_thread_id_hash(const std::thread::id &p_t) { static std::hash<std::thread::id> hasher; @@ -49,30 +46,27 @@ uint64_t Thread::_thread_id_hash(const std::thread::id &p_t) { Thread::ID Thread::main_thread_id = _thread_id_hash(std::this_thread::get_id()); thread_local Thread::ID Thread::caller_id = 0; -void Thread::_set_platform_funcs( - Error (*p_set_name_func)(const String &), - void (*p_set_priority_func)(Thread::Priority), - void (*p_init_func)(), - void (*p_term_func)()) { - Thread::set_name_func = p_set_name_func; - Thread::set_priority_func = p_set_priority_func; - Thread::init_func = p_init_func; - Thread::term_func = p_term_func; +void Thread::_set_platform_functions(const PlatformFunctions &p_functions) { + platform_functions = p_functions; } void Thread::callback(Thread *p_self, const Settings &p_settings, Callback p_callback, void *p_userdata) { Thread::caller_id = _thread_id_hash(p_self->thread.get_id()); - if (set_priority_func) { - set_priority_func(p_settings.priority); + if (platform_functions.set_priority) { + platform_functions.set_priority(p_settings.priority); } - if (init_func) { - init_func(); + if (platform_functions.init) { + platform_functions.init(); + } + ScriptServer::thread_enter(); // Scripts may need to attach a stack. + if (platform_functions.wrapper) { + platform_functions.wrapper(p_callback, p_userdata); + } else { + p_callback(p_userdata); } - ScriptServer::thread_enter(); //scripts may need to attach a stack - p_callback(p_userdata); ScriptServer::thread_exit(); - if (term_func) { - term_func(); + if (platform_functions.term) { + platform_functions.term(); } } @@ -105,8 +99,8 @@ void Thread::wait_to_finish() { } Error Thread::set_name(const String &p_name) { - if (set_name_func) { - return set_name_func(p_name); + if (platform_functions.set_name) { + return platform_functions.set_name(p_name); } return ERR_UNAVAILABLE; diff --git a/core/os/thread.h b/core/os/thread.h index 7462ec17fb..86442de760 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -63,6 +63,14 @@ public: Settings() { priority = PRIORITY_NORMAL; } }; + struct PlatformFunctions { + Error (*set_name)(const String &) = nullptr; + void (*set_priority)(Thread::Priority) = nullptr; + void (*init)() = nullptr; + void (*wrapper)(Thread::Callback, void *) = nullptr; + void (*term)() = nullptr; + }; + private: friend class Main; @@ -76,17 +84,10 @@ private: static void callback(Thread *p_self, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); - static Error (*set_name_func)(const String &); - static void (*set_priority_func)(Thread::Priority); - static void (*init_func)(); - static void (*term_func)(); + static PlatformFunctions platform_functions; public: - static void _set_platform_funcs( - Error (*p_set_name_func)(const String &), - void (*p_set_priority_func)(Thread::Priority), - void (*p_init_func)() = nullptr, - void (*p_term_func)() = nullptr); + static void _set_platform_functions(const PlatformFunctions &p_functions); _FORCE_INLINE_ ID get_id() const { return id; } // get the ID of the caller thread diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index f24ffeb1a9..a6e91bf2ac 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1835,11 +1835,13 @@ String Variant::stringify(int recursion_count) const { case DICTIONARY: { const Dictionary &d = *reinterpret_cast<const Dictionary *>(_data._mem); if (recursion_count > MAX_RECURSION) { - ERR_PRINT("Max recursion reached"); - return "{...}"; + ERR_PRINT("Maximum dictionary recursion reached!"); + return "{ ... }"; } - String str("{"); + // Add leading and trailing space to Dictionary printing. This distinguishes it + // from array printing on fonts that have similar-looking {} and [] characters. + String str("{ "); List<Variant> keys; d.get_key_list(&keys); @@ -1858,9 +1860,9 @@ String Variant::stringify(int recursion_count) const { if (i > 0) { str += ", "; } - str += pairs[i].key + ":" + pairs[i].value; + str += pairs[i].key + ": " + pairs[i].value; } - str += "}"; + str += " }"; return str; } break; @@ -1894,7 +1896,7 @@ String Variant::stringify(int recursion_count) const { case ARRAY: { Array arr = operator Array(); if (recursion_count > MAX_RECURSION) { - ERR_PRINT("Max recursion reached"); + ERR_PRINT("Maximum array recursion reached!"); return "[...]"; } @@ -3121,22 +3123,22 @@ uint32_t Variant::recursive_hash(int recursion_count) const { case PROJECTION: { uint32_t h = HASH_MURMUR3_SEED; const Projection &t = *_data._projection; - h = hash_murmur3_one_real(t.matrix[0].x, h); - h = hash_murmur3_one_real(t.matrix[0].y, h); - h = hash_murmur3_one_real(t.matrix[0].z, h); - h = hash_murmur3_one_real(t.matrix[0].w, h); - h = hash_murmur3_one_real(t.matrix[1].x, h); - h = hash_murmur3_one_real(t.matrix[1].y, h); - h = hash_murmur3_one_real(t.matrix[1].z, h); - h = hash_murmur3_one_real(t.matrix[1].w, h); - h = hash_murmur3_one_real(t.matrix[2].x, h); - h = hash_murmur3_one_real(t.matrix[2].y, h); - h = hash_murmur3_one_real(t.matrix[2].z, h); - h = hash_murmur3_one_real(t.matrix[2].w, h); - h = hash_murmur3_one_real(t.matrix[3].x, h); - h = hash_murmur3_one_real(t.matrix[3].y, h); - h = hash_murmur3_one_real(t.matrix[3].z, h); - h = hash_murmur3_one_real(t.matrix[3].w, h); + h = hash_murmur3_one_real(t.columns[0].x, h); + h = hash_murmur3_one_real(t.columns[0].y, h); + h = hash_murmur3_one_real(t.columns[0].z, h); + h = hash_murmur3_one_real(t.columns[0].w, h); + h = hash_murmur3_one_real(t.columns[1].x, h); + h = hash_murmur3_one_real(t.columns[1].y, h); + h = hash_murmur3_one_real(t.columns[1].z, h); + h = hash_murmur3_one_real(t.columns[1].w, h); + h = hash_murmur3_one_real(t.columns[2].x, h); + h = hash_murmur3_one_real(t.columns[2].y, h); + h = hash_murmur3_one_real(t.columns[2].z, h); + h = hash_murmur3_one_real(t.columns[2].w, h); + h = hash_murmur3_one_real(t.columns[3].x, h); + h = hash_murmur3_one_real(t.columns[3].y, h); + h = hash_murmur3_one_real(t.columns[3].z, h); + h = hash_murmur3_one_real(t.columns[3].w, h); return hash_fmix32(h); } break; // misc types @@ -3507,7 +3509,7 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const const Projection *r = p_variant._data._projection; for (int i = 0; i < 4; i++) { - if (!(hash_compare_vector4(l->matrix[i], r->matrix[i]))) { + if (!(hash_compare_vector4(l->columns[i], r->columns[i]))) { return false; } } diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 8151ff2102..d2e4d752a4 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1651,7 +1651,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i != 0 || j != 0) { s += ", "; } - s += rtos_fix(t.matrix[i][j]); + s += rtos_fix(t.columns[i][j]); } } diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index ff67b187ef..188103ee5e 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -826,7 +826,7 @@ INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Color, double, float, 4) INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(Transform2D, Vector2, .columns, 3) INDEXED_SETGET_STRUCT_BULTIN_FUNC(Basis, Vector3, set_column, get_column, 3) -INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(Projection, Vector4, .matrix, 4) +INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(Projection, Vector4, .columns, 4) INDEXED_SETGET_STRUCT_TYPED_NUMERIC(PackedByteArray, int64_t, uint8_t) INDEXED_SETGET_STRUCT_TYPED_NUMERIC(PackedInt32Array, int64_t, int32_t) diff --git a/core/variant/variant_setget.h b/core/variant/variant_setget.h index 570277dc7a..d151a85a6e 100644 --- a/core/variant/variant_setget.h +++ b/core/variant/variant_setget.h @@ -325,10 +325,10 @@ SETGET_STRUCT_FUNC_INDEX(Basis, Vector3, z, set_column, get_column, 2) SETGET_STRUCT(Transform3D, Basis, basis) SETGET_STRUCT(Transform3D, Vector3, origin) -SETGET_STRUCT_CUSTOM(Projection, Vector4, x, matrix[0]) -SETGET_STRUCT_CUSTOM(Projection, Vector4, y, matrix[1]) -SETGET_STRUCT_CUSTOM(Projection, Vector4, z, matrix[2]) -SETGET_STRUCT_CUSTOM(Projection, Vector4, w, matrix[3]) +SETGET_STRUCT_CUSTOM(Projection, Vector4, x, columns[0]) +SETGET_STRUCT_CUSTOM(Projection, Vector4, y, columns[1]) +SETGET_STRUCT_CUSTOM(Projection, Vector4, z, columns[2]) +SETGET_STRUCT_CUSTOM(Projection, Vector4, w, columns[3]) SETGET_NUMBER_STRUCT(Color, double, r) SETGET_NUMBER_STRUCT(Color, double, g) diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 5bb10d162f..1b57569582 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2260,10 +2260,10 @@ Enum value which doesn't correspond to any mouse button. This is used to initialize [enum MouseButton] properties with a generic state. </constant> <constant name="MOUSE_BUTTON_LEFT" value="1" enum="MouseButton"> - Left mouse button. + Primary mouse button, usually the left button. </constant> <constant name="MOUSE_BUTTON_RIGHT" value="2" enum="MouseButton"> - Right mouse button. + Secondary mouse button, usually the right button. </constant> <constant name="MOUSE_BUTTON_MIDDLE" value="3" enum="MouseButton"> Middle mouse button. @@ -2287,10 +2287,10 @@ Extra mouse button 2 (only present on some mice). </constant> <constant name="MOUSE_BUTTON_MASK_LEFT" value="1" enum="MouseButton"> - Left mouse button mask. + Primary mouse button mask, usually for the left button. </constant> <constant name="MOUSE_BUTTON_MASK_RIGHT" value="2" enum="MouseButton"> - Right mouse button mask. + Secondary mouse button mask, usually for the right button. </constant> <constant name="MOUSE_BUTTON_MASK_MIDDLE" value="4" enum="MouseButton"> Middle mouse button mask. @@ -2621,7 +2621,7 @@ </constant> <constant name="PROPERTY_HINT_RANGE" value="1" enum="PropertyHint"> Hints that an integer or float property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_less"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"-360,360,1,or_greater,or_less"[/code]. - Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians"[/code] for editing radian angles in degrees, [code]"degrees"[/code] to hint at an angle and [code]"no_slider"[/code] to hide the slider. + Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians"[/code] for editing radian angles in degrees, [code]"degrees"[/code] to hint at an angle and [code]"hide_slider"[/code] to hide the slider. </constant> <constant name="PROPERTY_HINT_ENUM" value="2" enum="PropertyHint"> Hints that an integer, float or string property is an enumerated value to pick in a list specified via a hint string. diff --git a/doc/classes/AtlasTexture.xml b/doc/classes/AtlasTexture.xml index 759acff773..809d983a9d 100644 --- a/doc/classes/AtlasTexture.xml +++ b/doc/classes/AtlasTexture.xml @@ -1,26 +1,27 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AtlasTexture" inherits="Texture2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Crops out one part of a texture, such as a texture from a texture atlas. + A texture that crops out part of another Texture2D. </brief_description> <description> - [Texture2D] resource that crops out one part of the [member atlas] texture, defined by [member region]. The main use case is cropping out textures from a texture atlas, which is a big texture file that packs multiple smaller textures. Consists of a [Texture2D] for the [member atlas], a [member region] that defines the area of [member atlas] to use, and a [member margin] that defines the border width. - [AtlasTexture] cannot be used in an [AnimatedTexture], cannot be tiled in nodes such as [TextureRect], and does not work properly if used inside of other [AtlasTexture] resources. Multiple [AtlasTexture] resources can be used to crop multiple textures from the atlas. Using a texture atlas helps to optimize video memory costs and render calls compared to using multiple small files. + [Texture2D] resource that draws only part of its [member atlas] texture, as defined by the [member region]. An additional [member margin] can also be set, which is useful for small adjustments. + Multiple [AtlasTexture] resources can be cropped from the same [member atlas]. Packing many smaller textures into a singular large texture helps to optimize video memory costs and render calls. + [b]Note:[/b] [AtlasTexture] cannot be used in an [AnimatedTexture], and may not tile properly in nodes such as [TextureRect], when inside other [AtlasTexture] resources. </description> <tutorials> </tutorials> <members> <member name="atlas" type="Texture2D" setter="set_atlas" getter="get_atlas"> - The texture that contains the atlas. Can be any [Texture2D] subtype. + The texture that contains the atlas. Can be any type inheriting from [Texture2D], including another [AtlasTexture]. </member> <member name="filter_clip" type="bool" setter="set_filter_clip" getter="has_filter_clip" default="false"> - If [code]true[/code], clips the area outside of the region to avoid bleeding of the surrounding texture pixels. + If [code]true[/code], the area outside of the [member region] is clipped to avoid bleeding of the surrounding texture pixels. </member> <member name="margin" type="Rect2" setter="set_margin" getter="get_margin" default="Rect2(0, 0, 0, 0)"> - The margin around the region. The [Rect2]'s [member Rect2.size] parameter ("w" and "h" in the editor) resizes the texture so it fits within the margin. + The margin around the [member region]. Useful for small adjustments. If the [member Rect2.size] of this property ("w" and "h" in the editor) is set, the drawn texture is resized to fit within the margin. </member> <member name="region" type="Rect2" setter="set_region" getter="get_region" default="Rect2(0, 0, 0, 0)"> - The AtlasTexture's used region. + The region used to draw the [member atlas]. </member> </members> </class> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index a1d24f778d..671ecb6af1 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -150,6 +150,13 @@ <member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="Camera2D.Camera2DProcessCallback" default="1"> The camera's process callback. See [enum Camera2DProcessCallback]. </member> + <member name="rotation_smoothing_enabled" type="bool" setter="set_rotation_smoothing_enabled" getter="is_rotation_smoothing_enabled" default="false"> + If [code]true[/code], the camera's view smoothly rotates, via asymptotic smoothing, to align with its target rotation at [member rotation_smoothing_speed]. + [b]Note:[/b] This property has no effect if [member ignore_rotation] is [code]true[/code]. + </member> + <member name="rotation_smoothing_speed" type="float" setter="set_rotation_smoothing_speed" getter="get_rotation_smoothing_speed" default="5.0"> + The angular, asymptotic speed of the camera's rotation smoothing effect when [member rotation_smoothing_enabled] is [code]true[/code]. + </member> <member name="smoothing_enabled" type="bool" setter="set_enable_follow_smoothing" getter="is_follow_smoothing_enabled" default="false"> If [code]true[/code], the camera smoothly moves towards the target at [member smoothing_speed]. </member> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index bf449ade77..9c7cb7b089 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -7,10 +7,10 @@ Base class for all UI-related nodes. [Control] features a bounding rectangle that defines its extents, an anchor position relative to its parent control or the current viewport, and offsets relative to the anchor. The offsets update automatically when the node, any of its parents, or the screen size change. For more information on Godot's UI system, anchors, offsets, and containers, see the related tutorials in the manual. To build flexible UIs, you'll need a mix of UI elements that inherit from [Control] and [Container] nodes. [b]User Interface nodes and input[/b] - Godot sends input events to the scene's root node first, by calling [method Node._input]. [method Node._input] forwards the event down the node tree to the nodes under the mouse cursor, or on keyboard focus. To do so, it calls [code]MainLoop._input_event[/code]. - [b]FIXME:[/b] No longer valid after DisplayServer split and Input refactoring. + Godot propagates input events via viewports. Each [Viewport] is responsible for propagating [InputEvent]s to their child nodes. As the [member SceneTree.root] is a [Window], this already happens automatically for all UI elements in your game. + Input events are propagated through the [SceneTree] from the root node to all child nodes by calling [method Node._input]. For UI elements specifically, it makes more sense to override the virtual method [method _gui_input], which filters out unrelated input events, such as by checking z-order, [member mouse_filter], focus, or if the event was inside of the control's bounding box. Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. - Only one [Control] node can be in keyboard focus. Only the node in focus will receive keyboard events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. + Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. [Theme] resources change the Control's appearance. If you change the [Theme] on a [Control] node, it affects all of its children. To override some of the theme's parameters, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can override the theme with the inspector. [b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class. @@ -680,7 +680,7 @@ <method name="release_focus"> <return type="void" /> <description> - Give up the focus. No other control will be able to receive keyboard input. + Give up the focus. No other control will be able to receive input. </description> </method> <method name="remove_theme_color_override"> @@ -976,26 +976,26 @@ The minimum size of the node's bounding rectangle. If you set it to a value greater than (0, 0), the node's bounding rectangle will always have at least this size, even if its content is smaller. If it's set to (0, 0), the node sizes automatically to fit its content, be it a texture or child nodes. </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" enum="Control.FocusMode" default="0"> - The focus access mode for the control (None, Click or All). Only one Control can be focused at the same time, and it will receive keyboard signals. + The focus access mode for the control (None, Click or All). Only one Control can be focused at the same time, and it will receive keyboard, gamepad, and mouse signals. </member> <member name="focus_neighbor_bottom" type="NodePath" setter="set_focus_neighbor" getter="get_focus_neighbor" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses the down arrow on the keyboard or down on a gamepad by default. You can change the key by editing the [code]ui_down[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. + Tells Godot which node it should give focus to if the user presses the down arrow on the keyboard or down on a gamepad by default. You can change the key by editing the [code]ui_down[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. </member> <member name="focus_neighbor_left" type="NodePath" setter="set_focus_neighbor" getter="get_focus_neighbor" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses the left arrow on the keyboard or left on a gamepad by default. You can change the key by editing the [code]ui_left[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the left of this one. + Tells Godot which node it should give focus to if the user presses the left arrow on the keyboard or left on a gamepad by default. You can change the key by editing the [code]ui_left[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the left of this one. </member> <member name="focus_neighbor_right" type="NodePath" setter="set_focus_neighbor" getter="get_focus_neighbor" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses the right arrow on the keyboard or right on a gamepad by default. You can change the key by editing the [code]ui_right[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. + Tells Godot which node it should give focus to if the user presses the right arrow on the keyboard or right on a gamepad by default. You can change the key by editing the [code]ui_right[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. </member> <member name="focus_neighbor_top" type="NodePath" setter="set_focus_neighbor" getter="get_focus_neighbor" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses the top arrow on the keyboard or top on a gamepad by default. You can change the key by editing the [code]ui_top[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. + Tells Godot which node it should give focus to if the user presses the top arrow on the keyboard or top on a gamepad by default. You can change the key by editing the [code]ui_top[/code] input action. The node must be a [Control]. If this property is not set, Godot will give focus to the closest [Control] to the bottom of this one. </member> <member name="focus_next" type="NodePath" setter="set_focus_next" getter="get_focus_next" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses [kbd]Tab[/kbd] on a keyboard by default. You can change the key by editing the [code]ui_focus_next[/code] input action. + Tells Godot which node it should give focus to if the user presses [kbd]Tab[/kbd] on a keyboard by default. You can change the key by editing the [code]ui_focus_next[/code] input action. If this property is not set, Godot will select a "best guess" based on surrounding nodes in the scene tree. </member> <member name="focus_previous" type="NodePath" setter="set_focus_previous" getter="get_focus_previous" default="NodePath("")"> - Tells Godot which node it should give keyboard focus to if the user presses [kbd]Shift + Tab[/kbd] on a keyboard by default. You can change the key by editing the [code]ui_focus_prev[/code] input action. + Tells Godot which node it should give focus to if the user presses [kbd]Shift + Tab[/kbd] on a keyboard by default. You can change the key by editing the [code]ui_focus_prev[/code] input action. If this property is not set, Godot will select a "best guess" based on surrounding nodes in the scene tree. </member> <member name="global_position" type="Vector2" setter="_set_global_position" getter="get_global_position"> @@ -1098,12 +1098,12 @@ <signals> <signal name="focus_entered"> <description> - Emitted when the node gains keyboard focus. + Emitted when the node gains focus. </description> </signal> <signal name="focus_exited"> <description> - Emitted when the node loses keyboard focus. + Emitted when the node loses focus. </description> </signal> <signal name="gui_input"> @@ -1159,7 +1159,7 @@ The node can only grab focus on mouse clicks. Use with [member focus_mode]. </constant> <constant name="FOCUS_ALL" value="2" enum="FocusMode"> - The node can grab focus on mouse click or using the arrows and the Tab keys on the keyboard. Use with [member focus_mode]. + The node can grab focus on mouse click, using the arrows and the Tab keys on the keyboard, or using the D-pad buttons on a gamepad. Use with [member focus_mode]. </constant> <constant name="NOTIFICATION_RESIZED" value="40"> Sent when the node changes size. Use [member size] to get the new size. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 6d3f3a7362..0dbaa9c86f 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1244,7 +1244,7 @@ <param index="1" name="window_id" type="int" default="0" /> <description> Sets window mode for the given window to [param mode]. See [enum WindowMode] for possible values and how each mode behaves. - [b]Note:[/b] Setting the window to fullscreen forcibly sets the borderless flag to [code]true[/code], so make sure to set it back to [code]false[/code] when not wanted. + [b]Note:[/b] Setting the window to full screen forcibly sets the borderless flag to [code]true[/code], so make sure to set it back to [code]false[/code] when not wanted. </description> </method> <method name="window_set_mouse_passthrough"> @@ -1324,7 +1324,8 @@ <param index="0" name="window_id" type="int" /> <param index="1" name="parent_window_id" type="int" /> <description> - Sets window transient parent. Transient window is will be destroyed with its transient parent and displayed on top of non-exclusive full-screen parent window. Transient windows can't enter full-screen mode. + Sets window transient parent. Transient window is will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode. + Note that behavior might be different depending on the platform. </description> </method> <method name="window_set_vsync_mode"> @@ -1495,60 +1496,78 @@ <constant name="CURSOR_MAX" value="17" enum="CursorShape"> </constant> <constant name="WINDOW_MODE_WINDOWED" value="0" enum="WindowMode"> + Windowed mode, i.e. [Window] doesn't occupy the whole screen (unless set to the size of the screen). </constant> <constant name="WINDOW_MODE_MINIMIZED" value="1" enum="WindowMode"> + Minimized window mode, i.e. [Window] is not visible and available on window manager's window list. Normally happens when the minimize button is pressed. </constant> <constant name="WINDOW_MODE_MAXIMIZED" value="2" enum="WindowMode"> + Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the minimize button is pressed. </constant> <constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode"> - Fullscreen window mode. Note that this is not [i]exclusive[/i] fullscreen. On Windows and Linux, a borderless window is used to emulate fullscreen. On macOS, a new desktop is used to display the running project. - 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. + Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux, a borderless window is used to emulate full screen. On macOS, a new desktop is used to display the running project. + Regardless of the platform, enabling full screen 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 full screen mode. </constant> <constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode"> - Exclusive fullscreen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. - Only one window in exclusive fullscreen mode can be visible on a given screen at a time. If multiple windows are in exclusive fullscreen mode for the same screen, the last one being set to this mode takes precedence. - 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. + Exclusive full screen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. + Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence. + Regardless of the platform, enabling full screen 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 full screen mode. </constant> <constant name="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags"> - Window can't be resizing by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows. + The window can't be resizing by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows. </constant> <constant name="WINDOW_FLAG_BORDERLESS" value="1" enum="WindowFlags"> - Window do not have native title bar and other decorations. This flag is ignored for full-screen windows. + The window do not have native title bar and other decorations. This flag is ignored for full-screen windows. </constant> <constant name="WINDOW_FLAG_ALWAYS_ON_TOP" value="2" enum="WindowFlags"> - Window is floating above other regular windows. This flag is ignored for full-screen windows. + The window is floating on top of all other windows. This flag is ignored for full-screen windows. </constant> <constant name="WINDOW_FLAG_TRANSPARENT" value="3" enum="WindowFlags"> - Window background can be transparent. + The window background can be transparent. [b]Note:[/b] This flag has no effect if [member ProjectSettings.display/window/per_pixel_transparency/allowed] is set to [code]false[/code]. + [b]Note:[/b] Transparency support is implemented on Linux, macOS and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities. </constant> <constant name="WINDOW_FLAG_NO_FOCUS" value="4" enum="WindowFlags"> - Window can't be focused. No-focus window will ignore all input, except mouse clicks. + The window can't be focused. No-focus window will ignore all input, except mouse clicks. </constant> <constant name="WINDOW_FLAG_POPUP" value="5" enum="WindowFlags"> - Window is part of menu or [OptionButton] dropdown. This flag can't be changed when window is visible. An active popup window will exclusively receive all input, without stealing focus from its parent. Popup windows are automatically closed when uses click outside it, or when an application is switched. Popup window must have [constant WINDOW_FLAG_TRANSPARENT] set. + Window is part of menu or [OptionButton] dropdown. This flag can't be changed when the window is visible. An active popup window will exclusively receive all input, without stealing focus from its parent. Popup windows are automatically closed when uses click outside it, or when an application is switched. Popup window must have [code]transient parent[/code] set (see [method window_set_transient]). </constant> <constant name="WINDOW_FLAG_EXTEND_TO_TITLE" value="6" enum="WindowFlags"> Window content is expanded to the full size of the window. Unlike borderless window, the frame is left intact and can be used to resize the window, title bar is transparent, but have minimize/maximize/close buttons. + Use [method window_set_window_buttons_offset] to adjust minimize/maximize/close buttons offset. + Use [method window_get_safe_title_margins] to determine area under the title bar that is not covered by decorations. [b]Note:[/b] This flag is implemented on macOS. </constant> <constant name="WINDOW_FLAG_MAX" value="7" enum="WindowFlags"> + Max value of the [enum WindowFlags]. </constant> <constant name="WINDOW_EVENT_MOUSE_ENTER" value="0" enum="WindowEvent"> + Sent when the mouse pointer enters the window, see [method window_set_window_event_callback]. </constant> <constant name="WINDOW_EVENT_MOUSE_EXIT" value="1" enum="WindowEvent"> + Sent when the mouse pointer exits the window, see [method window_set_window_event_callback]. </constant> <constant name="WINDOW_EVENT_FOCUS_IN" value="2" enum="WindowEvent"> + Sent when the window grabs focus, see [method window_set_window_event_callback]. </constant> <constant name="WINDOW_EVENT_FOCUS_OUT" value="3" enum="WindowEvent"> + Sent when the window loses focus, see [method window_set_window_event_callback]. </constant> <constant name="WINDOW_EVENT_CLOSE_REQUEST" value="4" enum="WindowEvent"> + Sent when the user has attempted to close the window (e.g. close button is pressed), see [method window_set_window_event_callback]. </constant> <constant name="WINDOW_EVENT_GO_BACK_REQUEST" value="5" enum="WindowEvent"> + Sent when the device "Back" button is pressed, see [method window_set_window_event_callback]. + [b]Note:[/b] This event is implemented on Android. </constant> <constant name="WINDOW_EVENT_DPI_CHANGE" value="6" enum="WindowEvent"> + Sent when the window is moved to the display with different DPI, or display DPI is changed, see [method window_set_window_event_callback]. + [b]Note:[/b] This flag is implemented on macOS. </constant> <constant name="WINDOW_EVENT_TITLEBAR_CHANGE" value="7" enum="WindowEvent"> + Sent when the window title bar decoration is changed (e.g. [constant WINDOW_FLAG_EXTEND_TO_TITLE] is set or window entered/exited full screen mode), see [method window_set_window_event_callback]. + [b]Note:[/b] This flag is implemented on macOS. </constant> <constant name="VSYNC_DISABLED" value="0" enum="VSyncMode"> No vertical synchronization, which means the engine will display frames as fast as possible (tearing may be visible). diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml index 9ee21fd63b..561ccdc6e7 100644 --- a/doc/classes/EditorNode3DGizmo.xml +++ b/doc/classes/EditorNode3DGizmo.xml @@ -168,16 +168,16 @@ Removes everything in the gizmo including meshes, collisions and handles. </description> </method> - <method name="get_plugin" qualifiers="const"> - <return type="EditorNode3DGizmoPlugin" /> + <method name="get_node_3d" qualifiers="const"> + <return type="Node3D" /> <description> - Returns the [EditorNode3DGizmoPlugin] that owns this gizmo. It's useful to retrieve materials using [method EditorNode3DGizmoPlugin.get_material]. + Returns the [Node3D] node associated with this gizmo. </description> </method> - <method name="get_spatial_node" qualifiers="const"> - <return type="Node3D" /> + <method name="get_plugin" qualifiers="const"> + <return type="EditorNode3DGizmoPlugin" /> <description> - Returns the Node3D node associated with this gizmo. + Returns the [EditorNode3DGizmoPlugin] that owns this gizmo. It's useful to retrieve materials using [method EditorNode3DGizmoPlugin.get_material]. </description> </method> <method name="get_subgizmo_selection" qualifiers="const"> @@ -200,7 +200,7 @@ Sets the gizmo's hidden state. If [code]true[/code], the gizmo will be hidden. If [code]false[/code], it will be shown. </description> </method> - <method name="set_spatial_node"> + <method name="set_node_3d"> <return type="void" /> <param index="0" name="node" type="Node" /> <description> diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml index 8a97dda9ae..24e1a2da7c 100644 --- a/doc/classes/EditorNode3DGizmoPlugin.xml +++ b/doc/classes/EditorNode3DGizmoPlugin.xml @@ -5,10 +5,10 @@ </brief_description> <description> [EditorNode3DGizmoPlugin] allows you to define a new type of Gizmo. There are two main ways to do so: extending [EditorNode3DGizmoPlugin] for the simpler gizmos, or creating a new [EditorNode3DGizmo] type. See the tutorial in the documentation for more info. - To use [EditorNode3DGizmoPlugin], register it using the [method EditorPlugin.add_spatial_gizmo_plugin] method first. + To use [EditorNode3DGizmoPlugin], register it using the [method EditorPlugin.add_node_3d_gizmo_plugin] method first. </description> <tutorials> - <link title="Spatial gizmo plugins">$DOCS_URL/tutorials/plugins/editor/spatial_gizmos.html</link> + <link title="Node3D gizmo plugins">$DOCS_URL/tutorials/plugins/editor/spatial_gizmos.html</link> </tutorials> <methods> <method name="_can_be_hidden" qualifiers="virtual const"> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 27cf410c15..563987e2a3 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -455,6 +455,14 @@ [/codeblocks] </description> </method> + <method name="add_node_3d_gizmo_plugin"> + <return type="void" /> + <param index="0" name="plugin" type="EditorNode3DGizmoPlugin" /> + <description> + Registers a new [EditorNode3DGizmoPlugin]. Gizmo plugins are used to add custom gizmos to the 3D preview viewport for a [Node3D]. + See [method add_inspector_plugin] for an example of how to register a plugin. + </description> + </method> <method name="add_scene_format_importer_plugin"> <return type="void" /> <param index="0" name="scene_format_importer" type="EditorSceneFormatImporter" /> @@ -473,14 +481,6 @@ If [param first_priority] is [code]true[/code], the new import plugin is inserted first in the list and takes precedence over pre-existing plugins. </description> </method> - <method name="add_spatial_gizmo_plugin"> - <return type="void" /> - <param index="0" name="plugin" type="EditorNode3DGizmoPlugin" /> - <description> - Registers a new [EditorNode3DGizmoPlugin]. Gizmo plugins are used to add custom gizmos to the 3D preview viewport for a [Node3D]. - See [method add_inspector_plugin] for an example of how to register a plugin. - </description> - </method> <method name="add_tool_menu_item"> <return type="void" /> <param index="0" name="name" type="String" /> @@ -621,6 +621,13 @@ Removes an inspector plugin registered by [method add_import_plugin] </description> </method> + <method name="remove_node_3d_gizmo_plugin"> + <return type="void" /> + <param index="0" name="plugin" type="EditorNode3DGizmoPlugin" /> + <description> + Removes a gizmo plugin registered by [method add_node_3d_gizmo_plugin]. + </description> + </method> <method name="remove_scene_format_importer_plugin"> <return type="void" /> <param index="0" name="scene_format_importer" type="EditorSceneFormatImporter" /> @@ -635,13 +642,6 @@ Remove the [EditorScenePostImportPlugin], added with [method add_scene_post_import_plugin]. </description> </method> - <method name="remove_spatial_gizmo_plugin"> - <return type="void" /> - <param index="0" name="plugin" type="EditorNode3DGizmoPlugin" /> - <description> - Removes a gizmo plugin registered by [method add_spatial_gizmo_plugin]. - </description> - </method> <method name="remove_tool_menu_item"> <return type="void" /> <param index="0" name="name" type="String" /> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index b138a55ea3..510f14ec54 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -197,7 +197,6 @@ <param index="0" name="renormalize" type="bool" default="false" /> <description> Generates mipmaps for the image. Mipmaps are precalculated lower-resolution copies of the image that are automatically used if the image needs to be scaled down when rendered. They help improve image quality and performance when rendering. This method returns an error if the image is compressed, in a custom format, or if the image's width/height is [code]0[/code]. - [b]Note:[/b] Mipmap generation is done on the CPU, is single-threaded and is [i]always[/i] done on the main thread. This means generating mipmaps will result in noticeable stuttering during gameplay, even if [method generate_mipmaps] is called from a [Thread]. </description> </method> <method name="get_data" qualifiers="const"> diff --git a/doc/classes/NavigationObstacle2D.xml b/doc/classes/NavigationObstacle2D.xml index 6d05e220e3..68d9691ccf 100644 --- a/doc/classes/NavigationObstacle2D.xml +++ b/doc/classes/NavigationObstacle2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> 2D Obstacle used in navigation for collision avoidance. The obstacle needs navigation data to work correctly. [NavigationObstacle2D] is physics safe. - [b]Note:[/b] Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. + Obstacles [b]don't[/b] change the resulting path from the pathfinding, they only affect the navigation agent movement in a radius. Therefore, using obstacles for the static walls in your level won't work because those walls don't exist in the pathfinding. The navigation agent will be pushed in a semi-random direction away while moving inside that radius. Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. </description> <tutorials> </tutorials> diff --git a/doc/classes/NavigationObstacle3D.xml b/doc/classes/NavigationObstacle3D.xml index f5a0bde089..90eb55887f 100644 --- a/doc/classes/NavigationObstacle3D.xml +++ b/doc/classes/NavigationObstacle3D.xml @@ -5,7 +5,7 @@ </brief_description> <description> 3D Obstacle used in navigation for collision avoidance. The obstacle needs navigation data to work correctly. [NavigationObstacle3D] is physics safe. - [b]Note:[/b] Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. + Obstacles [b]don't[/b] change the resulting path from the pathfinding, they only affect the navigation agent movement in a radius. Therefore, using obstacles for the static walls in your level won't work because those walls don't exist in the pathfinding. The navigation agent will be pushed in a semi-random direction away while moving inside that radius. Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. </description> <tutorials> </tutorials> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index d9732da3a3..bceb285584 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -110,11 +110,12 @@ <return type="void" /> <param index="0" name="event" type="InputEvent" /> <description> - Called when an [InputEventKey] or [InputEventShortcut] hasn't been consumed by [method _input] or any GUI [Control] item. The input event propagates up through the node tree until a node consumes it. + Called when an [InputEventKey] hasn't been consumed by [method _input] or any GUI [Control] item. The input event propagates up through the node tree until a node consumes it. It is only called if unhandled key input processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_unhandled_key_input]. To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called. This method can be used to handle Unicode character input with [kbd]Alt[/kbd], [kbd]Alt + Ctrl[/kbd], and [kbd]Alt + Shift[/kbd] modifiers, after shortcuts were handled. For gameplay input, this and [method _unhandled_input] are usually a better fit than [method _input] as they allow the GUI to intercept the events first. + This method also performs better than [method _unhandled_input], since unrelated events such as [InputEventMouseMotion] are automatically filtered. [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not an orphan). </description> </method> @@ -145,7 +146,7 @@ [/csharp] [/codeblocks] If you need the child node to be added below a specific node in the list of children, use [method add_sibling] instead of this method. - [b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/misc/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view. + [b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view. </description> </method> <method name="add_sibling"> @@ -767,7 +768,7 @@ </member> <member name="owner" type="Node" setter="set_owner" getter="get_owner"> The node owner. A node can have any other node as owner (as long as it is a valid parent, grandparent, etc. ascending in the tree). When saving a node (using [PackedScene]), all the nodes it owns will be saved with it. This allows for the creation of complex [SceneTree]s, with instancing and subinstancing. - [b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/misc/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view. + [b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]tool scripts[/url] and [url=$DOCS_URL/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view. </member> <member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0"> Can be used to pause or unpause the node, or make the node paused based on the [SceneTree], or make it inherit the process mode from its parent (default). diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index bc6ac8012f..15b3d4958c 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -4,7 +4,7 @@ Operating System functions. </brief_description> <description> - Operating System functions. OS wraps the most common functionality to communicate with the host operating system, such as the clipboard, video driver, date and time, timers, environment variables, execution of binaries, command line, etc. + Operating System functions. OS wraps the most common functionality to communicate with the host operating system, such as the clipboard, video driver, delays, environment variables, execution of binaries, command line, etc. </description> <tutorials> <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> diff --git a/doc/classes/PhysicsDirectBodyState3D.xml b/doc/classes/PhysicsDirectBodyState3D.xml index efe63e4093..04087cbfb6 100644 --- a/doc/classes/PhysicsDirectBodyState3D.xml +++ b/doc/classes/PhysicsDirectBodyState3D.xml @@ -226,6 +226,9 @@ <member name="inverse_inertia" type="Vector3" setter="" getter="get_inverse_inertia"> The inverse of the inertia of the body. </member> + <member name="inverse_inertia_tensor" type="Basis" setter="" getter="get_inverse_inertia_tensor"> + The inverse of the inertia tensor of the body. + </member> <member name="inverse_mass" type="float" setter="" getter="get_inverse_mass"> The inverse of the mass of the body. </member> diff --git a/doc/classes/PhysicsDirectBodyState3DExtension.xml b/doc/classes/PhysicsDirectBodyState3DExtension.xml index ade197eadc..4432f89b9d 100644 --- a/doc/classes/PhysicsDirectBodyState3DExtension.xml +++ b/doc/classes/PhysicsDirectBodyState3DExtension.xml @@ -159,6 +159,11 @@ <description> </description> </method> + <method name="_get_inverse_inertia_tensor" qualifiers="virtual const"> + <return type="Basis" /> + <description> + </description> + </method> <method name="_get_inverse_mass" qualifiers="virtual const"> <return type="float" /> <description> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 288dc25a77..85bb9a64a6 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -348,25 +348,25 @@ Desktop override for [member debug/file_logging/enable_file_logging], as log files are not readily accessible on mobile/Web platforms. </member> <member name="debug/file_logging/log_path" type="String" setter="" getter="" default=""user://logs/godot.log""> - Path to logs within the project. Using an [code]user://[/code] path is recommended. + Path at which to store log files for the project. Using a path under [code]user://[/code] is recommended. </member> <member name="debug/file_logging/max_log_files" type="int" setter="" getter="" default="5"> Specifies the maximum number of log files allowed (used for rotation). </member> <member name="debug/gdscript/warnings/assert_always_false" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when an [code]assert[/code] call always returns false. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to false. </member> <member name="debug/gdscript/warnings/assert_always_true" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when an [code]assert[/code] call always returns true. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to true. </member> <member name="debug/gdscript/warnings/constant_used_as_function" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a constant is used as a function. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a constant is used as a function. </member> <member name="debug/gdscript/warnings/deprecated_keyword" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when deprecated keywords are used. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when deprecated keywords are used. </member> <member name="debug/gdscript/warnings/empty_file" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when an empty file is parsed. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an empty file is parsed. </member> <member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings. @@ -375,87 +375,88 @@ If [code]true[/code], scripts in the [code]res://addons[/code] folder will not generate warnings. </member> <member name="debug/gdscript/warnings/function_used_as_property" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when using a function as if it was a property. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a function as if it is a property. </member> <member name="debug/gdscript/warnings/incompatible_ternary" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a ternary operator may emit values with incompatible types. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a ternary operator may emit values with incompatible types. </member> <member name="debug/gdscript/warnings/int_assigned_to_enum" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when trying to assign an integer to a variable that expects an enum value. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to assign an integer to a variable that expects an enum value. </member> <member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when dividing an integer by another integer (the decimal part will be discarded). + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded). </member> <member name="debug/gdscript/warnings/narrowing_conversion" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when passing a floating-point value to a function that expects an integer (it will be converted and lose precision). + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when passing a floating-point value to a function that expects an integer (it will be converted and lose precision). </member> <member name="debug/gdscript/warnings/property_used_as_function" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when using a property as if it was a function. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a property as if it is a function. </member> <member name="debug/gdscript/warnings/redundant_await" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a function that is not a coroutine is called with await. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function that is not a coroutine is called with await. </member> <member name="debug/gdscript/warnings/return_value_discarded" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when calling a function without using its return value (by assigning it to a variable or using it as a function argument). Such return values are sometimes used to denote possible errors using the [enum Error] enum. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a function without using its return value (by assigning it to a variable or using it as a function argument). Such return values are sometimes used to denote possible errors using the [enum Error] enum. </member> <member name="debug/gdscript/warnings/shadowed_global_identifier" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when defining a local or subclass member variable, signal, or enum that would have the same name as a built-in function or global class name, which possibly shadow it. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable, signal, or enum that would have the same name as a built-in function or global class name, thus shadowing it. </member> <member name="debug/gdscript/warnings/shadowed_variable" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when defining a local or subclass member variable that would shadow a variable at an upper level (such as a member variable). + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable that would shadow a member variable that the class defines. </member> <member name="debug/gdscript/warnings/shadowed_variable_base_class" type="int" setter="" getter="" default="1"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or subclass member variable that would shadow a variable that is inherited from a parent class. </member> <member name="debug/gdscript/warnings/standalone_expression" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when calling an expression that has no effect on the surrounding code, such as writing [code]2 + 2[/code] as a statement. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling an expression that has no effect on the surrounding code, such as writing [code]2 + 2[/code] as a statement. </member> <member name="debug/gdscript/warnings/standalone_ternary" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when calling a ternary expression that has no effect on the surrounding code, such as writing [code]42 if active else 0[/code] as a statement. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a ternary expression that has no effect on the surrounding code, such as writing [code]42 if active else 0[/code] as a statement. </member> <member name="debug/gdscript/warnings/treat_warnings_as_errors" type="bool" setter="" getter="" default="false"> - If [code]true[/code], all warnings will be reported as if they were errors. + If [code]true[/code], all warnings will be reported as if they are errors. </member> <member name="debug/gdscript/warnings/unassigned_variable" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when using a variable that wasn't previously assigned. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using a variable that wasn't previously assigned. </member> <member name="debug/gdscript/warnings/unassigned_variable_op_assign" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when assigning a variable using an assignment operator like [code]+=[/code] if the variable wasn't previously assigned. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when assigning a variable using an assignment operator like [code]+=[/code] if the variable wasn't previously assigned. </member> <member name="debug/gdscript/warnings/unreachable_code" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when unreachable code is detected (such as after a [code]return[/code] statement that will always be executed). + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when unreachable code is detected (such as after a [code]return[/code] statement that will always be executed). </member> <member name="debug/gdscript/warnings/unreachable_pattern" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when an unreachable [code]match[/code] pattern is detected. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an unreachable [code]match[/code] pattern is detected. </member> <member name="debug/gdscript/warnings/unsafe_call_argument" type="int" setter="" getter="" default="0"> - If [code]enabled[/code], prints a warning or an error when using an expression whose type may not be compatible with the function parameter expected. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using an expression whose type may not be compatible with the function parameter expected. </member> <member name="debug/gdscript/warnings/unsafe_cast" type="int" setter="" getter="" default="0"> - If [code]enabled[/code], prints a warning or an error when performing an unsafe cast. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when performing an unsafe cast. </member> <member name="debug/gdscript/warnings/unsafe_method_access" type="int" setter="" getter="" default="0"> - If [code]enabled[/code], prints a warning or an error when calling a method whose presence is not guaranteed at compile-time in the class. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a method whose presence is not guaranteed at compile-time in the class. </member> <member name="debug/gdscript/warnings/unsafe_property_access" type="int" setter="" getter="" default="0"> - If [code]enabled[/code], prints a warning or an error when accessing a property whose presence is not guaranteed at compile-time in the class. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when accessing a property whose presence is not guaranteed at compile-time in the class. </member> <member name="debug/gdscript/warnings/unused_local_constant" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a local constant is never used. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local constant is never used. </member> <member name="debug/gdscript/warnings/unused_parameter" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a function parameter is never used. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a function parameter is never used. </member> <member name="debug/gdscript/warnings/unused_private_class_variable" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a class variable is never used. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a private member variable is never used. </member> <member name="debug/gdscript/warnings/unused_signal" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a signal is unused. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a signal is declared but never emitted. </member> <member name="debug/gdscript/warnings/unused_variable" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when a local variable is unused. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable is unused. </member> <member name="debug/gdscript/warnings/void_assignment" type="int" setter="" getter="" default="1"> - If [code]enabled[/code], prints a warning or an error when assigning the result of a function that returns [code]void[/code] to a variable. + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when assigning the result of a function that returns [code]void[/code] to a variable. </member> <member name="debug/settings/crash_handler/message" type="String" setter="" getter="" default=""Please include this when reporting the bug to the project developer.""> Message to be displayed before the backtrace when the engine crashes. By default, this message is only used in exported projects due to the editor-only override applied to this setting. @@ -473,6 +474,7 @@ Print frames per second to standard output every second. </member> <member name="debug/settings/stdout/print_gpu_profile" type="bool" setter="" getter="" default="false"> + Print GPU profile information to standard output every second. This includes how long each frame takes the GPU to render on average, broken down into different steps of the render pipeline, such as CanvasItems, shadows, glow, etc. </member> <member name="debug/settings/stdout/verbose_stdout" type="bool" setter="" getter="" default="false"> Print more information to standard output when running. It displays information such as memory leaks, which scenes and resources are being loaded, etc. This can also be enabled using the [code]--verbose[/code] or [code]-v[/code] command line argument, even on an exported project. See also [method OS.is_stdout_verbose] and [method @GlobalScope.print_verbose]. @@ -575,8 +577,8 @@ [b]Note:[/b] This setting is ignored on iOS, Android, and Web. </member> <member name="display/window/size/extend_to_title" type="bool" setter="" getter="" default="false"> - Main window content is expanded to the full size of the window. Unlike borderless window, the frame is left intact and can be used to resize the window, title bar is transparent, but have minimize/maximize/close buttons. - [b]Note:[/b] This setting is implemented on macOS. + Main window content is expanded to the full size of the window. Unlike a borderless window, the frame is left intact and can be used to resize the window, and the title bar is transparent, but has minimize/maximize/close buttons. + [b]Note:[/b] This setting is implemented only on macOS. </member> <member name="display/window/size/mode" type="int" setter="" getter="" default="0"> Main window mode. See [enum DisplayServer.WindowMode] for possible values and how each mode behaves. @@ -685,9 +687,10 @@ If [code]true[/code], swaps Cancel and OK buttons in dialogs on Windows and UWP to follow interface conventions. </member> <member name="gui/common/text_edit_undo_stack_max_size" type="int" setter="" getter="" default="1024"> + Maximum undo/redo history size for [TextEdit] fields. </member> <member name="gui/theme/custom" type="String" setter="" getter="" default=""""> - Path to a custom [Theme] resource file to use for the project ([code]theme[/code] or generic [code]tres[/code]/[code]res[/code] extension). + Path to a custom [Theme] resource file to use for the project ([code].theme[/code] or generic [code].tres[/code]/[code].res[/code] extension). </member> <member name="gui/theme/custom_font" type="String" setter="" getter="" default=""""> Path to a custom [Font] resource to use as default for all GUI elements of the project. @@ -712,6 +715,8 @@ Default font glyph sub-pixel positioning mode. See [member FontFile.subpixel_positioning]. </member> <member name="gui/theme/default_theme_scale" type="float" setter="" getter="" default="1.0"> + The default scale factor for [Control]s, when not overriden by a [Theme]. + [b]Note:[/b] This property is only read when the project starts. To change the default scale at runtime, set [member ThemeDB.fallback_base_scale] instead. </member> <member name="gui/theme/lcd_subpixel_layout" type="int" setter="" getter="" default="1"> LCD sub-pixel layout used for font anti-aliasing. See [enum TextServer.FontLCDSubpixelLayout]. @@ -734,8 +739,12 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_copy" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to copy a selection to the clipboard. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_cut" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to cut a selection to the clipboard. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_down" type="Dictionary" setter="" getter=""> Default [InputEventAction] to move down in the UI. @@ -746,10 +755,16 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_filedialog_refresh" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to refresh the contents of the current directory of a [FileDialog]. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_filedialog_show_hidden" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to toggle showing hidden files and directories in a [FileDialog]. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_filedialog_up_one_level" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to go up one directory in a [FileDialog]. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_focus_next" type="Dictionary" setter="" getter=""> Default [InputEventAction] to focus the next [Control] in the scene. The focus behavior can be configured via [member Control.focus_next]. @@ -760,8 +775,12 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_graph_delete" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete a [GraphNode] in a [GraphEdit]. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_graph_duplicate" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to duplicate a [GraphNode] in a [GraphEdit]. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_home" type="Dictionary" setter="" getter=""> Default [InputEventAction] to go to the start position of a [Control] (e.g. first item in an [ItemList] or a [Tree]), matching the behavior of [constant KEY_HOME] on typical desktop UI systems. @@ -772,6 +791,8 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_menu" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to open a context menu in a text field. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_page_down" type="Dictionary" setter="" getter=""> Default [InputEventAction] to go down a page in a [Control] (e.g. in an [ItemList] or a [Tree]), matching the behavior of [constant KEY_PAGEDOWN] on typical desktop UI systems. @@ -782,8 +803,12 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_paste" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to paste from the clipboard. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_redo" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to redo an undone action. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_right" type="Dictionary" setter="" getter=""> Default [InputEventAction] to move right in the UI. @@ -796,96 +821,172 @@ <member name="input/ui_swap_input_direction" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_backspace" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete the character before the text cursor. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_backspace_all_to_left" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete [b]all[/b] text before the text cursor. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_backspace_all_to_left.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to delete all text before the text cursor. </member> <member name="input/ui_text_backspace_word" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete all characters before the cursor up until a whitespace or punctuation character. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_backspace_word.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to delete a word. </member> <member name="input/ui_text_caret_document_end" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor the the end of the text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_document_end.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor to the end of the text. </member> <member name="input/ui_text_caret_document_start" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor to the start of the text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_document_start.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor to the start of the text. </member> <member name="input/ui_text_caret_down" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor down. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_left" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor left. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_line_end" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor to the end of the line. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_line_end.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor to the end of the line. </member> <member name="input/ui_text_caret_line_start" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor to the start of the line. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_line_start.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor to the start of the line. </member> <member name="input/ui_text_caret_page_down" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor down one page. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_page_up" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor up one page. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_right" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor right. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_up" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor up. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_word_left" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor left to the next whitespace or punctuation. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_word_left.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor back one word. </member> <member name="input/ui_text_caret_word_right" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to move the text cursor right to the next whitespace or punctuation. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_caret_word_right.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to move the text cursor forward one word. </member> <member name="input/ui_text_completion_accept" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to accept an autocompetion hint. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_completion_query" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to request autocompetion. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_completion_replace" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to accept an autocompetion hint, replacing existing text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_dedent" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to unindent text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_delete" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete the character after the text cursor. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_delete_all_to_right" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete [b]all[/b] text after the text cursor. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_delete_all_to_right.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to delete all text after the text cursor. </member> <member name="input/ui_text_delete_word" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to delete all characters after the cursor up until a whitespace or punctuation character. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_delete_word.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to delete a word after the text cursor. </member> <member name="input/ui_text_indent" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to indent the current line. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_newline" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to insert a new line at the position of the text cursor. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_newline_above" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to insert a new line before the current one. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_newline_blank" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to insert a new line after the current one. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_scroll_down" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to scroll down one line of text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_scroll_down.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to scroll down one line. </member> <member name="input/ui_text_scroll_up" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to scroll up one line of text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_scroll_up.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to scroll up one line. </member> <member name="input/ui_text_select_all" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to select all text. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_select_word_under_caret" type="Dictionary" setter="" getter=""> If no selection is currently active, selects the word currently under the caret in text fields. If a selection is currently active, deselects the current selection. [b]Note:[/b] Currently, this is only implemented in [TextEdit], not [LineEdit]. </member> <member name="input/ui_text_submit" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to submit a text field. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_toggle_insert_mode" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to toggle [i]instert mode[/i] in a text field. While in insert mode, inserting new text overrides the character after the cursor, unless the next character is a new line. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_undo" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to undo the most recent action. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_up" type="Dictionary" setter="" getter=""> Default [InputEventAction] to move up in the UI. @@ -1700,6 +1801,7 @@ Another way to combat specular aliasing is to enable [member rendering/anti_aliasing/screen_space_roughness_limiter/enabled]. </member> <member name="rendering/anti_aliasing/quality/use_debanding" type="bool" setter="" getter="" default="false"> + If [code]true[/code], uses a fast post-processing dithering filter on the default screen [Viewport] to make banding significantly less visible. In some cases, the dithering pattern may be slightly noticable. Note that this will make losslessly compressed (PNG etc.) screenshots larger. </member> <member name="rendering/anti_aliasing/quality/use_taa" type="bool" setter="" getter="" default="false"> Enables Temporal Anti-Aliasing for the default screen [Viewport]. TAA works by jittering the camera and accumulating the images of the last rendered frames, motion vector rendering is used to account for camera and object motion. Enabling TAA can make the image blurrier, which is partially counteracted by automatically using a negative mipmap LOD bias (see [member rendering/textures/default_filters/texture_mipmap_bias]). @@ -1985,9 +2087,9 @@ </member> <member name="rendering/renderer/rendering_method" type="String" setter="" getter="" default=""forward_plus""> Sets the renderer that will be used by the project. Options are: - [b]Clustered[/b]: High-end renderer designed for Desktop devices. Has a higher base overhead, but scales well with complex scenes. Not suitable for older devices or mobile. - [b]Mobile[/b]: Modern renderer designed for mobile devices. Has a lower base overhead than Clustered, but does not scale as well to large scenes with many elements. - [b]Compatibility[/b]: Low-end renderer designed for older devices. Based on the limitations of the OpenGL 3.3/ OpenGL ES 3.0 / WebGL 2 APIs. + [b]Forward Plus[/b]: High-end renderer designed for Desktop devices. Has a higher base overhead, but scales well with complex scenes. Not suitable for older devices or mobile. + [b]Mobile[/b]: Modern renderer designed for mobile devices. Has a lower base overhead than Forward Plus, but does not scale as well to large scenes with many elements. + [b]GL Compatibility[/b]: Low-end renderer designed for older devices. Based on the limitations of the OpenGL 3.3/ OpenGL ES 3.0 / WebGL 2 APIs. </member> <member name="rendering/renderer/rendering_method.mobile" type="String" setter="" getter="" default=""mobile""> Override for [member rendering/renderer/rendering_method] on mobile devices. @@ -2033,6 +2135,7 @@ <member name="rendering/shader_compiler/shader_cache/compress" type="bool" setter="" getter="" default="true"> </member> <member name="rendering/shader_compiler/shader_cache/enabled" type="bool" setter="" getter="" default="true"> + Enable the shader cache, which stores compiled shaders to disk to prevent stuttering from shader compilation the next time the shader is needed. </member> <member name="rendering/shader_compiler/shader_cache/strip_debug" type="bool" setter="" getter="" default="false"> </member> diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index 21c160b780..2393790e12 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> Static body for 2D physics. - A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], it doesn't consume any CPU resources as long as they don't move. + A static body is a simple body that doesn't move under physics simulation, i.e. it can't be moved by external forces or contacts but its transformation can still be updated manually by the user. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], it doesn't consume any CPU resources as long as they don't move. They have extra functionalities to move and affect other bodies: [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index daa71d1168..0beaa6bb52 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -5,7 +5,7 @@ </brief_description> <description> Static body for 3D physics. - A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], it doesn't consume any CPU resources as long as they don't move. + A static body is a simple body that doesn't move under physics simulation, i.e. it can't be moved by external forces or contacts but its transformation can still be updated manually by the user. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], it doesn't consume any CPU resources as long as they don't move. They have extra functionalities to move and affect other bodies: [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 0905c0c20b..2f9b971fc8 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -5,6 +5,7 @@ </brief_description> <description> TextEdit is meant for editing large, multiline text. It also has facilities for editing code, such as syntax highlighting support and multiple levels of undo/redo. + [b]Note:[/b] Most viewport, caret and edit methods contain a [code]caret_index[/code] argument for [member caret_multiple] support. The argument should be one of the following: [code]-1[/code] for all carets, [code]0[/code] for the main caret, or greater than [code]0[/code] for secondary carets. [b]Note:[/b] When holding down [kbd]Alt[/kbd], the vertical scroll wheel will scroll 5 times as fast as it would normally do. This also works in the Godot script editor. </description> <tutorials> @@ -12,18 +13,21 @@ <methods> <method name="_backspace" qualifiers="virtual"> <return type="void" /> + <param index="0" name="caret_index" type="int" /> <description> Override this method to define what happens when the user presses the backspace key. </description> </method> <method name="_copy" qualifiers="virtual"> <return type="void" /> + <param index="0" name="caret_index" type="int" /> <description> Override this method to define what happens when the user performs a copy operation. </description> </method> <method name="_cut" qualifiers="virtual"> <return type="void" /> + <param index="0" name="caret_index" type="int" /> <description> Override this method to define what happens when the user performs a cut operation. </description> @@ -31,23 +35,34 @@ <method name="_handle_unicode_input" qualifiers="virtual"> <return type="void" /> <param index="0" name="unicode_char" type="int" /> + <param index="1" name="caret_index" type="int" /> <description> Override this method to define what happens when the user types in the provided key [param unicode_char]. </description> </method> <method name="_paste" qualifiers="virtual"> <return type="void" /> + <param index="0" name="caret_index" type="int" /> <description> Override this method to define what happens when the user performs a paste operation. </description> </method> <method name="_paste_primary_clipboard" qualifiers="virtual"> <return type="void" /> + <param index="0" name="caret_index" type="int" /> <description> Override this method to define what happens when the user performs a paste operation with middle mouse button. [b]Note:[/b] This method is only implemented on Linux. </description> </method> + <method name="add_caret"> + <return type="int" /> + <param index="0" name="line" type="int" /> + <param index="1" name="col" type="int" /> + <description> + Adds a new caret at the given location. Returns the index of the new caret, or [code]-1[/code] if the location is invalid. + </description> + </method> <method name="add_gutter"> <return type="void" /> <param index="0" name="at" type="int" default="-1" /> @@ -55,14 +70,27 @@ Register a new gutter to this [TextEdit]. Use [param at] to have a specific gutter order. A value of [code]-1[/code] appends the gutter to the right. </description> </method> + <method name="adjust_carets_after_edit"> + <return type="void" /> + <param index="0" name="caret" type="int" /> + <param index="1" name="from_line" type="int" /> + <param index="2" name="from_col" type="int" /> + <param index="3" name="to_line" type="int" /> + <param index="4" name="to_col" type="int" /> + <description> + Reposition the carets affected by the edit. This assumes edits are applied in edit order, see [method get_caret_index_edit_order]. + </description> + </method> <method name="adjust_viewport_to_caret"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Adjust the viewport so the caret is visible. </description> </method> <method name="backspace"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Called when the user presses the backspace key. Can be overridden with [method _backspace]. </description> @@ -75,6 +103,7 @@ </method> <method name="center_viewport_to_caret"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Centers the viewport on the line the editing caret is at. This also resets the [member scroll_horizontal] value to [code]0[/code]. </description> @@ -93,28 +122,38 @@ </method> <method name="copy"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Copies the current text selection. Can be overridden with [method _copy]. </description> </method> <method name="cut"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Cut's the current selection. Can be overridden with [method _cut]. </description> </method> <method name="delete_selection"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Deletes the selected text. </description> </method> <method name="deselect"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Deselects the current selection. </description> </method> + <method name="end_action"> + <return type="void" /> + <description> + Marks the end of steps in the current action started with [method start_action]. + </description> + </method> <method name="end_complex_operation"> <return type="void" /> <description> @@ -123,24 +162,40 @@ </method> <method name="get_caret_column" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the column the editing caret is at. </description> </method> + <method name="get_caret_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the number of carets in this [TextEdit]. + </description> + </method> <method name="get_caret_draw_pos" qualifiers="const"> <return type="Vector2" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the caret pixel draw position. </description> </method> + <method name="get_caret_index_edit_order"> + <return type="PackedInt32Array" /> + <description> + Returns a list of caret indexes in their edit order, this done from bottom to top. Edit order refers to the way actions such as [method insert_text_at_caret] are applied. + </description> + </method> <method name="get_caret_line" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the line the editing caret is on. </description> </method> <method name="get_caret_wrap_index" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the wrap index the editing caret is on. </description> @@ -381,32 +436,37 @@ Returns the scroll position for [param wrap_index] of [param line]. </description> </method> - <method name="get_selected_text" qualifiers="const"> + <method name="get_selected_text"> <return type="String" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Returns the text inside the selection. </description> </method> <method name="get_selection_column" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the original start column of the selection. </description> </method> <method name="get_selection_from_column" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the selection begin column. </description> </method> <method name="get_selection_from_line" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the selection begin line. </description> </method> <method name="get_selection_line" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the original start line of the selection. </description> @@ -419,12 +479,14 @@ </method> <method name="get_selection_to_column" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the selection end column. </description> </method> <method name="get_selection_to_line" qualifiers="const"> <return type="int" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns the selection end line. </description> @@ -476,6 +538,7 @@ </method> <method name="get_word_under_caret" qualifiers="const"> <return type="String" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Returns a [String] text with the word under the caret's location. </description> @@ -494,6 +557,7 @@ </method> <method name="has_selection" qualifiers="const"> <return type="bool" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Returns [code]true[/code] if the user has selected text. </description> @@ -515,12 +579,14 @@ <method name="insert_text_at_caret"> <return type="void" /> <param index="0" name="text" type="String" /> + <param index="1" name="caret_index" type="int" default="-1" /> <description> Insert the specified text at the caret position. </description> </method> <method name="is_caret_visible" qualifiers="const"> <return type="bool" /> + <param index="0" name="caret_index" type="int" default="0" /> <description> Returns [code]true[/code] if the caret is visible on the screen. </description> @@ -576,6 +642,7 @@ <method name="is_mouse_over_selection" qualifiers="const"> <return type="bool" /> <param index="0" name="edges" type="bool" /> + <param index="1" name="caret_index" type="int" default="-1" /> <description> Returns whether the mouse is over selection. If [param edges] is [code]true[/code], the edges are considered part of the selection. </description> @@ -601,18 +668,41 @@ Merge the gutters from [param from_line] into [param to_line]. Only overwritable gutters will be copied. </description> </method> + <method name="merge_overlapping_carets"> + <return type="void" /> + <description> + Merges any overlapping carets. Will favour the newest caret, or the caret with a selection. + [b]Note:[/b] This is not called when a caret changes position but after certain actions, so it is possible to get into a state where carets overlap. + </description> + </method> <method name="paste"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Paste at the current location. Can be overridden with [method _paste]. </description> </method> + <method name="paste_primary_clipboard"> + <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> + <description> + Pastes the primary clipboard. + </description> + </method> <method name="redo"> <return type="void" /> <description> Perform redo operation. </description> </method> + <method name="remove_caret"> + <return type="void" /> + <param index="0" name="caret" type="int" /> + <description> + Removes the given caret index. + [b]Note:[/b] This can result in adjustment of all other caret indices. + </description> + </method> <method name="remove_gutter"> <return type="void" /> <param index="0" name="gutter" type="int" /> @@ -620,6 +710,12 @@ Removes the gutter from this [TextEdit]. </description> </method> + <method name="remove_secondary_carets"> + <return type="void" /> + <description> + Removes all additional carets. + </description> + </method> <method name="remove_text"> <return type="void" /> <param index="0" name="from_line" type="int" /> @@ -666,6 +762,7 @@ <param index="1" name="from_column" type="int" /> <param index="2" name="to_line" type="int" /> <param index="3" name="to_column" type="int" /> + <param index="4" name="caret_index" type="int" default="0" /> <description> Perform selection, from line/column to line/column. If [member selecting_enabled] is [code]false[/code], no selection will occur. @@ -680,6 +777,7 @@ </method> <method name="select_word_under_caret"> <return type="void" /> + <param index="0" name="caret_index" type="int" default="-1" /> <description> Selects the word under the caret. </description> @@ -688,9 +786,11 @@ <return type="void" /> <param index="0" name="column" type="int" /> <param index="1" name="adjust_viewport" type="bool" default="true" /> + <param index="2" name="caret_index" type="int" default="0" /> <description> Moves the caret to the specified [param column] index. If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs. + [b]Note:[/b] If supporting multiple carets this will not check for any overlap. See [method merge_overlapping_carets]. </description> </method> <method name="set_caret_line"> @@ -699,10 +799,12 @@ <param index="1" name="adjust_viewport" type="bool" default="true" /> <param index="2" name="can_be_hidden" type="bool" default="true" /> <param index="3" name="wrap_index" type="int" default="0" /> + <param index="4" name="caret_index" type="int" default="0" /> <description> Moves the caret to the specified [param line] index. If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs. If [param can_be_hidden] is [code]true[/code], the specified [code]line[/code] can be hidden. + [b]Note:[/b] If supporting multiple carets this will not check for any overlap. See [method merge_overlapping_carets]. </description> </method> <method name="set_gutter_clickable"> @@ -872,6 +974,7 @@ <param index="0" name="mode" type="int" enum="TextEdit.SelectionMode" /> <param index="1" name="line" type="int" default="-1" /> <param index="2" name="column" type="int" default="-1" /> + <param index="3" name="caret_index" type="int" default="0" /> <description> Sets the current selection mode. </description> @@ -890,6 +993,14 @@ Provide custom tooltip text. The callback method must take the following args: [code]hovered_word: String[/code] </description> </method> + <method name="start_action"> + <return type="void" /> + <param index="0" name="action" type="int" enum="TextEdit.EditAction" /> + <description> + Starts an action, will end the current action if [code]action[/code] is different. + An action will also end after a call to [method end_action], after [member ProjectSettings.gui/timers/text_edit_idle_detect_sec] is triggered or a new undoable step outside the [method start_action] and [method end_action] calls. + </description> + </method> <method name="swap_lines"> <return type="void" /> <param index="0" name="from_line" type="int" /> @@ -926,6 +1037,9 @@ If [code]true[/code], a right-click moves the caret at the mouse position before displaying the context menu. If [code]false[/code], the context menu disregards mouse location. </member> + <member name="caret_multiple" type="bool" setter="set_multiple_carets_enabled" getter="is_multiple_carets_enabled" default="true"> + Sets if multiple carets are allowed. + </member> <member name="caret_type" type="int" setter="set_caret_type" getter="get_caret_type" enum="TextEdit.CaretType" default="0"> Set the type of caret to draw. </member> @@ -1154,6 +1268,18 @@ <constant name="MENU_MAX" value="28" enum="MenuItems"> Represents the size of the [enum MenuItems] enum. </constant> + <constant name="ACTION_NONE" value="0" enum="EditAction"> + No current action. + </constant> + <constant name="ACTION_TYPING" value="1" enum="EditAction"> + A typing action. + </constant> + <constant name="ACTION_BACKSPACE" value="2" enum="EditAction"> + A backwards delete action. + </constant> + <constant name="ACTION_DELETE" value="3" enum="EditAction"> + A forward delete action. + </constant> <constant name="SEARCH_MATCH_CASE" value="1" enum="SearchFlags"> Match case when searching. </constant> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index fd9c44091c..348184a16a 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -155,6 +155,7 @@ <method name="get_layers_count" qualifiers="const"> <return type="int" /> <description> + Returns the number of layers in the TileMap. </description> </method> <method name="get_neighbor_cell" qualifiers="const"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 87ee26fa32..998e782975 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -129,6 +129,9 @@ <method name="is_input_handled" qualifiers="const"> <return type="bool" /> <description> + Returns whether the current [InputEvent] has been handled. Input events are not handled until [method set_input_as_handled] has been called during the lifetime of an [InputEvent]. + This is usually done as part of input handling methods like [method Node._input], [method Control._gui_input] or others, as well as in corresponding signal handlers. + If [member handle_input_locally] is set to [code]false[/code], this method will try finding the first parent viewport that is set to handle input locally, and return its value for [method is_input_handled] instead. </description> </method> <method name="push_input"> @@ -136,6 +139,13 @@ <param index="0" name="event" type="InputEvent" /> <param index="1" name="in_local_coords" type="bool" default="false" /> <description> + Triggers the given [param event] in this [Viewport]. This can be used to pass an [InputEvent] between viewports, or to locally apply inputs that were sent over the network or saved to a file. + If [param in_local_coords] is [code]false[/code], the event's position is in the embedder's coordinates and will be converted to viewport coordinates. If [param in_local_coords] is [code]true[/code], the event's position is in viewport coordinates. + While this method serves a similar purpose as [method Input.parse_input_event], it does not remap the specified [param event] based on project settings like [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse]. + Calling this method will propagate calls to child nodes for following methods in the given order: + - [method Node._input] + - [method Control._gui_input] for [Control] nodes + If an earlier method marks the input as handled via [method set_input_as_handled], any later method in this list will not be called. </description> </method> <method name="push_text_input"> @@ -149,6 +159,15 @@ <param index="0" name="event" type="InputEvent" /> <param index="1" name="in_local_coords" type="bool" default="false" /> <description> + Triggers the given [InputEvent] in this [Viewport]. This can be used to pass input events between viewports, or to locally apply inputs that were sent over the network or saved to a file. + If [param in_local_coords] is [code]false[/code], the event's position is in the embedder's coordinates and will be converted to viewport coordinates. If [param in_local_coords] is [code]true[/code], the event's position is in viewport coordinates. + While this method serves a similar purpose as [method Input.parse_input_event], it does not remap the specified [param event] based on project settings like [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse]. + Calling this method will propagate calls to child nodes for following methods in the given order: + - [method Node._shortcut_input] + - [method Node._unhandled_input] + - [method Node._unhandled_key_input] + If an earlier method marks the input as handled via [method set_input_as_handled], any later method in this list will not be called. + If none of the methods handle the event and [member physics_object_picking] is [code]true[/code], the event is used for physics object picking. </description> </method> <method name="set_input_as_handled"> @@ -212,6 +231,9 @@ If [code]true[/code], the GUI controls on the viewport will lay pixel perfectly. </member> <member name="handle_input_locally" type="bool" setter="set_handle_input_locally" getter="is_handling_input_locally" default="true"> + If [code]true[/code], this viewport will mark incoming input events as handled by itself. If [code]false[/code], this is instead done by the the first parent viewport that is set to handle input locally. + A [SubViewportContainer] will automatically set this property to [code]false[/code] for the [Viewport] contained inside of it. + See also [method set_input_as_handled] and [method is_input_handled]. </member> <member name="mesh_lod_threshold" type="float" setter="set_mesh_lod_threshold" getter="get_mesh_lod_threshold" default="1.0"> The automatic LOD bias to use for meshes rendered within the [Viewport] (this is analogous to [member ReflectionProbe.mesh_lod_threshold]). Higher values will use less detailed versions of meshes that have LOD variations generated. If set to [code]0.0[/code], automatic LOD is disabled. Increase [member mesh_lod_threshold] to improve performance at the cost of geometry detail. diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index c585b54ee1..c278f7031f 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -355,7 +355,7 @@ </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="Window.Mode" default="0"> Set's the window's current mode. - [b]Note:[/b] Fullscreen mode is not exclusive fullscreen on Windows and Linux. + [b]Note:[/b] Fullscreen mode is not exclusive full screen on Windows and Linux. </member> <member name="popup_window" type="bool" setter="set_flag" getter="get_flag" default="false"> If [code]true[/code], the [Window] will be considered a popup. Popups are sub-windows that don't show as separate windows in system's window manager's window list and will send close request when anything is clicked outside of them (unless [member exclusive] is enabled). @@ -377,12 +377,13 @@ The window's title. If the [Window] is non-embedded, title styles set in [Theme] will have no effect. </member> <member name="transient" type="bool" setter="set_transient" getter="is_transient" default="false"> - If [code]true[/code], the [Window] is transient, i.e. it's considered a child of another [Window]. Transient windows can't be in fullscreen mode and will return focus to their parent when closed. + If [code]true[/code], the [Window] is transient, i.e. it's considered a child of another [Window]. Transient window is will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode. Note that behavior might be different depending on the platform. </member> <member name="transparent" type="bool" setter="set_flag" getter="get_flag" default="false"> If [code]true[/code], the [Window]'s background can be transparent. This is best used with embedded windows. - [b]Note:[/b] This flag has no effect if [member ProjectSettings.display/window/per_pixel_transparency/allowed] is set to [code]false[/code]. + [b]Note:[/b] For native windows, this flag has no effect if [member ProjectSettings.display/window/per_pixel_transparency/allowed] is set to [code]false[/code]. + [b]Note:[/b] Transparency support is implemented on Linux, macOS and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities. </member> <member name="unfocusable" type="bool" setter="set_flag" getter="get_flag" default="false"> If [code]true[/code], the [Window] can't be focused nor interacted with. It can still be visible. @@ -457,7 +458,7 @@ </signal> <signal name="titlebar_changed"> <description> - Emitted when window title bar decorations are changed, e.g., macOS window enter/exit full screen mode, or extend-to-title flag is changed. + Emitted when window title bar decorations are changed, e.g. macOS window enter/exit full screen mode, or extend-to-title flag is changed. </description> </signal> <signal name="visibility_changed"> @@ -468,7 +469,7 @@ <signal name="window_input"> <param index="0" name="event" type="InputEvent" /> <description> - Emitted when the [Window] is currently focused and receives any input, passing the received event as an argument. + Emitted when the [Window] is currently focused and receives any input, passing the received event as an argument. The event's position, if present, is in the embedder's coordinate system. </description> </signal> </signals> @@ -484,43 +485,45 @@ [b]Note:[/b] As an optimization, this notification won't be sent from changes that occur while this node is outside of the scene tree. Instead, all of the theme item updates can be applied at once when the node enters the scene tree. </constant> <constant name="MODE_WINDOWED" value="0" enum="Mode"> - Windowed mode, i.e. [Window] doesn't occupy whole screen (unless set to the size of the screen). + Windowed mode, i.e. [Window] doesn't occupy the whole screen (unless set to the size of the screen). </constant> <constant name="MODE_MINIMIZED" value="1" enum="Mode"> - Minimized window mode, i.e. [Window] is not visible and available on window manager's window list. Normally happens when the minimize button is presesd. + Minimized window mode, i.e. [Window] is not visible and available on window manager's window list. Normally happens when the minimize button is pressed. </constant> <constant name="MODE_MAXIMIZED" value="2" enum="Mode"> - Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the minimize button is presesd. + Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the minimize button is pressed. </constant> <constant name="MODE_FULLSCREEN" value="3" enum="Mode"> - Fullscreen window mode. Note that this is not [i]exclusive[/i] fullscreen. On Windows and Linux, a borderless window is used to emulate fullscreen. On macOS, a new desktop is used to display the running project. - 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. + Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux, a borderless window is used to emulate full screen. On macOS, a new desktop is used to display the running project. + Regardless of the platform, enabling full screen 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 full screen mode. </constant> <constant name="MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="Mode"> - Exclusive fullscreen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant MODE_FULLSCREEN]. - Only one window in exclusive fullscreen mode can be visible on a given screen at a time. If multiple windows are in exclusive fullscreen mode for the same screen, the last one being set to this mode takes precedence. - 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. + Exclusive full screen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant MODE_FULLSCREEN]. + Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence. + Regardless of the platform, enabling full screen 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 full screen mode. </constant> <constant name="FLAG_RESIZE_DISABLED" value="0" enum="Flags"> - The window's ability to be resized. Set with [member unresizable]. + The window can't be resizing by dragging its resize grip. It's still possible to resize the window using [member size]. This flag is ignored for full screen windows. Set with [member unresizable]. </constant> <constant name="FLAG_BORDERLESS" value="1" enum="Flags"> - Borderless window. Set with [member borderless]. + The window do not have native title bar and other decorations. This flag is ignored for full-screen windows. Set with [member borderless]. </constant> <constant name="FLAG_ALWAYS_ON_TOP" value="2" enum="Flags"> - Flag for making the window always on top of all other windows. Set with [member always_on_top]. + The window is floating on top of all other windows. This flag is ignored for full-screen windows. Set with [member always_on_top]. </constant> <constant name="FLAG_TRANSPARENT" value="3" enum="Flags"> - Flag for per-pixel transparency. Set with [member transparent]. + The window background can be transparent. + [b]Note:[/b] This flag has no effect if [member ProjectSettings.display/window/per_pixel_transparency/allowed] is set to [code]false[/code]. Set with [member transparent]. </constant> <constant name="FLAG_NO_FOCUS" value="4" enum="Flags"> - The window's ability to gain focus. Set with [member unfocusable]. + The window can't be focused. No-focus window will ignore all input, except mouse clicks. Set with [member unfocusable]. </constant> <constant name="FLAG_POPUP" value="5" enum="Flags"> - Whether the window is popup or a regular window. Set with [member popup_window]. + Window is part of menu or [OptionButton] dropdown. This flag can't be changed when the window is visible. An active popup window will exclusively receive all input, without stealing focus from its parent. Popup windows are automatically closed when uses click outside it, or when an application is switched. Popup window must have [code]transient parent[/code] set (see [member transient]). </constant> <constant name="FLAG_EXTEND_TO_TITLE" value="6" enum="Flags"> - Window contents is expanded to the full size of the window, window title bar is transparent. + Window content is expanded to the full size of the window. Unlike borderless window, the frame is left intact and can be used to resize the window, title bar is transparent, but have minimize/maximize/close buttons. Set with [member extend_to_title]. + [b]Note:[/b] This flag is implemented on macOS. </constant> <constant name="FLAG_MAX" value="7" enum="Flags"> Max value of the [enum Flags]. diff --git a/doc/classes/World2D.xml b/doc/classes/World2D.xml index b0cf126d7b..4a13389708 100644 --- a/doc/classes/World2D.xml +++ b/doc/classes/World2D.xml @@ -14,7 +14,7 @@ The [RID] of this world's canvas resource. Used by the [RenderingServer] for 2D drawing. </member> <member name="direct_space_state" type="PhysicsDirectSpaceState2D" setter="" getter="get_direct_space_state"> - Direct access to the world's physics 2D space state. Used for querying current and potential collisions. When using multi-threaded physics, access is limited to [code]_physics_process(delta)[/code] in the main thread. + Direct access to the world's physics 2D space state. Used for querying current and potential collisions. When using multi-threaded physics, access is limited to [method Node._physics_process] in the main thread. </member> <member name="navigation_map" type="RID" setter="" getter="get_navigation_map"> The [RID] of this world's navigation map. Used by the [NavigationServer2D]. diff --git a/doc/classes/World3D.xml b/doc/classes/World3D.xml index f3c7136075..5e58bb0360 100644 --- a/doc/classes/World3D.xml +++ b/doc/classes/World3D.xml @@ -14,7 +14,7 @@ The default [CameraAttributes] resource to use if none set on the [Camera3D]. </member> <member name="direct_space_state" type="PhysicsDirectSpaceState3D" setter="" getter="get_direct_space_state"> - Direct access to the world's physics 3D space state. Used for querying current and potential collisions. + Direct access to the world's physics 3D space state. Used for querying current and potential collisions. When using multi-threaded physics, access is limited to [method Node._physics_process] in the main thread. </member> <member name="environment" type="Environment" setter="set_environment" getter="get_environment"> The World3D's [Environment]. diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml index 06ef18b534..5958999037 100644 --- a/doc/classes/XRInterfaceExtension.xml +++ b/doc/classes/XRInterfaceExtension.xml @@ -39,6 +39,18 @@ Returns the capabilities of this interface. </description> </method> + <method name="_get_color_texture" qualifiers="virtual"> + <return type="RID" /> + <description> + Return color texture into which to render (if applicable). + </description> + </method> + <method name="_get_depth_texture" qualifiers="virtual"> + <return type="RID" /> + <description> + Return depth texture into which to render (if applicable). + </description> + </method> <method name="_get_name" qualifiers="virtual const"> <return type="StringName" /> <description> @@ -100,6 +112,12 @@ Returns a [Transform3D] for a given view. </description> </method> + <method name="_get_velocity_texture" qualifiers="virtual"> + <return type="RID" /> + <description> + Return velocity texture into which to render (if applicable). + </description> + </method> <method name="_get_view_count" qualifiers="virtual"> <return type="int" /> <description> @@ -213,6 +231,16 @@ Blits our render results to screen optionally applying lens distortion. This can only be called while processing [code]_commit_views[/code]. </description> </method> + <method name="get_color_texture"> + <return type="RID" /> + <description> + </description> + </method> + <method name="get_depth_texture"> + <return type="RID" /> + <description> + </description> + </method> <method name="get_render_target_texture"> <return type="RID" /> <param index="0" name="render_target" type="RID" /> @@ -220,5 +248,10 @@ Returns a valid [RID] for a texture to which we should render the current frame if supported by the interface. </description> </method> + <method name="get_velocity_texture"> + <return type="RID" /> + <description> + </description> + </method> </methods> </class> diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index 372ac00493..15556e3193 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -235,14 +235,14 @@ public: GLuint vertex_buffer; GLuint vertex_array; GLuint index_buffer; - int count; + int count = 0; bool color_disabled = false; Color color; }; struct { HashMap<PolygonID, PolygonBuffers> polygons; - PolygonID last_id; + PolygonID last_id = 0; } polygon_buffers; RendererCanvasRender::PolygonID request_polygon(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>()) override; diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 3575837794..3ac923d33c 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -280,11 +280,7 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); ERR_FAIL_COND(!rt); - if (rt->external.fbo != 0) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->external.fbo); - } else { - glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo); - } + glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo); glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); // Flip content upside down to correct for coordinates. diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 82d054ac59..4c71edc24c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -753,7 +753,7 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::ORIENTATION, sky_transform, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); - GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, camera.matrix[2][0], camera.matrix[0][0], camera.matrix[2][1], camera.matrix[1][1], shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); + GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, camera.columns[2][0], camera.columns[0][0], camera.columns[2][1], camera.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); @@ -854,7 +854,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::POSITION, p_transform.origin, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); - GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, cm.matrix[2][0], cm.matrix[0][0], cm.matrix[2][1], cm.matrix[1][1], shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); + GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, cm.columns[2][0], cm.columns[0][0], cm.columns[2][1], cm.columns[1][1], shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); glBindVertexArray(sky_globals.screen_triangle_array); diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index bbbad50b14..8e6009c943 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -714,7 +714,7 @@ _FORCE_INLINE_ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataTy Projection v = value; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - gui[i * 4 + j] = v.matrix[i][j]; + gui[i * 4 + j] = v.columns[i][j]; } } } diff --git a/drivers/gles3/storage/material_storage.h b/drivers/gles3/storage/material_storage.h index 65c46631ed..6504c7748c 100644 --- a/drivers/gles3/storage/material_storage.h +++ b/drivers/gles3/storage/material_storage.h @@ -494,7 +494,7 @@ public: static _FORCE_INLINE_ void store_camera(const Projection &p_mtx, float *p_array) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - p_array[i * 4 + j] = p_mtx.matrix[i][j]; + p_array[i * 4 + j] = p_mtx.columns[i][j]; } } } diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 8ea10539ec..5950997f9b 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1343,24 +1343,6 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { rt->fbo = 0; rt->color = 0; } - /* - if (rt->external.fbo != 0) { - // free this - glDeleteFramebuffers(1, &rt->external.fbo); - - // clean up our texture - Texture *t = get_texture(rt->external.texture); - t->alloc_height = 0; - t->alloc_width = 0; - t->width = 0; - t->height = 0; - t->active = false; - texture_free(rt->external.texture); - memdelete(t); - - rt->external.fbo = 0; - } - */ Texture *tex = get_texture(rt->texture); tex->alloc_height = 0; @@ -1412,6 +1394,13 @@ void TextureStorage::render_target_set_position(RID p_render_target, int p_x, in rt->position = Point2i(p_x, p_y); } +Point2i TextureStorage::render_target_get_position(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, Point2i()); + + return rt->position; +}; + void TextureStorage::render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND(!rt); @@ -1428,9 +1417,9 @@ void TextureStorage::render_target_set_size(RID p_render_target, int p_width, in } // TODO: convert to Size2i internally -Size2i TextureStorage::render_target_get_size(RID p_render_target) { +Size2i TextureStorage::render_target_get_size(RID p_render_target) const { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); - ERR_FAIL_COND_V(!rt, Size2()); + ERR_FAIL_COND_V(!rt, Size2i()); return rt->size; } @@ -1439,105 +1428,7 @@ RID TextureStorage::render_target_get_texture(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, RID()); - if (rt->external.fbo == 0) { - return rt->texture; - } else { - return rt->external.texture; - } -} - -void TextureStorage::render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) { - RenderTarget *rt = render_target_owner.get_or_null(p_render_target); - ERR_FAIL_COND(!rt); - - if (p_texture_id == 0) { - if (rt->external.fbo != 0) { - // free this - glDeleteFramebuffers(1, &rt->external.fbo); - - // and this - if (rt->external.depth != 0) { - glDeleteRenderbuffers(1, &rt->external.depth); - } - - // clean up our texture - Texture *t = get_texture(rt->external.texture); - t->alloc_height = 0; - t->alloc_width = 0; - t->width = 0; - t->height = 0; - t->active = false; - texture_free(rt->external.texture); - //memdelete(t); - - rt->external.fbo = 0; - rt->external.color = 0; - rt->external.depth = 0; - } - } else { - Texture *t; - - if (rt->external.fbo == 0) { - // create our fbo - glGenFramebuffers(1, &rt->external.fbo); - glBindFramebuffer(GL_FRAMEBUFFER, rt->external.fbo); - - // allocate a texture - t = memnew(Texture); - - t->type = Texture::TYPE_2D; - t->width = 0; - t->height = 0; - t->alloc_height = 0; - t->alloc_width = 0; - t->format = Image::FORMAT_RGBA8; - t->target = GL_TEXTURE_2D; - t->gl_format_cache = 0; - t->gl_internal_format_cache = 0; - t->gl_type_cache = 0; - t->total_data_size = 0; - t->mipmaps = 1; - t->active = true; - t->tex_id = 0; - t->render_target = rt; - t->is_render_target = true; - - //rt->external.texture = make_rid(t); - - } else { - // bind our frame buffer - glBindFramebuffer(GL_FRAMEBUFFER, rt->external.fbo); - - // find our texture - t = get_texture(rt->external.texture); - } - - // set our texture - t->tex_id = p_texture_id; - rt->external.color = p_texture_id; - - // size shouldn't be different - t->width = rt->size.x; - t->height = rt->size.y; - t->alloc_height = rt->size.x; - t->alloc_width = rt->size.y; - - // Switch our texture on our frame buffer - { - // set our texture as the destination for our framebuffer - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_texture_id, 0); - } - - // check status and unbind - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - - if (status != GL_FRAMEBUFFER_COMPLETE) { - WARN_PRINT("framebuffer fail, status: " + get_framebuffer_error(status)); - } - - ERR_FAIL_COND(status != GL_FRAMEBUFFER_COMPLETE); - } + return rt->texture; } void TextureStorage::render_target_set_transparent(RID p_render_target, bool p_transparent) { @@ -1550,6 +1441,13 @@ void TextureStorage::render_target_set_transparent(RID p_render_target, bool p_t _update_render_target(rt); } +bool TextureStorage::render_target_get_transparent(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, false); + + return rt->is_transparent; +} + void TextureStorage::render_target_set_direct_to_screen(RID p_render_target, bool p_direct_to_screen) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND(!rt); @@ -1564,7 +1462,14 @@ void TextureStorage::render_target_set_direct_to_screen(RID p_render_target, boo _update_render_target(rt); } -bool TextureStorage::render_target_was_used(RID p_render_target) { +bool TextureStorage::render_target_get_direct_to_screen(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, false); + + return rt->direct_to_screen; +} + +bool TextureStorage::render_target_was_used(RID p_render_target) const { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, false); @@ -1591,6 +1496,13 @@ void TextureStorage::render_target_set_msaa(RID p_render_target, RS::ViewportMSA _update_render_target(rt); } +RS::ViewportMSAA TextureStorage::render_target_get_msaa(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RS::VIEWPORT_MSAA_DISABLED); + + return rt->msaa; +} + void TextureStorage::render_target_request_clear(RID p_render_target, const Color &p_clear_color) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND(!rt); diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 16bf406845..0d4cd9c7e3 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -327,16 +327,6 @@ private: }; struct RenderTarget { - struct External { - GLuint fbo = 0; - GLuint color = 0; - GLuint depth = 0; - RID texture; - - External() { - } - } external; - Point2i position = Point2i(0, 0); Size2i size = Size2i(0, 0); int mipmap_count = 1; @@ -524,17 +514,19 @@ public: virtual RID render_target_create() override; virtual void render_target_free(RID p_rid) override; + virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) override; + virtual Point2i render_target_get_position(RID p_render_target) const override; virtual void render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) override; - Size2i render_target_get_size(RID p_render_target); - virtual RID render_target_get_texture(RID p_render_target) override; - virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) override; - + virtual Size2i render_target_get_size(RID p_render_target) const override; virtual void render_target_set_transparent(RID p_render_target, bool p_is_transparent) override; + virtual bool render_target_get_transparent(RID p_render_target) const override; virtual void render_target_set_direct_to_screen(RID p_render_target, bool p_direct_to_screen) override; - virtual bool render_target_was_used(RID p_render_target) override; + virtual bool render_target_get_direct_to_screen(RID p_render_target) const override; + virtual bool render_target_was_used(RID p_render_target) const override; void render_target_clear_used(RID p_render_target); virtual void render_target_set_msaa(RID p_render_target, RS::ViewportMSAA p_msaa) override; + virtual RS::ViewportMSAA render_target_get_msaa(RID p_render_target) const override; // new void render_target_set_as_unused(RID p_render_target) override { @@ -554,8 +546,20 @@ public: void render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps); void render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color); void render_target_gen_back_buffer_mipmaps(RID p_render_target, const Rect2i &p_region); - virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override{}; - virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override{}; + + virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override {} + virtual RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const override { return RS::VIEWPORT_VRS_DISABLED; } + virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_vrs_texture(RID p_render_target) const override { return RID(); } + + virtual void render_target_set_override_color(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_color(RID p_render_target) const override { return RID(); } + virtual void render_target_set_override_depth(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_depth(RID p_render_target) const override { return RID(); } + virtual void render_target_set_override_velocity(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_velocity(RID p_render_target) const override { return RID(); } + + virtual RID render_target_get_texture(RID p_render_target) override; void bind_framebuffer(GLuint framebuffer) { glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.h b/drivers/pulseaudio/audio_driver_pulseaudio.h index 85e328b49f..ae6e0acc97 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.h +++ b/drivers/pulseaudio/audio_driver_pulseaudio.h @@ -48,8 +48,8 @@ class AudioDriverPulseAudio : public AudioDriver { pa_context *pa_ctx = nullptr; pa_stream *pa_str = nullptr; pa_stream *pa_rec_str = nullptr; - pa_channel_map pa_map; - pa_channel_map pa_rec_map; + pa_channel_map pa_map = {}; + pa_channel_map pa_rec_map = {}; String device_name = "Default"; String new_device = "Default"; diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index c25ceba44d..d894ceba59 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -71,7 +71,7 @@ bool DirAccessUnix::file_exists(String p_file) { p_file = fix_path(p_file); - struct stat flags; + struct stat flags = {}; bool success = (stat(p_file.utf8().get_data(), &flags) == 0); if (success && S_ISDIR(flags.st_mode)) { @@ -90,7 +90,7 @@ bool DirAccessUnix::dir_exists(String p_dir) { p_dir = fix_path(p_dir); - struct stat flags; + struct stat flags = {}; bool success = (stat(p_dir.utf8().get_data(), &flags) == 0); return (success && S_ISDIR(flags.st_mode)); @@ -125,7 +125,7 @@ uint64_t DirAccessUnix::get_modified_time(String p_file) { p_file = fix_path(p_file); - struct stat flags; + struct stat flags = {}; bool success = (stat(p_file.utf8().get_data(), &flags) == 0); if (success) { @@ -158,7 +158,7 @@ String DirAccessUnix::get_next() { if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) { String f = current_dir.path_join(fname); - struct stat flags; + struct stat flags = {}; if (stat(f.utf8().get_data(), &flags) == 0) { _cisdir = S_ISDIR(flags.st_mode); } else { @@ -412,7 +412,7 @@ Error DirAccessUnix::remove(String p_path) { p_path = fix_path(p_path); - struct stat flags; + struct stat flags = {}; if ((stat(p_path.utf8().get_data(), &flags) != 0)) { return FAILED; } @@ -431,7 +431,7 @@ bool DirAccessUnix::is_link(String p_file) { p_file = fix_path(p_file); - struct stat flags; + struct stat flags = {}; if ((lstat(p_file.utf8().get_data(), &flags) != 0)) { return FAILED; } diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 0df645c4e5..0b80fb1491 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -86,7 +86,7 @@ Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) { backend (unix-compatible mostly) supports utf8 encoding */ //printf("opening %s as %s\n", p_path.utf8().get_data(), path.utf8().get_data()); - struct stat st; + struct stat st = {}; int err = stat(path.utf8().get_data(), &st); if (!err) { switch (st.st_mode & S_IFMT) { @@ -252,7 +252,7 @@ void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) { bool FileAccessUnix::file_exists(const String &p_path) { int err; - struct stat st; + struct stat st = {}; String filename = fix_path(p_path); // Does the name exist at all? @@ -284,7 +284,7 @@ bool FileAccessUnix::file_exists(const String &p_path) { uint64_t FileAccessUnix::_get_modified_time(const String &p_file) { String file = fix_path(p_file); - struct stat flags; + struct stat flags = {}; int err = stat(file.utf8().get_data(), &flags); if (!err) { @@ -297,7 +297,7 @@ uint64_t FileAccessUnix::_get_modified_time(const String &p_file) { uint32_t FileAccessUnix::_get_unix_permissions(const String &p_file) { String file = fix_path(p_file); - struct stat flags; + struct stat flags = {}; int err = stat(file.utf8().get_data(), &flags); if (!err) { diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 748b48d316..10d65b83db 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -500,10 +500,6 @@ bool OS_Unix::set_environment(const String &p_var, const String &p_value) const return setenv(p_var.utf8().get_data(), p_value.utf8().get_data(), /* overwrite: */ true) == 0; } -int OS_Unix::get_processor_count() const { - return sysconf(_SC_NPROCESSORS_CONF); -} - String OS_Unix::get_user_data_dir() const { String appname = get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name")); if (!appname.is_empty()) { diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index ce81ca0aca..fce962e32c 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -84,8 +84,6 @@ public: virtual bool set_environment(const String &p_var, const String &p_value) const override; virtual String get_locale() const override; - virtual int get_processor_count() const override; - virtual void initialize_debugging() override; virtual String get_executable_path() const override; diff --git a/drivers/unix/thread_posix.cpp b/drivers/unix/thread_posix.cpp index 6292d8b3bc..f6adbee108 100644 --- a/drivers/unix/thread_posix.cpp +++ b/drivers/unix/thread_posix.cpp @@ -70,7 +70,7 @@ static Error set_name(const String &p_name) { } void init_thread_posix() { - Thread::_set_platform_funcs(&set_name, nullptr); + Thread::_set_platform_functions({ .set_name = set_name }); } #endif // UNIX_ENABLED || PTHREAD_ENABLED diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 98adc0f16e..e64b0c4a84 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -85,19 +85,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::_debug_messenger_callback( return VK_FALSE; } - // Workaround for Vulkan-Loader usability bug: https://github.com/KhronosGroup/Vulkan-Loader/issues/262. - if (strstr(pCallbackData->pMessage, "wrong ELF class: ELFCLASS32") != nullptr) { - return VK_FALSE; - } - -#ifdef WINDOWS_ENABLED - // Some software installs Vulkan overlays in Windows registry and never cleans them up on uninstall. - // So we get spammy error level messages from the loader about those - make them verbose instead. - if (strstr(pCallbackData->pMessage, "loader_get_json: Failed to open JSON file") != nullptr) { - messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; - } -#endif - if (pCallbackData->pMessageIdName && strstr(pCallbackData->pMessageIdName, "UNASSIGNED-CoreValidation-DrawState-ClearCmdBeforeDraw") != nullptr) { return VK_FALSE; } @@ -320,6 +307,16 @@ Error VulkanContext::_initialize_extensions() { VkBool32 platformSurfaceExtFound = 0; memset(extension_names, 0, sizeof(extension_names)); + // Only enable debug utils in verbose mode or DEV_ENABLED. + // End users would get spammed with messages of varying verbosity due to the + // mess that thirdparty layers/extensions and drivers seem to leave in their + // wake, making the Windows registry a bottomless pit of broken layer JSON. +#ifdef DEV_ENABLED + bool want_debug_utils = true; +#else + bool want_debug_utils = OS::get_singleton()->is_stdout_verbose(); +#endif + VkResult err = vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr); ERR_FAIL_COND_V(err != VK_SUCCESS && err != VK_INCOMPLETE, ERR_CANT_CREATE); @@ -347,8 +344,10 @@ Error VulkanContext::_initialize_extensions() { } } if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) { - extension_names[enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; - enabled_debug_utils = true; + if (want_debug_utils) { + extension_names[enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + enabled_debug_utils = true; + } } if (!strcmp(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, instance_extensions[i].extensionName)) { extension_names[enabled_extension_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; @@ -2261,8 +2260,6 @@ Error VulkanContext::swap_buffers() { } } #endif - static int total_frames = 0; - total_frames++; // print_line("current buffer: " + itos(current_buffer)); err = fpQueuePresentKHR(present_queue, &present); diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index b6348c5952..9b1ff78727 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -28,731 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "action_map_editor.h" - -#include "core/input/input_map.h" -#include "core/os/keyboard.h" +#include "editor/action_map_editor.h" #include "editor/editor_scale.h" -#include "scene/gui/separator.h" - -///////////////////////////////////////// - -// Maps to 2*axis if value is neg, or 2*axis+1 if value is pos. -static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = { - TTRC("Left Stick Left, Joystick 0 Left"), - TTRC("Left Stick Right, Joystick 0 Right"), - TTRC("Left Stick Up, Joystick 0 Up"), - TTRC("Left Stick Down, Joystick 0 Down"), - TTRC("Right Stick Left, Joystick 1 Left"), - TTRC("Right Stick Right, Joystick 1 Right"), - TTRC("Right Stick Up, Joystick 1 Up"), - TTRC("Right Stick Down, Joystick 1 Down"), - TTRC("Joystick 2 Left"), - TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right"), - TTRC("Joystick 2 Up"), - TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down"), - TTRC("Joystick 3 Left"), - TTRC("Joystick 3 Right"), - TTRC("Joystick 3 Up"), - TTRC("Joystick 3 Down"), - TTRC("Joystick 4 Left"), - TTRC("Joystick 4 Right"), - TTRC("Joystick 4 Up"), - TTRC("Joystick 4 Down"), -}; - -String InputEventConfigurationDialog::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) const { - ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent"); - - String text = p_event->as_text(); - - Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_command_or_control_autoremap()) { -#ifdef MACOS_ENABLED - text = text.replace("Command", "Command/Ctrl"); -#else - text = text.replace("Ctrl", "Command/Ctrl"); -#endif - } - Ref<InputEventMouse> mouse = p_event; - Ref<InputEventJoypadMotion> jp_motion = p_event; - Ref<InputEventJoypadButton> jp_button = p_event; - if (jp_motion.is_valid()) { - // Joypad motion events will display slightly differently than what the event->as_text() provides. See #43660. - String desc = TTR("Unknown Joypad Axis"); - if (jp_motion->get_axis() < JoyAxis::MAX) { - desc = RTR(_joy_axis_descriptions[2 * (size_t)jp_motion->get_axis() + (jp_motion->get_axis_value() < 0 ? 0 : 1)]); - } - - text = vformat("Joypad Axis %s %s (%s)", itos((int64_t)jp_motion->get_axis()), jp_motion->get_axis_value() < 0 ? "-" : "+", desc); - } - if (p_include_device && (mouse.is_valid() || jp_button.is_valid() || jp_motion.is_valid())) { - String device_string = _get_device_string(p_event->get_device()); - text += vformat(" - %s", device_string); - } - - return text; -} - -void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection) { - if (p_event.is_valid()) { - event = p_event; - - // Update Label - event_as_text->set_text(get_event_text(event, true)); - - Ref<InputEventKey> k = p_event; - Ref<InputEventMouseButton> mb = p_event; - Ref<InputEventJoypadButton> joyb = p_event; - Ref<InputEventJoypadMotion> joym = p_event; - Ref<InputEventWithModifiers> mod = p_event; - - // Update option values and visibility - bool show_mods = false; - bool show_device = false; - bool show_phys_key = false; - - if (mod.is_valid()) { - show_mods = true; - mod_checkboxes[MOD_ALT]->set_pressed(mod->is_alt_pressed()); - mod_checkboxes[MOD_SHIFT]->set_pressed(mod->is_shift_pressed()); - mod_checkboxes[MOD_CTRL]->set_pressed(mod->is_ctrl_pressed()); - mod_checkboxes[MOD_META]->set_pressed(mod->is_meta_pressed()); - - autoremap_command_or_control_checkbox->set_pressed(mod->is_command_or_control_autoremap()); - } - - if (k.is_valid()) { - show_phys_key = true; - physical_key_checkbox->set_pressed(k->get_physical_keycode() != Key::NONE && k->get_keycode() == Key::NONE); - - } else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) { - show_device = true; - _set_current_device(event->get_device()); - } - - mod_container->set_visible(show_mods); - device_container->set_visible(show_device); - physical_key_checkbox->set_visible(show_phys_key); - additional_options_container->show(); - - // Update selected item in input list. - if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) { - TreeItem *category = input_list_tree->get_root()->get_first_child(); - while (category) { - TreeItem *input_item = category->get_first_child(); - - if (input_item != nullptr) { - // input_type should always be > 0, unless the tree structure has been misconfigured. - int input_type = input_item->get_parent()->get_meta("__type", 0); - if (input_type == 0) { - return; - } - - // If event type matches input types of this category. - if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { - // Loop through all items of this category until one matches. - while (input_item) { - bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); - bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); - bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); - bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); - if (key_match || joyb_match || joym_match || mb_match) { - category->set_collapsed(false); - input_item->select(0); - input_list_tree->ensure_cursor_is_visible(); - return; - } - input_item = input_item->get_next(); - } - } - } - - category->set_collapsed(true); // Event not in this category, so collapse; - category = category->get_next(); - } - } - } else { - // Event is not valid, reset dialog - event = p_event; - Vector<String> strings; - - // Reset message, promp for input according to which input types are allowed. - String text = TTR("Perform an Input (%s)."); - - if (allowed_input_types & INPUT_KEY) { - strings.append(TTR("Key")); - } - - if (allowed_input_types & INPUT_JOY_BUTTON) { - strings.append(TTR("Joypad Button")); - } - if (allowed_input_types & INPUT_JOY_MOTION) { - strings.append(TTR("Joypad Axis")); - } - if (allowed_input_types & INPUT_MOUSE_BUTTON) { - strings.append(TTR("Mouse Button in area below")); - } - if (strings.size() == 0) { - text = TTR("Input Event dialog has been misconfigured: No input types are allowed."); - event_as_text->set_text(text); - } else { - String insert_text = String(", ").join(strings); - event_as_text->set_text(vformat(text, insert_text)); - } - - additional_options_container->hide(); - input_list_tree->deselect_all(); - _update_input_list(); - } -} - -void InputEventConfigurationDialog::_tab_selected(int p_tab) { - Callable signal_method = callable_mp(this, &InputEventConfigurationDialog::_listen_window_input); - if (p_tab == 0) { - // Start Listening. - if (!is_connected("window_input", signal_method)) { - connect("window_input", signal_method); - } - } else { - // Stop Listening. - if (is_connected("window_input", signal_method)) { - disconnect("window_input", signal_method); - } - input_list_tree->call_deferred(SNAME("ensure_cursor_is_visible")); - if (input_list_tree->get_selected() == nullptr) { - // If nothing selected, scroll to top. - input_list_tree->scroll_to_item(input_list_tree->get_root()); - } - } -} - -void InputEventConfigurationDialog::_listen_window_input(const Ref<InputEvent> &p_event) { - // Ignore if echo or not pressed - if (p_event->is_echo() || !p_event->is_pressed()) { - return; - } - - // Ignore mouse motion - Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { - return; - } - - // Ignore mouse button if not in the detection rect - Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid()) { - Rect2 r = mouse_detection_rect->get_rect(); - if (!r.has_point(mouse_detection_rect->get_local_mouse_position() + r.get_position())) { - return; - } - } - - // Create an editable reference - Ref<InputEvent> received_event = p_event; - - // Check what the type is and if it is allowed. - Ref<InputEventKey> k = received_event; - Ref<InputEventJoypadButton> joyb = received_event; - Ref<InputEventJoypadMotion> joym = received_event; - - int type = 0; - if (k.is_valid()) { - type = INPUT_KEY; - } else if (joyb.is_valid()) { - type = INPUT_JOY_BUTTON; - } else if (joym.is_valid()) { - type = INPUT_JOY_MOTION; - } else if (mb.is_valid()) { - type = INPUT_MOUSE_BUTTON; - } - - if (!(allowed_input_types & type)) { - return; - } - - if (joym.is_valid()) { - float axis_value = joym->get_axis_value(); - if (ABS(axis_value) < 0.9) { - // Ignore motion below 0.9 magnitude to avoid accidental touches - return; - } else { - // Always make the value 1 or -1 for display consistency - joym->set_axis_value(SIGN(axis_value)); - } - } - - if (k.is_valid()) { - k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway. - // Maintain physical keycode option state - if (physical_key_checkbox->is_pressed()) { - k->set_keycode(Key::NONE); - } else { - k->set_physical_keycode(Key::NONE); - } - } - - Ref<InputEventWithModifiers> mod = received_event; - if (mod.is_valid()) { - mod->set_window_id(0); - } - - // Maintain device selection. - received_event->set_device(_get_current_device()); - - _set_event(received_event); - set_input_as_handled(); -} - -void InputEventConfigurationDialog::_search_term_updated(const String &) { - _update_input_list(); -} - -void InputEventConfigurationDialog::_update_input_list() { - input_list_tree->clear(); - - TreeItem *root = input_list_tree->create_item(); - String search_term = input_list_search->get_text(); - - bool collapse = input_list_search->get_text().is_empty(); - - if (allowed_input_types & INPUT_KEY) { - TreeItem *kb_root = input_list_tree->create_item(root); - kb_root->set_text(0, TTR("Keyboard Keys")); - kb_root->set_icon(0, icon_cache.keyboard); - kb_root->set_collapsed(collapse); - kb_root->set_meta("__type", INPUT_KEY); - - for (int i = 0; i < keycode_get_count(); i++) { - String name = keycode_get_name_by_index(i); - - if (!search_term.is_empty() && name.findn(search_term) == -1) { - continue; - } - - TreeItem *item = input_list_tree->create_item(kb_root); - item->set_text(0, name); - item->set_meta("__keycode", keycode_get_value_by_index(i)); - } - } - - if (allowed_input_types & INPUT_MOUSE_BUTTON) { - TreeItem *mouse_root = input_list_tree->create_item(root); - mouse_root->set_text(0, TTR("Mouse Buttons")); - mouse_root->set_icon(0, icon_cache.mouse); - mouse_root->set_collapsed(collapse); - mouse_root->set_meta("__type", INPUT_MOUSE_BUTTON); - - MouseButton mouse_buttons[9] = { MouseButton::LEFT, MouseButton::RIGHT, MouseButton::MIDDLE, MouseButton::WHEEL_UP, MouseButton::WHEEL_DOWN, MouseButton::WHEEL_LEFT, MouseButton::WHEEL_RIGHT, MouseButton::MB_XBUTTON1, MouseButton::MB_XBUTTON2 }; - for (int i = 0; i < 9; i++) { - Ref<InputEventMouseButton> mb; - mb.instantiate(); - mb->set_button_index(mouse_buttons[i]); - String desc = get_event_text(mb, false); - - if (!search_term.is_empty() && desc.findn(search_term) == -1) { - continue; - } - - TreeItem *item = input_list_tree->create_item(mouse_root); - item->set_text(0, desc); - item->set_meta("__index", mouse_buttons[i]); - } - } - - if (allowed_input_types & INPUT_JOY_BUTTON) { - TreeItem *joyb_root = input_list_tree->create_item(root); - joyb_root->set_text(0, TTR("Joypad Buttons")); - joyb_root->set_icon(0, icon_cache.joypad_button); - joyb_root->set_collapsed(collapse); - joyb_root->set_meta("__type", INPUT_JOY_BUTTON); - - for (int i = 0; i < (int)JoyButton::MAX; i++) { - Ref<InputEventJoypadButton> joyb; - joyb.instantiate(); - joyb->set_button_index((JoyButton)i); - String desc = get_event_text(joyb, false); - - if (!search_term.is_empty() && desc.findn(search_term) == -1) { - continue; - } - - TreeItem *item = input_list_tree->create_item(joyb_root); - item->set_text(0, desc); - item->set_meta("__index", i); - } - } - - if (allowed_input_types & INPUT_JOY_MOTION) { - TreeItem *joya_root = input_list_tree->create_item(root); - joya_root->set_text(0, TTR("Joypad Axes")); - joya_root->set_icon(0, icon_cache.joypad_axis); - joya_root->set_collapsed(collapse); - joya_root->set_meta("__type", INPUT_JOY_MOTION); - - for (int i = 0; i < (int)JoyAxis::MAX * 2; i++) { - int axis = i / 2; - int direction = (i & 1) ? 1 : -1; - Ref<InputEventJoypadMotion> joym; - joym.instantiate(); - joym->set_axis((JoyAxis)axis); - joym->set_axis_value(direction); - String desc = get_event_text(joym, false); - - if (!search_term.is_empty() && desc.findn(search_term) == -1) { - continue; - } - - TreeItem *item = input_list_tree->create_item(joya_root); - item->set_text(0, desc); - item->set_meta("__axis", i >> 1); - item->set_meta("__value", (i & 1) ? 1 : -1); - } - } -} - -void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) { - Ref<InputEventWithModifiers> ie = event; - - // Not event with modifiers - if (ie.is_null()) { - return; - } - - if (p_index == 0) { - ie->set_alt_pressed(p_checked); - } else if (p_index == 1) { - ie->set_shift_pressed(p_checked); - } else if (p_index == 2) { - if (!autoremap_command_or_control_checkbox->is_pressed()) { - ie->set_ctrl_pressed(p_checked); - } - } else if (p_index == 3) { - if (!autoremap_command_or_control_checkbox->is_pressed()) { - ie->set_meta_pressed(p_checked); - } - } - - _set_event(ie); -} - -void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) { - Ref<InputEventWithModifiers> ie = event; - if (ie.is_valid()) { - ie->set_command_or_control_autoremap(p_checked); - _set_event(ie); - } - - if (p_checked) { - mod_checkboxes[MOD_META]->hide(); - mod_checkboxes[MOD_CTRL]->hide(); - } else { - mod_checkboxes[MOD_META]->show(); - mod_checkboxes[MOD_CTRL]->show(); - } -} - -void InputEventConfigurationDialog::_physical_keycode_toggled(bool p_checked) { - Ref<InputEventKey> k = event; - - if (k.is_null()) { - return; - } - - if (p_checked) { - k->set_physical_keycode(k->get_keycode()); - k->set_keycode(Key::NONE); - } else { - k->set_keycode((Key)k->get_physical_keycode()); - k->set_physical_keycode(Key::NONE); - } - - _set_event(k); -} - -void InputEventConfigurationDialog::_input_list_item_selected() { - TreeItem *selected = input_list_tree->get_selected(); - - // Invalid tree selection - type only exists on the "category" items, which are not a valid selection. - if (selected->has_meta("__type")) { - return; - } - - InputEventConfigurationDialog::InputType input_type = (InputEventConfigurationDialog::InputType)(int)selected->get_parent()->get_meta("__type"); - - switch (input_type) { - case InputEventConfigurationDialog::INPUT_KEY: { - Key keycode = (Key)(int)selected->get_meta("__keycode"); - Ref<InputEventKey> k; - k.instantiate(); - - if (physical_key_checkbox->is_pressed()) { - k->set_physical_keycode(keycode); - k->set_keycode(Key::NONE); - } else { - k->set_physical_keycode(Key::NONE); - k->set_keycode(keycode); - } - - // Maintain modifier state from checkboxes - k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); - k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); - if (autoremap_command_or_control_checkbox->is_pressed()) { - k->set_command_or_control_autoremap(true); - } else { - k->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); - k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); - } - - _set_event(k, false); - } break; - case InputEventConfigurationDialog::INPUT_MOUSE_BUTTON: { - MouseButton idx = (MouseButton)(int)selected->get_meta("__index"); - Ref<InputEventMouseButton> mb; - mb.instantiate(); - mb->set_button_index(idx); - // Maintain modifier state from checkboxes - mb->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); - mb->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); - if (autoremap_command_or_control_checkbox->is_pressed()) { - mb->set_command_or_control_autoremap(true); - } else { - mb->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); - mb->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); - } - - // Maintain selected device - mb->set_device(_get_current_device()); - - _set_event(mb, false); - } break; - case InputEventConfigurationDialog::INPUT_JOY_BUTTON: { - JoyButton idx = (JoyButton)(int)selected->get_meta("__index"); - Ref<InputEventJoypadButton> jb = InputEventJoypadButton::create_reference(idx); - - // Maintain selected device - jb->set_device(_get_current_device()); - - _set_event(jb, false); - } break; - case InputEventConfigurationDialog::INPUT_JOY_MOTION: { - JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis"); - int value = selected->get_meta("__value"); - - Ref<InputEventJoypadMotion> jm; - jm.instantiate(); - jm->set_axis(axis); - jm->set_axis_value(value); - - // Maintain selected device - jm->set_device(_get_current_device()); - - _set_event(jm, false); - } break; - } -} - -void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { - // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) - // and option index 1 corresponds to device 0, etc... - event->set_device(p_option_button_index - 1); - event_as_text->set_text(get_event_text(event, true)); -} - -void InputEventConfigurationDialog::_set_current_device(int p_device) { - device_id_option->select(p_device + 1); -} - -int InputEventConfigurationDialog::_get_current_device() const { - return device_id_option->get_selected() - 1; -} - -String InputEventConfigurationDialog::_get_device_string(int p_device) const { - if (p_device == InputMap::ALL_DEVICES) { - return TTR("All Devices"); - } - return TTR("Device") + " " + itos(p_device); -} - -void InputEventConfigurationDialog::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - input_list_search->set_right_icon(input_list_search->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - - physical_key_checkbox->set_icon(get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); - - icon_cache.keyboard = get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")); - icon_cache.mouse = get_theme_icon(SNAME("Mouse"), SNAME("EditorIcons")); - icon_cache.joypad_button = get_theme_icon(SNAME("JoyButton"), SNAME("EditorIcons")); - icon_cache.joypad_axis = get_theme_icon(SNAME("JoyAxis"), SNAME("EditorIcons")); - - _update_input_list(); - } break; - } -} - -void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) { - if (p_event.is_valid()) { - _set_event(p_event); - } else { - // Clear Event - _set_event(p_event); - - // Clear Checkbox Values - for (int i = 0; i < MOD_MAX; i++) { - mod_checkboxes[i]->set_pressed(false); - } - - // Enable the Physical Key checkbox by default to encourage its use. - // Physical Key should be used for most game inputs as it allows keys to work - // on non-QWERTY layouts out of the box. - // This is especially important for WASD movement layouts. - physical_key_checkbox->set_pressed(true); - - autoremap_command_or_control_checkbox->set_pressed(false); - _set_current_device(0); - - // Switch to "Listen" tab - tab_container->set_current_tab(0); - - // Select "All Devices" by default. - device_id_option->select(0); - } - - popup_centered(Size2(0, 400) * EDSCALE); -} - -Ref<InputEvent> InputEventConfigurationDialog::get_event() const { - return event; -} - -void InputEventConfigurationDialog::set_allowed_input_types(int p_type_masks) { - allowed_input_types = p_type_masks; -} - -InputEventConfigurationDialog::InputEventConfigurationDialog() { - allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION | INPUT_MOUSE_BUTTON; - - set_title(TTR("Event Configuration")); - set_min_size(Size2i(550 * EDSCALE, 0)); // Min width - - VBoxContainer *main_vbox = memnew(VBoxContainer); - add_child(main_vbox); - - tab_container = memnew(TabContainer); - tab_container->set_use_hidden_tabs_for_min_size(true); - tab_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); - tab_container->set_theme_type_variation("TabContainerOdd"); - tab_container->connect("tab_selected", callable_mp(this, &InputEventConfigurationDialog::_tab_selected)); - main_vbox->add_child(tab_container); - - // Listen to input tab - VBoxContainer *vb = memnew(VBoxContainer); - vb->set_name(TTR("Listen for Input")); - event_as_text = memnew(Label); - event_as_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - vb->add_child(event_as_text); - // Mouse button detection rect (Mouse button event outside this rect will be ignored) - mouse_detection_rect = memnew(Panel); - mouse_detection_rect->set_v_size_flags(Control::SIZE_EXPAND_FILL); - vb->add_child(mouse_detection_rect); - tab_container->add_child(vb); - - // List of all input options to manually select from. - - VBoxContainer *manual_vbox = memnew(VBoxContainer); - manual_vbox->set_name(TTR("Manual Selection")); - manual_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); - tab_container->add_child(manual_vbox); - - input_list_search = memnew(LineEdit); - input_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); - input_list_search->set_placeholder(TTR("Filter Inputs")); - input_list_search->set_clear_button_enabled(true); - input_list_search->connect("text_changed", callable_mp(this, &InputEventConfigurationDialog::_search_term_updated)); - manual_vbox->add_child(input_list_search); - - input_list_tree = memnew(Tree); - input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree - input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); - input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); - manual_vbox->add_child(input_list_tree); - - input_list_tree->set_hide_root(true); - input_list_tree->set_columns(1); - - _update_input_list(); - - // Additional Options - additional_options_container = memnew(VBoxContainer); - additional_options_container->hide(); - - Label *opts_label = memnew(Label); - opts_label->set_theme_type_variation("HeaderSmall"); - opts_label->set_text(TTR("Additional Options")); - additional_options_container->add_child(opts_label); - - // Device Selection - device_container = memnew(HBoxContainer); - device_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); - - Label *device_label = memnew(Label); - device_label->set_theme_type_variation("HeaderSmall"); - device_label->set_text(TTR("Device:")); - device_container->add_child(device_label); - - device_id_option = memnew(OptionButton); - device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); - for (int i = -1; i < 8; i++) { - device_id_option->add_item(_get_device_string(i)); - } - device_id_option->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); - _set_current_device(InputMap::ALL_DEVICES); - device_container->add_child(device_id_option); - - device_container->hide(); - additional_options_container->add_child(device_container); - - // Modifier Selection - mod_container = memnew(HBoxContainer); - for (int i = 0; i < MOD_MAX; i++) { - String name = mods[i]; - mod_checkboxes[i] = memnew(CheckBox); - mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); - mod_checkboxes[i]->set_text(name); - mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i])); - mod_container->add_child(mod_checkboxes[i]); - } - - mod_container->add_child(memnew(VSeparator)); - - autoremap_command_or_control_checkbox = memnew(CheckBox); - autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); - autoremap_command_or_control_checkbox->set_pressed(false); - autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)")); - autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform.")); - mod_container->add_child(autoremap_command_or_control_checkbox); - - mod_container->hide(); - additional_options_container->add_child(mod_container); - - // Physical Key Checkbox - - physical_key_checkbox = memnew(CheckBox); - physical_key_checkbox->set_text(TTR("Use Physical Keycode")); - physical_key_checkbox->set_tooltip_text(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications.")); - physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled)); - physical_key_checkbox->hide(); - additional_options_container->add_child(physical_key_checkbox); - - main_vbox->add_child(additional_options_container); - - // Default to first tab - tab_container->set_current_tab(0); -} - -///////////////////////////////////////// +#include "editor/event_listener_line_edit.h" +#include "editor/input_event_configuration_dialog.h" +#include "scene/gui/check_button.h" +#include "scene/gui/tree.h" static bool _is_action_name_valid(const String &p_name) { const char32_t *cstr = p_name.get_data(); @@ -944,6 +225,12 @@ void ActionMapEditor::_search_term_updated(const String &) { update_action_list(); } +void ActionMapEditor::_search_by_event(const Ref<InputEvent> &p_event) { + if (p_event.is_null() || (p_event->is_pressed() && !p_event->is_echo())) { + update_action_list(); + } +} + Variant ActionMapEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { TreeItem *selected = action_tree->get_selected(); if (!selected) { @@ -1084,6 +371,22 @@ InputEventConfigurationDialog *ActionMapEditor::get_configuration_dialog() { return event_config_dialog; } +bool ActionMapEditor::_should_display_action(const String &p_name, const Array &p_events) const { + const Ref<InputEvent> search_ev = action_list_search_by_event->get_event(); + bool event_match = true; + if (search_ev.is_valid()) { + event_match = false; + for (int i = 0; i < p_events.size(); ++i) { + const Ref<InputEvent> ev = p_events[i]; + if (ev.is_valid() && ev->is_match(search_ev, true)) { + event_match = true; + } + } + } + + return event_match && action_list_search->get_text().is_subsequence_ofn(p_name); +} + void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_infos) { if (!p_action_infos.is_empty()) { actions_cache = p_action_infos; @@ -1101,8 +404,8 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info uneditable_count++; } - String search_term = action_list_search->get_text(); - if (!search_term.is_empty() && action_info.name.findn(search_term) == -1) { + const Array events = action_info.action["events"]; + if (!_should_display_action(action_info.name, events)) { continue; } @@ -1110,7 +413,6 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info continue; } - const Array events = action_info.action["events"]; const Variant deadzone = action_info.action["deadzone"]; // Update Tree... @@ -1206,16 +508,22 @@ ActionMapEditor::ActionMapEditor() { action_list_search = memnew(LineEdit); action_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); - action_list_search->set_placeholder(TTR("Filter Actions")); + action_list_search->set_placeholder(TTR("Filter by name...")); action_list_search->set_clear_button_enabled(true); action_list_search->connect("text_changed", callable_mp(this, &ActionMapEditor::_search_term_updated)); top_hbox->add_child(action_list_search); - show_builtin_actions_checkbutton = memnew(CheckButton); - show_builtin_actions_checkbutton->set_pressed(false); - show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions")); - show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions)); - top_hbox->add_child(show_builtin_actions_checkbutton); + action_list_search_by_event = memnew(EventListenerLineEdit); + action_list_search_by_event->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_list_search_by_event->set_stretch_ratio(0.75); + action_list_search_by_event->connect("event_changed", callable_mp(this, &ActionMapEditor::_search_by_event)); + top_hbox->add_child(action_list_search_by_event); + + Button *clear_all_search = memnew(Button); + clear_all_search->set_text(TTR("Clear All")); + clear_all_search->connect("pressed", callable_mp(action_list_search_by_event, &EventListenerLineEdit::clear_event)); + clear_all_search->connect("pressed", callable_mp(action_list_search, &LineEdit::clear)); + top_hbox->add_child(clear_all_search); // Adding Action line edit + button add_hbox = memnew(HBoxContainer); @@ -1236,6 +544,12 @@ ActionMapEditor::ActionMapEditor() { // Disable the button and set its tooltip. _add_edit_text_changed(add_edit->get_text()); + show_builtin_actions_checkbutton = memnew(CheckButton); + show_builtin_actions_checkbutton->set_pressed(false); + show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions")); + show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions)); + add_hbox->add_child(show_builtin_actions_checkbutton); + main_vbox->add_child(add_hbox); // Action Editor Tree diff --git a/editor/action_map_editor.h b/editor/action_map_editor.h index 36d21fe258..d56ee6f9eb 100644 --- a/editor/action_map_editor.h +++ b/editor/action_map_editor.h @@ -31,109 +31,16 @@ #ifndef ACTION_MAP_EDITOR_H #define ACTION_MAP_EDITOR_H -#include "scene/gui/check_box.h" -#include "scene/gui/check_button.h" -#include "scene/gui/color_rect.h" -#include "scene/gui/dialogs.h" -#include "scene/gui/label.h" -#include "scene/gui/option_button.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/tree.h" - -// Confirmation Dialog used when configuring an input event. -// Separate from ActionMapEditor for code cleanliness and separation of responsibilities. -class InputEventConfigurationDialog : public ConfirmationDialog { - GDCLASS(InputEventConfigurationDialog, ConfirmationDialog); +#include "scene/gui/control.h" -public: - enum InputType { - INPUT_KEY = 1, - INPUT_MOUSE_BUTTON = 2, - INPUT_JOY_BUTTON = 4, - INPUT_JOY_MOTION = 8 - }; - -private: - struct IconCache { - Ref<Texture2D> keyboard; - Ref<Texture2D> mouse; - Ref<Texture2D> joypad_button; - Ref<Texture2D> joypad_axis; - } icon_cache; - - Ref<InputEvent> event = Ref<InputEvent>(); - - TabContainer *tab_container = nullptr; - - // Listening for input - Label *event_as_text = nullptr; - Panel *mouse_detection_rect = nullptr; - - // List of All Key/Mouse/Joypad input options. - int allowed_input_types; - Tree *input_list_tree = nullptr; - LineEdit *input_list_search = nullptr; - - // Additional Options, shown depending on event selected - VBoxContainer *additional_options_container = nullptr; - - HBoxContainer *device_container = nullptr; - OptionButton *device_id_option = nullptr; - - HBoxContainer *mod_container = nullptr; // Contains the subcontainer and the store command checkbox. - - enum ModCheckbox { - MOD_ALT, - MOD_SHIFT, - MOD_CTRL, - MOD_META, - MOD_MAX - }; -#if defined(MACOS_ENABLED) - String mods[MOD_MAX] = { "Option", "Shift", "Ctrl", "Command" }; -#elif defined(WINDOWS_ENABLED) - String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Windows" }; -#else - String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Meta" }; -#endif - String mods_tip[MOD_MAX] = { "Alt or Option key", "Shift key", "Control key", "Meta/Windows or Command key" }; - - CheckBox *mod_checkboxes[MOD_MAX]; - CheckBox *autoremap_command_or_control_checkbox = nullptr; - - CheckBox *physical_key_checkbox = nullptr; - - void _set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection = true); - - void _tab_selected(int p_tab); - void _listen_window_input(const Ref<InputEvent> &p_event); - - void _search_term_updated(const String &p_term); - void _update_input_list(); - void _input_list_item_selected(); - - void _mod_toggled(bool p_checked, int p_index); - void _autoremap_command_or_control_toggled(bool p_checked); - void _physical_keycode_toggled(bool p_checked); - - void _device_selection_changed(int p_option_button_index); - void _set_current_device(int p_device); - int _get_current_device() const; - String _get_device_string(int p_device) const; - -protected: - void _notification(int p_what); - -public: - // Pass an existing event to configure it. Alternatively, pass no event to start with a blank configuration. - void popup_and_configure(const Ref<InputEvent> &p_event = Ref<InputEvent>()); - Ref<InputEvent> get_event() const; - String get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) const; - - void set_allowed_input_types(int p_type_masks); - - InputEventConfigurationDialog(); -}; +class Button; +class HBoxContainer; +class EventListenerLineEdit; +class LineEdit; +class CheckButton; +class AcceptDialog; +class InputEventConfigurationDialog; +class Tree; class ActionMapEditor : public Control { GDCLASS(ActionMapEditor, Control); @@ -174,6 +81,7 @@ private: bool show_builtin_actions = false; CheckButton *show_builtin_actions_checkbutton = nullptr; LineEdit *action_list_search = nullptr; + EventListenerLineEdit *action_list_search_by_event = nullptr; HBoxContainer *add_hbox = nullptr; LineEdit *add_edit = nullptr; @@ -191,6 +99,8 @@ private: void _tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _tree_item_activated(); void _search_term_updated(const String &p_search_term); + void _search_by_event(const Ref<InputEvent> &p_event); + bool _should_display_action(const String &p_name, const Array &p_events) const; Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 11a6912aa5..fe9831d0ef 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -55,6 +55,7 @@ void GotoLineDialog::ok_pressed() { if (get_line() < 1 || get_line() > text_editor->get_line_count()) { return; } + text_editor->remove_secondary_carets(); text_editor->unfold_line(get_line() - 1); text_editor->set_caret_line(get_line() - 1); hide(); @@ -149,7 +150,7 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) text_editor->unfold_line(pos.y); text_editor->set_caret_line(pos.y, false); text_editor->set_caret_column(pos.x + text.length(), false); - text_editor->center_viewport_to_caret(); + text_editor->center_viewport_to_caret(0); text_editor->select(pos.y, pos.x, pos.y, pos.x + text.length()); line_col_changed_for_result = true; @@ -176,11 +177,11 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) } void FindReplaceBar::_replace() { - bool selection_enabled = text_editor->has_selection(); + bool selection_enabled = text_editor->has_selection(0); Point2i selection_begin, selection_end; if (selection_enabled) { - selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_column()); - selection_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column()); + selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0)); + selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0)); } String replace_text = get_replace_text(); @@ -188,25 +189,25 @@ void FindReplaceBar::_replace() { text_editor->begin_complex_operation(); if (selection_enabled && is_selection_only()) { // To restrict search_current() to selected region - text_editor->set_caret_line(selection_begin.width); - text_editor->set_caret_column(selection_begin.height); + text_editor->set_caret_line(selection_begin.width, false, true, 0, 0); + text_editor->set_caret_column(selection_begin.height, true, 0); } if (search_current()) { text_editor->unfold_line(result_line); - text_editor->select(result_line, result_col, result_line, result_col + search_text_len); + text_editor->select(result_line, result_col, result_line, result_col + search_text_len, 0); if (selection_enabled && is_selection_only()) { Point2i match_from(result_line, result_col); Point2i match_to(result_line, result_col + search_text_len); if (!(match_from < selection_begin || match_to > selection_end)) { - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); if (match_to.x == selection_end.x) { // Adjust selection bounds if necessary selection_end.y += replace_text.length() - search_text_len; } } } else { - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); } } text_editor->end_complex_operation(); @@ -216,29 +217,29 @@ void FindReplaceBar::_replace() { if (selection_enabled && is_selection_only()) { // Reselect in order to keep 'Replace' restricted to selection - text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y); + text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y, 0); } else { - text_editor->deselect(); + text_editor->deselect(0); } } void FindReplaceBar::_replace_all() { text_editor->disconnect("text_changed", callable_mp(this, &FindReplaceBar::_editor_text_changed)); // Line as x so it gets priority in comparison, column as y. - Point2i orig_cursor(text_editor->get_caret_line(), text_editor->get_caret_column()); + Point2i orig_cursor(text_editor->get_caret_line(0), text_editor->get_caret_column(0)); Point2i prev_match = Point2(-1, -1); - bool selection_enabled = text_editor->has_selection(); + bool selection_enabled = text_editor->has_selection(0); Point2i selection_begin, selection_end; if (selection_enabled) { - selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_column()); - selection_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column()); + selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0)); + selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0)); } int vsval = text_editor->get_v_scroll(); - text_editor->set_caret_line(0); - text_editor->set_caret_column(0); + text_editor->set_caret_line(0, false, true, 0, 0); + text_editor->set_caret_column(0, true, 0); String replace_text = get_replace_text(); int search_text_len = get_search_text().length(); @@ -250,8 +251,8 @@ void FindReplaceBar::_replace_all() { text_editor->begin_complex_operation(); if (selection_enabled && is_selection_only()) { - text_editor->set_caret_line(selection_begin.width); - text_editor->set_caret_column(selection_begin.height); + text_editor->set_caret_line(selection_begin.width, false, true, 0, 0); + text_editor->set_caret_column(selection_begin.height, true, 0); } if (search_current()) { do { @@ -266,7 +267,7 @@ void FindReplaceBar::_replace_all() { prev_match = Point2i(result_line, result_col + replace_text.length()); text_editor->unfold_line(result_line); - text_editor->select(result_line, result_col, result_line, match_to.y); + text_editor->select(result_line, result_col, result_line, match_to.y, 0); if (selection_enabled && is_selection_only()) { if (match_from < selection_begin || match_to > selection_end) { @@ -274,14 +275,14 @@ void FindReplaceBar::_replace_all() { } // Replace but adjust selection bounds. - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); if (match_to.x == selection_end.x) { selection_end.y += replace_text.length() - search_text_len; } } else { // Just replace. - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); } rc++; @@ -293,14 +294,14 @@ void FindReplaceBar::_replace_all() { replace_all_mode = false; // Restore editor state (selection, cursor, scroll). - text_editor->set_caret_line(orig_cursor.x); - text_editor->set_caret_column(orig_cursor.y); + text_editor->set_caret_line(orig_cursor.x, false, true, 0, 0); + text_editor->set_caret_column(orig_cursor.y, true, 0); if (selection_enabled && is_selection_only()) { // Reselect. - text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y); + text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y, 0); } else { - text_editor->deselect(); + text_editor->deselect(0); } text_editor->set_v_scroll(vsval); @@ -314,10 +315,10 @@ void FindReplaceBar::_replace_all() { } void FindReplaceBar::_get_search_from(int &r_line, int &r_col) { - r_line = text_editor->get_caret_line(); - r_col = text_editor->get_caret_column(); + r_line = text_editor->get_caret_line(0); + r_col = text_editor->get_caret_column(0); - if (text_editor->has_selection() && is_selection_only()) { + if (text_editor->has_selection(0) && is_selection_only()) { return; } @@ -434,7 +435,7 @@ bool FindReplaceBar::search_prev() { int line, col; _get_search_from(line, col); - if (text_editor->has_selection()) { + if (text_editor->has_selection(0)) { col--; // Skip currently selected word. } @@ -512,8 +513,8 @@ void FindReplaceBar::_show_search(bool p_focus_replace, bool p_show_only) { search_text->call_deferred(SNAME("grab_focus")); } - if (text_editor->has_selection() && !selection_only->is_pressed()) { - search_text->set_text(text_editor->get_selected_text()); + if (text_editor->has_selection(0) && !selection_only->is_pressed()) { + search_text->set_text(text_editor->get_selected_text(0)); } if (!get_search_text().is_empty()) { @@ -548,9 +549,9 @@ void FindReplaceBar::popup_replace() { hbc_option_replace->show(); } - selection_only->set_pressed((text_editor->has_selection() && text_editor->get_selection_from_line() < text_editor->get_selection_to_line())); + selection_only->set_pressed((text_editor->has_selection(0) && text_editor->get_selection_from_line(0) < text_editor->get_selection_to_line(0))); - _show_search(is_visible() || text_editor->has_selection()); + _show_search(is_visible() || text_editor->has_selection(0)); } void FindReplaceBar::_search_options_changed(bool p_pressed) { @@ -587,7 +588,7 @@ void FindReplaceBar::_search_text_submitted(const String &p_text) { } void FindReplaceBar::_replace_text_submitted(const String &p_text) { - if (selection_only->is_pressed() && text_editor->has_selection()) { + if (selection_only->is_pressed() && text_editor->has_selection(0)) { _replace_all(); _hide_bar(); } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { @@ -1091,6 +1092,7 @@ void CodeTextEditor::trim_trailing_whitespace() { } if (trimed_whitespace) { + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1122,8 +1124,11 @@ void CodeTextEditor::convert_indent_to_spaces() { indent += " "; } - int cursor_line = text_editor->get_caret_line(); - int cursor_column = text_editor->get_caret_column(); + Vector<int> cursor_columns; + cursor_columns.resize(text_editor->get_caret_count()); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + cursor_columns.write[c] = text_editor->get_caret_column(c); + } bool changed_indentation = false; for (int i = 0; i < text_editor->get_line_count(); i++) { @@ -1140,8 +1145,10 @@ void CodeTextEditor::convert_indent_to_spaces() { text_editor->begin_complex_operation(); changed_indentation = true; } - if (cursor_line == i && cursor_column > j) { - cursor_column += indent_size - 1; + for (int c = 0; c < text_editor->get_caret_count(); c++) { + if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) { + cursor_columns.write[c] += indent_size - 1; + } } line = line.left(j) + indent + line.substr(j + 1); } @@ -1152,7 +1159,10 @@ void CodeTextEditor::convert_indent_to_spaces() { } } if (changed_indentation) { - text_editor->set_caret_column(cursor_column); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + text_editor->set_caret_column(cursor_columns[c], c == 0, c); + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1162,8 +1172,11 @@ void CodeTextEditor::convert_indent_to_tabs() { int indent_size = EditorSettings::get_singleton()->get("text_editor/behavior/indent/size"); indent_size -= 1; - int cursor_line = text_editor->get_caret_line(); - int cursor_column = text_editor->get_caret_column(); + Vector<int> cursor_columns; + cursor_columns.resize(text_editor->get_caret_count()); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + cursor_columns.write[c] = text_editor->get_caret_column(c); + } bool changed_indentation = false; for (int i = 0; i < text_editor->get_line_count(); i++) { @@ -1184,8 +1197,10 @@ void CodeTextEditor::convert_indent_to_tabs() { text_editor->begin_complex_operation(); changed_indentation = true; } - if (cursor_line == i && cursor_column > j) { - cursor_column -= indent_size; + for (int c = 0; c < text_editor->get_caret_count(); c++) { + if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) { + cursor_columns.write[c] -= indent_size; + } } line = line.left(j - indent_size) + "\t" + line.substr(j + 1); j = 0; @@ -1201,7 +1216,10 @@ void CodeTextEditor::convert_indent_to_tabs() { } } if (changed_indentation) { - text_editor->set_caret_column(cursor_column); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + text_editor->set_caret_column(cursor_columns[c], c == 0, c); + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1211,59 +1229,128 @@ void CodeTextEditor::convert_case(CaseStyle p_case) { if (!text_editor->has_selection()) { return; } - text_editor->begin_complex_operation(); - int begin = text_editor->get_selection_from_line(); - int end = text_editor->get_selection_to_line(); - int begin_col = text_editor->get_selection_from_column(); - int end_col = text_editor->get_selection_to_column(); - - for (int i = begin; i <= end; i++) { - int len = text_editor->get_line(i).length(); - if (i == end) { - len = end_col; - } - if (i == begin) { - len -= begin_col; + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + if (!text_editor->has_selection(c)) { + continue; } - String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); - switch (p_case) { - case UPPER: { - new_line = new_line.to_upper(); - } break; - case LOWER: { - new_line = new_line.to_lower(); - } break; - case CAPITALIZE: { - new_line = new_line.capitalize(); - } break; - } + int begin = text_editor->get_selection_from_line(c); + int end = text_editor->get_selection_to_line(c); + int begin_col = text_editor->get_selection_from_column(c); + int end_col = text_editor->get_selection_to_column(c); - if (i == begin) { - new_line = text_editor->get_line(i).left(begin_col) + new_line; - } - if (i == end) { - new_line = new_line + text_editor->get_line(i).substr(end_col); + for (int i = begin; i <= end; i++) { + int len = text_editor->get_line(i).length(); + if (i == end) { + len = end_col; + } + if (i == begin) { + len -= begin_col; + } + String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); + + switch (p_case) { + case UPPER: { + new_line = new_line.to_upper(); + } break; + case LOWER: { + new_line = new_line.to_lower(); + } break; + case CAPITALIZE: { + new_line = new_line.capitalize(); + } break; + } + + if (i == begin) { + new_line = text_editor->get_line(i).left(begin_col) + new_line; + } + if (i == end) { + new_line = new_line + text_editor->get_line(i).substr(end_col); + } + text_editor->set_line(i, new_line); } - text_editor->set_line(i, new_line); } text_editor->end_complex_operation(); } void CodeTextEditor::move_lines_up() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int from_line = text_editor->get_selection_from_line(); - int from_col = text_editor->get_selection_from_column(); - int to_line = text_editor->get_selection_to_line(); - int to_column = text_editor->get_selection_to_column(); - int cursor_line = text_editor->get_caret_line(); - for (int i = from_line; i <= to_line; i++) { - int line_id = i; - int next_id = i - 1; + Vector<int> carets_to_remove; + + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; + } + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int from_line = text_editor->get_selection_from_line(c); + int from_col = text_editor->get_selection_from_column(c); + int to_line = text_editor->get_selection_to_line(c); + int to_column = text_editor->get_selection_to_column(c); + int cursor_line = text_editor->get_caret_line(c); + + for (int j = from_line; j <= to_line; j++) { + int line_id = j; + int next_id = j - 1; + + if (line_id == 0 || next_id < 0) { + return; + } + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); + } + int from_line_up = from_line > 0 ? from_line - 1 : from_line; + int to_line_up = to_line > 0 ? to_line - 1 : to_line; + int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line; + text_editor->select(from_line_up, from_col, to_line_up, to_column, c); + text_editor->set_caret_line(cursor_line_up, c == 0, true, 0, c); + } else { + int line_id = text_editor->get_caret_line(c); + int next_id = line_id - 1; if (line_id == 0 || next_id < 0) { return; @@ -1273,238 +1360,336 @@ void CodeTextEditor::move_lines_up() { text_editor->unfold_line(next_id); text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); - } - int from_line_up = from_line > 0 ? from_line - 1 : from_line; - int to_line_up = to_line > 0 ? to_line - 1 : to_line; - int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line; - text_editor->select(from_line_up, from_col, to_line_up, to_column); - text_editor->set_caret_line(cursor_line_up); - } else { - int line_id = text_editor->get_caret_line(); - int next_id = line_id - 1; - - if (line_id == 0 || next_id < 0) { - return; + text_editor->set_caret_line(next_id, c == 0, true, 0, c); } - - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); - - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); } text_editor->end_complex_operation(); + text_editor->merge_overlapping_carets(); text_editor->queue_redraw(); } void CodeTextEditor::move_lines_down() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int from_line = text_editor->get_selection_from_line(); - int from_col = text_editor->get_selection_from_column(); - int to_line = text_editor->get_selection_to_line(); - int to_column = text_editor->get_selection_to_column(); - int cursor_line = text_editor->get_caret_line(); - for (int i = to_line; i >= from_line; i--) { - int line_id = i; - int next_id = i + 1; + Vector<int> carets_to_remove; + + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; + } + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int from_line = text_editor->get_selection_from_line(c); + int from_col = text_editor->get_selection_from_column(c); + int to_line = text_editor->get_selection_to_line(c); + int to_column = text_editor->get_selection_to_column(c); + int cursor_line = text_editor->get_caret_line(c); + + for (int l = to_line; l >= from_line; l--) { + int line_id = l; + int next_id = l + 1; + + if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { + continue; + } + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); + } + int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; + int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; + int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line; + text_editor->select(from_line_down, from_col, to_line_down, to_column, c); + text_editor->set_caret_line(cursor_line_down, c == 0, true, 0, c); + } else { + int line_id = text_editor->get_caret_line(c); + int next_id = line_id + 1; if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - return; + continue; } text_editor->unfold_line(line_id); text_editor->unfold_line(next_id); text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); - } - int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; - int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; - int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line; - text_editor->select(from_line_down, from_col, to_line_down, to_column); - text_editor->set_caret_line(cursor_line_down); - } else { - int line_id = text_editor->get_caret_line(); - int next_id = line_id + 1; - - if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - return; + text_editor->set_caret_line(next_id, c == 0, true, 0, c); } + } - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); - - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + text_editor->remove_caret(carets_to_remove[i]); } + + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } -void CodeTextEditor::_delete_line(int p_line) { +void CodeTextEditor::_delete_line(int p_line, int p_caret) { // this is currently intended to be called within delete_lines() // so `begin_complex_operation` is omitted here text_editor->set_line(p_line, ""); if (p_line == 0 && text_editor->get_line_count() > 1) { - text_editor->set_caret_line(1); - text_editor->set_caret_column(0); + text_editor->set_caret_line(1, p_caret == 0, true, 0, p_caret); + text_editor->set_caret_column(0, p_caret == 0, p_caret); } - text_editor->backspace(); + text_editor->backspace(p_caret); if (p_line < text_editor->get_line_count()) { text_editor->unfold_line(p_line); } - text_editor->set_caret_line(p_line); + text_editor->set_caret_line(p_line, p_caret == 0, true, 0, p_caret); } void CodeTextEditor::delete_lines() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int to_line = text_editor->get_selection_to_line(); - int from_line = text_editor->get_selection_from_line(); - int count = Math::abs(to_line - from_line) + 1; - text_editor->set_caret_line(from_line, false); - text_editor->deselect(); - for (int i = 0; i < count; i++) { - _delete_line(from_line); + Vector<int> carets_to_remove; + + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; } - } else { - _delete_line(text_editor->get_caret_line()); + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int to_line = text_editor->get_selection_to_line(c); + int from_line = text_editor->get_selection_from_line(c); + int count = Math::abs(to_line - from_line) + 1; + + text_editor->set_caret_line(from_line, false, true, 0, c); + text_editor->deselect(c); + for (int j = 0; j < count; j++) { + _delete_line(from_line, c); + } + } else { + _delete_line(text_editor->get_caret_line(c), c); + } + } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + text_editor->remove_caret(carets_to_remove[i]); } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); } void CodeTextEditor::duplicate_selection() { - const int cursor_column = text_editor->get_caret_column(); - int from_line = text_editor->get_caret_line(); - int to_line = text_editor->get_caret_line(); - int from_column = 0; - int to_column = 0; - int cursor_new_line = to_line + 1; - int cursor_new_column = text_editor->get_caret_column(); - String new_text = "\n" + text_editor->get_line(from_line); - bool selection_active = false; - - text_editor->set_caret_column(text_editor->get_line(from_line).length()); - if (text_editor->has_selection()) { - from_column = text_editor->get_selection_from_column(); - to_column = text_editor->get_selection_to_column(); - - from_line = text_editor->get_selection_from_line(); - to_line = text_editor->get_selection_to_line(); - cursor_new_line = to_line + text_editor->get_caret_line() - from_line; - cursor_new_column = to_column == cursor_column ? 2 * to_column - from_column : to_column; - new_text = text_editor->get_selected_text(); - selection_active = true; - - text_editor->set_caret_line(to_line); - text_editor->set_caret_column(to_column); - } - text_editor->begin_complex_operation(); - for (int i = from_line; i <= to_line; i++) { - text_editor->unfold_line(i); - } - text_editor->deselect(); - text_editor->insert_text_at_caret(new_text); - text_editor->set_caret_line(cursor_new_line); - text_editor->set_caret_column(cursor_new_column); - if (selection_active) { - text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column); - } + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + const int cursor_column = text_editor->get_caret_column(c); + int from_line = text_editor->get_caret_line(c); + int to_line = text_editor->get_caret_line(c); + int from_column = 0; + int to_column = 0; + int cursor_new_line = to_line + 1; + int cursor_new_column = text_editor->get_caret_column(c); + String new_text = "\n" + text_editor->get_line(from_line); + bool selection_active = false; + + text_editor->set_caret_column(text_editor->get_line(from_line).length(), c == 0, c); + if (text_editor->has_selection(c)) { + from_column = text_editor->get_selection_from_column(c); + to_column = text_editor->get_selection_to_column(c); + + from_line = text_editor->get_selection_from_line(c); + to_line = text_editor->get_selection_to_line(c); + cursor_new_line = to_line + text_editor->get_caret_line(c) - from_line; + cursor_new_column = to_column == cursor_column ? 2 * to_column - from_column : to_column; + new_text = text_editor->get_selected_text(c); + selection_active = true; + + text_editor->set_caret_line(to_line, c == 0, true, 0, c); + text_editor->set_caret_column(to_column, c == 0, c); + } + for (int i = from_line; i <= to_line; i++) { + text_editor->unfold_line(i); + } + text_editor->deselect(c); + text_editor->insert_text_at_caret(new_text, c); + text_editor->set_caret_line(cursor_new_line, c == 0, true, 0, c); + text_editor->set_caret_column(cursor_new_column, c == 0, c); + if (selection_active) { + text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column, c); + } + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } void CodeTextEditor::toggle_inline_comment(const String &delimiter) { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int begin = text_editor->get_selection_from_line(); - int end = text_editor->get_selection_to_line(); - // End of selection ends on the first column of the last line, ignore it. - if (text_editor->get_selection_to_column() == 0) { - end -= 1; - } + Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + if (text_editor->has_selection(c)) { + int begin = text_editor->get_selection_from_line(c); + int end = text_editor->get_selection_to_line(c); - int col_to = text_editor->get_selection_to_column(); - int cursor_pos = text_editor->get_caret_column(); + // End of selection ends on the first column of the last line, ignore it. + if (text_editor->get_selection_to_column(c) == 0) { + end -= 1; + } - // Check if all lines in the selected block are commented. - bool is_commented = true; - for (int i = begin; i <= end; i++) { - if (!text_editor->get_line(i).begins_with(delimiter)) { - is_commented = false; - break; + int col_to = text_editor->get_selection_to_column(c); + int cursor_pos = text_editor->get_caret_column(c); + + // Check if all lines in the selected block are commented. + bool is_commented = true; + for (int i = begin; i <= end; i++) { + if (!text_editor->get_line(i).begins_with(delimiter)) { + is_commented = false; + break; + } } - } - for (int i = begin; i <= end; i++) { - String line_text = text_editor->get_line(i); + for (int i = begin; i <= end; i++) { + String line_text = text_editor->get_line(i); - if (line_text.strip_edges().is_empty()) { - line_text = delimiter; - } else { - if (is_commented) { - line_text = line_text.substr(delimiter.length(), line_text.length()); + if (line_text.strip_edges().is_empty()) { + line_text = delimiter; } else { - line_text = delimiter + line_text; + if (is_commented) { + line_text = line_text.substr(delimiter.length(), line_text.length()); + } else { + line_text = delimiter + line_text; + } } + text_editor->set_line(i, line_text); } - text_editor->set_line(i, line_text); - } - // Adjust selection & cursor position. - int offset = (is_commented ? -1 : 1) * delimiter.length(); - int col_from = text_editor->get_selection_from_column() > 0 ? text_editor->get_selection_from_column() + offset : 0; + // Adjust selection & cursor position. + int offset = (is_commented ? -1 : 1) * delimiter.length(); + int col_from = text_editor->get_selection_from_column(c) > 0 ? text_editor->get_selection_from_column(c) + offset : 0; - if (is_commented && text_editor->get_caret_column() == text_editor->get_line(text_editor->get_caret_line()).length() + 1) { - cursor_pos += 1; - } + if (is_commented && text_editor->get_caret_column(c) == text_editor->get_line(text_editor->get_caret_line(c)).length() + 1) { + cursor_pos += 1; + } - if (text_editor->get_selection_to_column() != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line()).length() + 1) { - col_to += offset; - } + if (text_editor->get_selection_to_column(c) != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line(c)).length() + 1) { + col_to += offset; + } - if (text_editor->get_caret_column() != 0) { - cursor_pos += offset; - } + if (text_editor->get_caret_column(c) != 0) { + cursor_pos += offset; + } - text_editor->select(begin, col_from, text_editor->get_selection_to_line(), col_to); - text_editor->set_caret_column(cursor_pos); + text_editor->select(begin, col_from, text_editor->get_selection_to_line(c), col_to, c); + text_editor->set_caret_column(cursor_pos, c == 0, c); - } else { - int begin = text_editor->get_caret_line(); - String line_text = text_editor->get_line(begin); - int delimiter_length = delimiter.length(); - - int col = text_editor->get_caret_column(); - if (line_text.begins_with(delimiter)) { - line_text = line_text.substr(delimiter_length, line_text.length()); - col -= delimiter_length; } else { - line_text = delimiter + line_text; - col += delimiter_length; - } + int begin = text_editor->get_caret_line(c); + String line_text = text_editor->get_line(begin); + int delimiter_length = delimiter.length(); + + int col = text_editor->get_caret_column(c); + if (line_text.begins_with(delimiter)) { + line_text = line_text.substr(delimiter_length, line_text.length()); + col -= delimiter_length; + } else { + line_text = delimiter + line_text; + col += delimiter_length; + } - text_editor->set_line(begin, line_text); - text_editor->set_caret_column(col); + text_editor->set_line(begin, line_text); + text_editor->set_caret_column(col, c == 0, c); + } } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } void CodeTextEditor::goto_line(int p_line) { + text_editor->remove_secondary_carets(); text_editor->deselect(); text_editor->unfold_line(p_line); text_editor->call_deferred(SNAME("set_caret_line"), p_line); } void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + text_editor->remove_secondary_carets(); text_editor->unfold_line(p_line); text_editor->call_deferred(SNAME("set_caret_line"), p_line); text_editor->call_deferred(SNAME("set_caret_column"), p_begin); @@ -1526,19 +1711,7 @@ void CodeTextEditor::clear_executing_line() { Variant CodeTextEditor::get_edit_state() { Dictionary state; - - state["scroll_position"] = text_editor->get_v_scroll(); - state["h_scroll_position"] = text_editor->get_h_scroll(); - state["column"] = text_editor->get_caret_column(); - state["row"] = text_editor->get_caret_line(); - - state["selection"] = get_text_editor()->has_selection(); - if (get_text_editor()->has_selection()) { - state["selection_from_line"] = text_editor->get_selection_from_line(); - state["selection_from_column"] = text_editor->get_selection_from_column(); - state["selection_to_line"] = text_editor->get_selection_to_line(); - state["selection_to_column"] = text_editor->get_selection_to_column(); - } + state.merge(get_navigation_state()); state["folded_lines"] = text_editor->get_folded_lines(); state["breakpoints"] = text_editor->get_breakpointed_lines(); @@ -1559,8 +1732,10 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) { text_editor->set_v_scroll(state["scroll_position"]); text_editor->set_h_scroll(state["h_scroll_position"]); - if (state.has("selection")) { + if (state.get("selection", false)) { text_editor->select(state["selection_from_line"], state["selection_from_column"], state["selection_to_line"], state["selection_to_column"]); + } else { + text_editor->deselect(); } if (state.has("folded_lines")) { @@ -1585,6 +1760,25 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) { } } +Variant CodeTextEditor::get_navigation_state() { + Dictionary state; + + state["scroll_position"] = text_editor->get_v_scroll(); + state["h_scroll_position"] = text_editor->get_h_scroll(); + state["column"] = text_editor->get_caret_column(); + state["row"] = text_editor->get_caret_line(); + + state["selection"] = get_text_editor()->has_selection(); + if (get_text_editor()->has_selection()) { + state["selection_from_line"] = text_editor->get_selection_from_line(); + state["selection_from_column"] = text_editor->get_selection_from_column(); + state["selection_to_line"] = text_editor->get_selection_to_line(); + state["selection_to_column"] = text_editor->get_selection_to_column(); + } + + return state; +} + void CodeTextEditor::set_error(const String &p_error) { error->set_text(p_error); if (!p_error.is_empty()) { @@ -1608,6 +1802,7 @@ void CodeTextEditor::goto_error() { if (text_editor->get_line_count() != error_line) { text_editor->unfold_line(error_line); } + text_editor->remove_secondary_carets(); text_editor->set_caret_line(error_line); text_editor->set_caret_column(error_column); text_editor->center_viewport_to_caret(); @@ -1784,8 +1979,10 @@ void CodeTextEditor::set_warning_count(int p_warning_count) { } void CodeTextEditor::toggle_bookmark() { - int line = text_editor->get_caret_line(); - text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line)); + for (int i = 0; i < text_editor->get_caret_count(); i++) { + int line = text_editor->get_caret_line(i); + text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line)); + } } void CodeTextEditor::goto_next_bookmark() { @@ -1794,6 +1991,7 @@ void CodeTextEditor::goto_next_bookmark() { return; } + text_editor->remove_secondary_carets(); int line = text_editor->get_caret_line(); if (line >= (int)bmarks[bmarks.size() - 1]) { text_editor->unfold_line(bmarks[0]); @@ -1818,6 +2016,7 @@ void CodeTextEditor::goto_prev_bookmark() { return; } + text_editor->remove_secondary_carets(); int line = text_editor->get_caret_line(); if (line <= (int)bmarks[0]) { text_editor->unfold_line(bmarks[bmarks.size() - 1]); diff --git a/editor/code_editor.h b/editor/code_editor.h index 49679cc700..c3279e8764 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -197,7 +197,7 @@ class CodeTextEditor : public VBoxContainer { void _update_status_bar_theme(); - void _delete_line(int p_line); + void _delete_line(int p_line, int p_caret); void _toggle_scripts_pressed(); protected: @@ -246,6 +246,7 @@ public: Variant get_edit_state(); void set_edit_state(const Variant &p_state); + Variant get_navigation_state(); void set_error_count(int p_error_count); void set_warning_count(int p_warning_count); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 6bc1536cb9..622ad00502 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1563,8 +1563,22 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { ti = ti->get_parent(); } - // We only need the first child here (C++ source stack trace). + // Find the child with the "C++ Source". + // It's not at a fixed position as "C++ Error" may come first. TreeItem *ci = ti->get_first_child(); + const String cpp_source = "<" + TTR("C++ Source") + ">"; + while (ci) { + if (ci->get_text(0) == cpp_source) { + break; + } + ci = ci->get_next(); + } + + if (!ci) { + WARN_PRINT_ED("No C++ source reference is available for this error."); + return; + } + // Parse back the `file:line @ method()` string. const Vector<String> file_line_number = ci->get_text(1).split("@")[0].strip_edges().split(":"); ERR_FAIL_COND_MSG(file_line_number.size() < 2, "Incorrect C++ source stack trace file:line format (please report)."); diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 1b8146a0f0..73688fc2b7 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -320,6 +320,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes_init() { matched_item = nullptr; match_highest_score = 0; + terms = term.split_spaces(); + if (terms.is_empty()) { + terms.append(term); + } + return true; } @@ -350,62 +355,38 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { // Make an exception for annotations, since there are not that many of them. if (term.length() > 1 || term == "@") { if (search_flags & SEARCH_CONSTRUCTORS) { - for (int i = 0; i < class_doc.constructors.size(); i++) { - String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.constructors[i].name : class_doc.constructors[i].name.to_lower(); - if (method_name.find(term) > -1 || - (term.begins_with(".") && method_name.begins_with(term.substr(1))) || - (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || - (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { - match.constructors.push_back(const_cast<DocData::MethodDoc *>(&class_doc.constructors[i])); - } - } + _match_method_name_and_push_back(class_doc.constructors, &match.constructors); } if (search_flags & SEARCH_METHODS) { - for (int i = 0; i < class_doc.methods.size(); i++) { - String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.methods[i].name : class_doc.methods[i].name.to_lower(); - if (method_name.find(term) > -1 || - (term.begins_with(".") && method_name.begins_with(term.substr(1))) || - (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || - (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { - match.methods.push_back(const_cast<DocData::MethodDoc *>(&class_doc.methods[i])); - } - } + _match_method_name_and_push_back(class_doc.methods, &match.methods); } if (search_flags & SEARCH_OPERATORS) { - for (int i = 0; i < class_doc.operators.size(); i++) { - String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.operators[i].name : class_doc.operators[i].name.to_lower(); - if (method_name.find(term) > -1 || - (term.begins_with(".") && method_name.begins_with(term.substr(1))) || - (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || - (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { - match.operators.push_back(const_cast<DocData::MethodDoc *>(&class_doc.operators[i])); - } - } + _match_method_name_and_push_back(class_doc.operators, &match.operators); } if (search_flags & SEARCH_SIGNALS) { for (int i = 0; i < class_doc.signals.size(); i++) { - if (_match_string(term, class_doc.signals[i].name)) { + if (_all_terms_in_name(class_doc.signals[i].name)) { match.signals.push_back(const_cast<DocData::MethodDoc *>(&class_doc.signals[i])); } } } if (search_flags & SEARCH_CONSTANTS) { for (int i = 0; i < class_doc.constants.size(); i++) { - if (_match_string(term, class_doc.constants[i].name)) { + if (_all_terms_in_name(class_doc.constants[i].name)) { match.constants.push_back(const_cast<DocData::ConstantDoc *>(&class_doc.constants[i])); } } } if (search_flags & SEARCH_PROPERTIES) { for (int i = 0; i < class_doc.properties.size(); i++) { - if (_match_string(term, class_doc.properties[i].name) || _match_string(term, class_doc.properties[i].getter) || _match_string(term, class_doc.properties[i].setter)) { + if (_all_terms_in_name(class_doc.properties[i].name)) { match.properties.push_back(const_cast<DocData::PropertyDoc *>(&class_doc.properties[i])); } } } if (search_flags & SEARCH_THEME_ITEMS) { for (int i = 0; i < class_doc.theme_properties.size(); i++) { - if (_match_string(term, class_doc.theme_properties[i].name)) { + if (_all_terms_in_name(class_doc.theme_properties[i].name)) { match.theme_properties.push_back(const_cast<DocData::ThemeItemDoc *>(&class_doc.theme_properties[i])); } } @@ -417,7 +398,6 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { } } } - matches[class_doc.name] = match; } matches[class_doc.name] = match; } @@ -514,6 +494,28 @@ bool EditorHelpSearch::Runner::_phase_select_match() { return true; } +void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<DocData::MethodDoc *> *r_match_methods) { + // Constructors, Methods, Operators... + for (int i = 0; i < p_methods.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower(); + if (_all_terms_in_name(method_name) || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + r_match_methods->push_back(const_cast<DocData::MethodDoc *>(&p_methods[i])); + } + } +} + +bool EditorHelpSearch::Runner::_all_terms_in_name(String name) { + for (int i = 0; i < terms.size(); i++) { + if (!_match_string(terms[i], name)) { + return false; + } + } + return true; +} + bool EditorHelpSearch::Runner::_match_string(const String &p_term, const String &p_string) const { if (search_flags & SEARCH_CASE_SENSITIVE) { return p_string.find(p_term) > -1; diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h index efd8645cd7..a8bd219b89 100644 --- a/editor/editor_help_search.h +++ b/editor/editor_help_search.h @@ -119,6 +119,7 @@ class EditorHelpSearch::Runner : public RefCounted { Control *ui_service = nullptr; Tree *results_tree = nullptr; String term; + Vector<String> terms; int search_flags; Ref<Texture2D> empty_icon; @@ -145,6 +146,8 @@ class EditorHelpSearch::Runner : public RefCounted { String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const; + void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<DocData::MethodDoc *> *r_match_methods); + bool _all_terms_in_name(String name); bool _match_string(const String &p_term, const String &p_string) const; void _match_item(TreeItem *p_item, const String &p_text); TreeItem *_create_class_hierarchy(const ClassMatch &p_match); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6bc281c7e4..34c0c35b12 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3772,7 +3772,7 @@ int EditorNode::new_scene() { if (editor_data.get_edited_scene_count() > 1) { for (int i = 0; i < editor_data.get_edited_scene_count() - 1; i++) { bool unsaved = get_undo_redo()->is_history_unsaved(editor_data.get_scene_history_id(i)); - if (!unsaved && editor_data.get_scene_path(i).is_empty()) { + if (!unsaved && editor_data.get_scene_path(i).is_empty() && editor_data.get_edited_scene_root(i) == nullptr) { editor_data.remove_scene(i); idx--; } @@ -7618,7 +7618,7 @@ bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) { return discard; } -EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { +EditorPlugin::AfterGUIInput EditorPluginList::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) { EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; for (int i = 0; i < plugins_list.size(); i++) { @@ -7626,7 +7626,7 @@ EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D continue; } - EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_spatial_gui_input(p_camera, p_event); + EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_3d_gui_input(p_camera, p_event); if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) { after = EditorPlugin::AFTER_GUI_INPUT_STOP; } @@ -7650,15 +7650,15 @@ void EditorPluginList::forward_canvas_force_draw_over_viewport(Control *p_overla } } -void EditorPluginList::forward_spatial_draw_over_viewport(Control *p_overlay) { +void EditorPluginList::forward_3d_draw_over_viewport(Control *p_overlay) { for (int i = 0; i < plugins_list.size(); i++) { - plugins_list[i]->forward_spatial_draw_over_viewport(p_overlay); + plugins_list[i]->forward_3d_draw_over_viewport(p_overlay); } } -void EditorPluginList::forward_spatial_force_draw_over_viewport(Control *p_overlay) { +void EditorPluginList::forward_3d_force_draw_over_viewport(Control *p_overlay) { for (int i = 0; i < plugins_list.size(); i++) { - plugins_list[i]->forward_spatial_force_draw_over_viewport(p_overlay); + plugins_list[i]->forward_3d_force_draw_over_viewport(p_overlay); } } diff --git a/editor/editor_node.h b/editor/editor_node.h index 3c236a1301..9452425dc8 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -935,9 +935,9 @@ public: bool forward_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); void forward_canvas_force_draw_over_viewport(Control *p_overlay); - EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); - void forward_spatial_draw_over_viewport(Control *p_overlay); - void forward_spatial_force_draw_over_viewport(Control *p_overlay); + EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled); + void forward_3d_draw_over_viewport(Control *p_overlay); + void forward_3d_force_draw_over_viewport(Control *p_overlay); void add_plugin(EditorPlugin *p_plugin); void remove_plugin(EditorPlugin *p_plugin); void clear(); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 981dad2d2a..ddd4ea4adb 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -604,7 +604,7 @@ int EditorPlugin::update_overlays() const { } } -EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput EditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { int success; if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) { @@ -614,11 +614,11 @@ EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_ return EditorPlugin::AFTER_GUI_INPUT_PASS; } -void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) { +void EditorPlugin::forward_3d_draw_over_viewport(Control *p_overlay) { GDVIRTUAL_CALL(_forward_3d_draw_over_viewport, p_overlay); } -void EditorPlugin::forward_spatial_force_draw_over_viewport(Control *p_overlay) { +void EditorPlugin::forward_3d_force_draw_over_viewport(Control *p_overlay) { GDVIRTUAL_CALL(_forward_3d_force_draw_over_viewport, p_overlay); } @@ -753,12 +753,12 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte EditorExport::get_singleton()->remove_export_plugin(p_exporter); } -void EditorPlugin::add_spatial_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { +void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { ERR_FAIL_COND(!p_gizmo_plugin.is_valid()); Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin); } -void EditorPlugin::remove_spatial_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { +void EditorPlugin::remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { ERR_FAIL_COND(!p_gizmo_plugin.is_valid()); Node3DEditor::get_singleton()->remove_gizmo_plugin(p_gizmo_plugin); } @@ -908,8 +908,8 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin); ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin); ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin); - ClassDB::bind_method(D_METHOD("add_spatial_gizmo_plugin", "plugin"), &EditorPlugin::add_spatial_gizmo_plugin); - ClassDB::bind_method(D_METHOD("remove_spatial_gizmo_plugin", "plugin"), &EditorPlugin::remove_spatial_gizmo_plugin); + ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin); + ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin); ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin); ClassDB::bind_method(D_METHOD("remove_inspector_plugin", "plugin"), &EditorPlugin::remove_inspector_plugin); ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index a048b174e4..1211bcf53c 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -237,9 +237,9 @@ public: virtual void forward_canvas_draw_over_viewport(Control *p_overlay); virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay); - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); - virtual void forward_spatial_draw_over_viewport(Control *p_overlay); - virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay); + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual void forward_3d_draw_over_viewport(Control *p_overlay); + virtual void forward_3d_force_draw_over_viewport(Control *p_overlay); virtual String get_name() const; virtual const Ref<Texture2D> get_icon() const; @@ -285,8 +285,8 @@ public: void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter); void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter); - void add_spatial_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); - void remove_spatial_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); + void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); + void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 3b99962435..66a3076733 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1489,12 +1489,12 @@ void EditorPropertyFloat::update_property() { void EditorPropertyFloat::_bind_methods() { } -void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range, bool p_greater, bool p_lesser, const String &p_suffix, bool p_angle_in_radians) { +void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_exp_range, bool p_greater, bool p_lesser, const String &p_suffix, bool p_angle_in_radians) { angle_in_radians = p_angle_in_radians; spin->set_min(p_min); spin->set_max(p_max); spin->set_step(p_step); - spin->set_hide_slider(p_no_slider); + spin->set_hide_slider(p_hide_slider); spin->set_exp_ratio(p_exp_range); spin->set_allow_greater(p_greater); spin->set_allow_lesser(p_lesser); @@ -1797,12 +1797,12 @@ void EditorPropertyVector2::_notification(int p_what) { } } -void EditorPropertyVector2::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_link, const String &p_suffix) { +void EditorPropertyVector2::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link, const String &p_suffix) { for (int i = 0; i < 2; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -1907,12 +1907,12 @@ void EditorPropertyRect2::_notification(int p_what) { void EditorPropertyRect2::_bind_methods() { } -void EditorPropertyRect2::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyRect2::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -2078,13 +2078,13 @@ void EditorPropertyVector3::_notification(int p_what) { void EditorPropertyVector3::_bind_methods() { } -void EditorPropertyVector3::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_link, const String &p_suffix, bool p_angle_in_radians) { +void EditorPropertyVector3::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link, const String &p_suffix, bool p_angle_in_radians) { angle_in_radians = p_angle_in_radians; for (int i = 0; i < 3; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -2210,12 +2210,12 @@ void EditorPropertyVector2i::_notification(int p_what) { } } -void EditorPropertyVector2i::setup(int p_min, int p_max, bool p_no_slider, bool p_link, const String &p_suffix) { +void EditorPropertyVector2i::setup(int p_min, int p_max, bool p_hide_slider, bool p_link, const String &p_suffix) { for (int i = 0; i < 2; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(1); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -2320,12 +2320,12 @@ void EditorPropertyRect2i::_notification(int p_what) { void EditorPropertyRect2i::_bind_methods() { } -void EditorPropertyRect2i::setup(int p_min, int p_max, bool p_no_slider, const String &p_suffix) { +void EditorPropertyRect2i::setup(int p_min, int p_max, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(1); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -2464,12 +2464,12 @@ void EditorPropertyVector3i::_notification(int p_what) { void EditorPropertyVector3i::_bind_methods() { } -void EditorPropertyVector3i::setup(int p_min, int p_max, bool p_no_slider, bool p_link, const String &p_suffix) { +void EditorPropertyVector3i::setup(int p_min, int p_max, bool p_hide_slider, bool p_link, const String &p_suffix) { for (int i = 0; i < 3; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(1); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -2573,12 +2573,12 @@ void EditorPropertyPlane::_notification(int p_what) { void EditorPropertyPlane::_bind_methods() { } -void EditorPropertyPlane::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyPlane::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); } @@ -2734,12 +2734,12 @@ void EditorPropertyQuaternion::_notification(int p_what) { void EditorPropertyQuaternion::_bind_methods() { } -void EditorPropertyQuaternion::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix, bool p_hide_editor) { +void EditorPropertyQuaternion::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix, bool p_hide_editor) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); // Quaternion is inherently unitless, however someone may want to use it as @@ -2882,16 +2882,14 @@ void EditorPropertyVector4::_notification(int p_what) { void EditorPropertyVector4::_bind_methods() { } -void EditorPropertyVector4::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyVector4::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); - // Vector4 is inherently unitless, however someone may want to use it as - // a generic way to store 4 values, so we'll still respect the suffix. spin[i]->set_suffix(p_suffix); } } @@ -2974,11 +2972,11 @@ void EditorPropertyVector4i::_notification(int p_what) { void EditorPropertyVector4i::_bind_methods() { } -void EditorPropertyVector4i::setup(double p_min, double p_max, bool p_no_slider, const String &p_suffix) { +void EditorPropertyVector4i::setup(double p_min, double p_max, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 4; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -3069,12 +3067,12 @@ void EditorPropertyAABB::_notification(int p_what) { void EditorPropertyAABB::_bind_methods() { } -void EditorPropertyAABB::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyAABB::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 6; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); spin[i]->set_suffix(p_suffix); @@ -3157,12 +3155,12 @@ void EditorPropertyTransform2D::_notification(int p_what) { void EditorPropertyTransform2D::_bind_methods() { } -void EditorPropertyTransform2D::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyTransform2D::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 6; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); if (i % 3 == 2) { @@ -3249,12 +3247,12 @@ void EditorPropertyBasis::_notification(int p_what) { void EditorPropertyBasis::_bind_methods() { } -void EditorPropertyBasis::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyBasis::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 9; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); // Basis is inherently unitless, however someone may want to use it as @@ -3347,12 +3345,12 @@ void EditorPropertyTransform3D::_notification(int p_what) { void EditorPropertyTransform3D::_bind_methods() { } -void EditorPropertyTransform3D::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyTransform3D::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 12; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); if (i % 4 == 3) { @@ -3393,22 +3391,22 @@ void EditorPropertyProjection::_value_changed(double val, const String &p_name) } Projection p; - p.matrix[0][0] = spin[0]->get_value(); - p.matrix[0][1] = spin[1]->get_value(); - p.matrix[0][2] = spin[2]->get_value(); - p.matrix[0][3] = spin[3]->get_value(); - p.matrix[1][0] = spin[4]->get_value(); - p.matrix[1][1] = spin[5]->get_value(); - p.matrix[1][2] = spin[6]->get_value(); - p.matrix[1][3] = spin[7]->get_value(); - p.matrix[2][0] = spin[8]->get_value(); - p.matrix[2][1] = spin[9]->get_value(); - p.matrix[2][2] = spin[10]->get_value(); - p.matrix[2][3] = spin[11]->get_value(); - p.matrix[3][0] = spin[12]->get_value(); - p.matrix[3][1] = spin[13]->get_value(); - p.matrix[3][2] = spin[14]->get_value(); - p.matrix[3][3] = spin[15]->get_value(); + p.columns[0][0] = spin[0]->get_value(); + p.columns[0][1] = spin[1]->get_value(); + p.columns[0][2] = spin[2]->get_value(); + p.columns[0][3] = spin[3]->get_value(); + p.columns[1][0] = spin[4]->get_value(); + p.columns[1][1] = spin[5]->get_value(); + p.columns[1][2] = spin[6]->get_value(); + p.columns[1][3] = spin[7]->get_value(); + p.columns[2][0] = spin[8]->get_value(); + p.columns[2][1] = spin[9]->get_value(); + p.columns[2][2] = spin[10]->get_value(); + p.columns[2][3] = spin[11]->get_value(); + p.columns[3][0] = spin[12]->get_value(); + p.columns[3][1] = spin[13]->get_value(); + p.columns[3][2] = spin[14]->get_value(); + p.columns[3][3] = spin[15]->get_value(); emit_changed(get_edited_property(), p, p_name); } @@ -3419,22 +3417,22 @@ void EditorPropertyProjection::update_property() { void EditorPropertyProjection::update_using_transform(Projection p_transform) { setting = true; - spin[0]->set_value(p_transform.matrix[0][0]); - spin[1]->set_value(p_transform.matrix[0][1]); - spin[2]->set_value(p_transform.matrix[0][2]); - spin[3]->set_value(p_transform.matrix[0][3]); - spin[4]->set_value(p_transform.matrix[1][0]); - spin[5]->set_value(p_transform.matrix[1][1]); - spin[6]->set_value(p_transform.matrix[1][2]); - spin[7]->set_value(p_transform.matrix[1][3]); - spin[8]->set_value(p_transform.matrix[2][0]); - spin[9]->set_value(p_transform.matrix[2][1]); - spin[10]->set_value(p_transform.matrix[2][2]); - spin[11]->set_value(p_transform.matrix[2][3]); - spin[12]->set_value(p_transform.matrix[3][0]); - spin[13]->set_value(p_transform.matrix[3][1]); - spin[14]->set_value(p_transform.matrix[3][2]); - spin[15]->set_value(p_transform.matrix[3][3]); + spin[0]->set_value(p_transform.columns[0][0]); + spin[1]->set_value(p_transform.columns[0][1]); + spin[2]->set_value(p_transform.columns[0][2]); + spin[3]->set_value(p_transform.columns[0][3]); + spin[4]->set_value(p_transform.columns[1][0]); + spin[5]->set_value(p_transform.columns[1][1]); + spin[6]->set_value(p_transform.columns[1][2]); + spin[7]->set_value(p_transform.columns[1][3]); + spin[8]->set_value(p_transform.columns[2][0]); + spin[9]->set_value(p_transform.columns[2][1]); + spin[10]->set_value(p_transform.columns[2][2]); + spin[11]->set_value(p_transform.columns[2][3]); + spin[12]->set_value(p_transform.columns[3][0]); + spin[13]->set_value(p_transform.columns[3][1]); + spin[14]->set_value(p_transform.columns[3][2]); + spin[15]->set_value(p_transform.columns[3][3]); setting = false; } @@ -3453,12 +3451,12 @@ void EditorPropertyProjection::_notification(int p_what) { void EditorPropertyProjection::_bind_methods() { } -void EditorPropertyProjection::setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix) { +void EditorPropertyProjection::setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix) { for (int i = 0; i < 16; i++) { spin[i]->set_min(p_min); spin[i]->set_max(p_max); spin[i]->set_step(p_step); - spin[i]->set_hide_slider(p_no_slider); + spin[i]->set_hide_slider(p_hide_slider); spin[i]->set_allow_greater(true); spin[i]->set_allow_lesser(true); if (i % 4 == 3) { @@ -4218,7 +4216,7 @@ static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const Stri hint.or_greater = true; } else if (slice == "or_less") { hint.or_less = true; - } else if (slice == "no_slider") { + } else if (slice == "hide_slider") { hint.hide_slider = true; } else if (slice == "exp") { hint.exp_range = true; diff --git a/editor/editor_properties.h b/editor/editor_properties.h index d6c9510634..9ad4c02111 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -433,7 +433,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range, bool p_greater, bool p_lesser, const String &p_suffix = String(), bool p_angle_in_radians = false); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_exp_range, bool p_greater, bool p_lesser, const String &p_suffix = String(), bool p_angle_in_radians = false); EditorPropertyFloat(); }; @@ -496,7 +496,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_link = false, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link = false, const String &p_suffix = String()); EditorPropertyVector2(bool p_force_wide = false); }; @@ -513,7 +513,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyRect2(bool p_force_wide = false); }; @@ -541,7 +541,7 @@ public: virtual void update_property() override; virtual void update_using_vector(Vector3 p_vector); virtual Vector3 get_vector(); - void setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_link = false, const String &p_suffix = String(), bool p_angle_in_radians = false); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link = false, const String &p_suffix = String(), bool p_angle_in_radians = false); EditorPropertyVector3(bool p_force_wide = false); }; @@ -561,7 +561,7 @@ protected: public: virtual void update_property() override; - void setup(int p_min, int p_max, bool p_no_slider, bool p_link = false, const String &p_suffix = String()); + void setup(int p_min, int p_max, bool p_hide_slider, bool p_link = false, const String &p_suffix = String()); EditorPropertyVector2i(bool p_force_wide = false); }; @@ -578,7 +578,7 @@ protected: public: virtual void update_property() override; - void setup(int p_min, int p_max, bool p_no_slider, const String &p_suffix = String()); + void setup(int p_min, int p_max, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyRect2i(bool p_force_wide = false); }; @@ -603,7 +603,7 @@ protected: public: virtual void update_property() override; - void setup(int p_min, int p_max, bool p_no_slider, bool p_link = false, const String &p_suffix = String()); + void setup(int p_min, int p_max, bool p_hide_slider, bool p_link = false, const String &p_suffix = String()); EditorPropertyVector3i(bool p_force_wide = false); }; @@ -620,7 +620,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyPlane(bool p_force_wide = false); }; @@ -654,7 +654,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String(), bool p_hide_editor = false); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String(), bool p_hide_editor = false); EditorPropertyQuaternion(); }; @@ -671,7 +671,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyVector4(); }; @@ -688,7 +688,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyVector4i(); }; @@ -705,7 +705,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyAABB(); }; @@ -722,7 +722,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyTransform2D(bool p_include_origin = true); }; @@ -739,7 +739,7 @@ protected: public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyBasis(); }; @@ -757,7 +757,7 @@ protected: public: virtual void update_property() override; virtual void update_using_transform(Transform3D p_transform); - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyTransform3D(); }; @@ -775,7 +775,7 @@ protected: public: virtual void update_property() override; virtual void update_using_transform(Projection p_transform); - void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String()); + void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String()); EditorPropertyProjection(); }; diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index 2c09543d92..db501aac03 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -41,6 +41,8 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/event_listener_line_edit.h" +#include "editor/input_event_configuration_dialog.h" #include "scene/gui/margin_container.h" void EditorSettingsDialog::ok_pressed() { @@ -106,11 +108,16 @@ void EditorSettingsDialog::popup_edit_settings() { _focus_current_search_box(); } -void EditorSettingsDialog::_filter_shortcuts(const String &p_filter) { - shortcut_filter = p_filter; +void EditorSettingsDialog::_filter_shortcuts(const String &) { _update_shortcuts(); } +void EditorSettingsDialog::_filter_shortcuts_by_event(const Ref<InputEvent> &p_event) { + if (p_event.is_null() || (p_event->is_pressed() && !p_event->is_echo())) { + _update_shortcuts(); + } +} + void EditorSettingsDialog::_undo_redo_callback(void *p_self, const String &p_name) { EditorNode::get_log()->add_message(p_name, EditorLog::MSG_TYPE_EDITOR); } @@ -326,6 +333,22 @@ void EditorSettingsDialog::_create_shortcut_treeitem(TreeItem *p_parent, const S } } +bool EditorSettingsDialog::_should_display_shortcut(const String &p_name, const Array &p_events) const { + const Ref<InputEvent> search_ev = shortcut_search_by_event->get_event(); + bool event_match = true; + if (search_ev.is_valid()) { + event_match = false; + for (int i = 0; i < p_events.size(); ++i) { + const Ref<InputEvent> ev = p_events[i]; + if (ev.is_valid() && ev->is_match(search_ev, true)) { + event_match = true; + } + } + } + + return event_match && shortcut_search_box->get_text().is_subsequence_ofn(p_name); +} + void EditorSettingsDialog::_update_shortcuts() { // Before clearing the tree, take note of which categories are collapsed so that this state can be maintained when the tree is repopulated. HashMap<String, bool> collapsed; @@ -379,32 +402,17 @@ void EditorSettingsDialog::_update_shortcuts() { const String &action_name = E.key; const InputMap::Action &action = E.value; - Array events; // Need to get the list of events into an array so it can be set as metadata on the item. - Vector<String> event_strings; - // Skip non-builtin actions. if (!InputMap::get_singleton()->get_builtins_with_feature_overrides_applied().has(action_name)) { continue; } const List<Ref<InputEvent>> &all_default_events = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied().find(action_name)->value; - List<Ref<InputEventKey>> key_default_events; - // Remove all non-key events from the defaults. Only check keys, since we are in the editor. - for (const List<Ref<InputEvent>>::Element *I = all_default_events.front(); I; I = I->next()) { - Ref<InputEventKey> k = I->get(); - if (k.is_valid()) { - key_default_events.push_back(k); - } - } - - // Join the text of the events with a delimiter so they can all be displayed in one cell. - String events_display_string = event_strings.is_empty() ? "None" : String("; ").join(event_strings); - - if (!shortcut_filter.is_subsequence_ofn(action_name) && (events_display_string == "None" || !shortcut_filter.is_subsequence_ofn(events_display_string))) { + Array action_events = _event_list_to_array_helper(action.inputs); + if (!_should_display_shortcut(action_name, action_events)) { continue; } - Array action_events = _event_list_to_array_helper(action.inputs); Array default_events = _event_list_to_array_helper(all_default_events); bool same_as_defaults = Shortcut::is_event_array_equal(default_events, action_events); bool collapse = !collapsed.has(action_name) || (collapsed.has(action_name) && collapsed[action_name]); @@ -459,8 +467,7 @@ void EditorSettingsDialog::_update_shortcuts() { String section_name = E.get_slice("/", 0); TreeItem *section = sections[section_name]; - // Shortcut Item - if (!shortcut_filter.is_subsequence_ofn(sc->get_name())) { + if (!_should_display_shortcut(sc->get_name(), sc->get_events())) { continue; } @@ -749,12 +756,29 @@ EditorSettingsDialog::EditorSettingsDialog() { tabs->add_child(tab_shortcuts); tab_shortcuts->set_name(TTR("Shortcuts")); + HBoxContainer *top_hbox = memnew(HBoxContainer); + top_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tab_shortcuts->add_child(top_hbox); + shortcut_search_box = memnew(LineEdit); - shortcut_search_box->set_placeholder(TTR("Filter Shortcuts")); + shortcut_search_box->set_placeholder(TTR("Filter by name...")); shortcut_search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); - tab_shortcuts->add_child(shortcut_search_box); + top_hbox->add_child(shortcut_search_box); shortcut_search_box->connect("text_changed", callable_mp(this, &EditorSettingsDialog::_filter_shortcuts)); + shortcut_search_by_event = memnew(EventListenerLineEdit); + shortcut_search_by_event->set_h_size_flags(Control::SIZE_EXPAND_FILL); + shortcut_search_by_event->set_stretch_ratio(0.75); + shortcut_search_by_event->set_allowed_input_types(INPUT_KEY); + shortcut_search_by_event->connect("event_changed", callable_mp(this, &EditorSettingsDialog::_filter_shortcuts_by_event)); + top_hbox->add_child(shortcut_search_by_event); + + Button *clear_all_search = memnew(Button); + clear_all_search->set_text(TTR("Clear All")); + clear_all_search->connect("pressed", callable_mp(shortcut_search_box, &LineEdit::clear)); + clear_all_search->connect("pressed", callable_mp(shortcut_search_by_event, &EventListenerLineEdit::clear_event)); + top_hbox->add_child(clear_all_search); + shortcuts = memnew(Tree); shortcuts->set_v_size_flags(Control::SIZE_EXPAND_FILL); shortcuts->set_columns(2); @@ -771,8 +795,7 @@ EditorSettingsDialog::EditorSettingsDialog() { // Adding event dialog shortcut_editor = memnew(InputEventConfigurationDialog); shortcut_editor->connect("confirmed", callable_mp(this, &EditorSettingsDialog::_event_config_confirmed)); - shortcut_editor->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_KEY); - shortcut_editor->set_close_on_escape(false); + shortcut_editor->set_allowed_input_types(INPUT_KEY); add_child(shortcut_editor); set_hide_on_ok(true); diff --git a/editor/editor_settings_dialog.h b/editor/editor_settings_dialog.h index 87ed6a77eb..05424a64ed 100644 --- a/editor/editor_settings_dialog.h +++ b/editor/editor_settings_dialog.h @@ -53,6 +53,7 @@ class EditorSettingsDialog : public AcceptDialog { LineEdit *search_box = nullptr; LineEdit *shortcut_search_box = nullptr; + EventListenerLineEdit *shortcut_search_by_event = nullptr; SectionedInspector *inspector = nullptr; // Shortcuts @@ -64,7 +65,6 @@ class EditorSettingsDialog : public AcceptDialog { }; Tree *shortcuts = nullptr; - String shortcut_filter; InputEventConfigurationDialog *shortcut_editor = nullptr; @@ -103,13 +103,13 @@ class EditorSettingsDialog : public AcceptDialog { void _focus_current_search_box(); void _filter_shortcuts(const String &p_filter); + void _filter_shortcuts_by_event(const Ref<InputEvent> &p_event); + bool _should_display_shortcut(const String &p_name, const Array &p_events) const; void _update_shortcuts(); void _shortcut_button_pressed(Object *p_item, int p_column, int p_idx, MouseButton p_button = MouseButton::LEFT); void _shortcut_cell_double_clicked(); - void _builtin_action_popup_index_pressed(int p_index); - static void _undo_redo_callback(void *p_self, const String &p_name); Label *restart_label = nullptr; diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp new file mode 100644 index 0000000000..14e482432a --- /dev/null +++ b/editor/event_listener_line_edit.cpp @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* event_listener_line_edit.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/event_listener_line_edit.h" + +bool EventListenerLineEdit::_is_event_allowed(const Ref<InputEvent> &p_event) const { + const Ref<InputEventMouseButton> mb = p_event; + const Ref<InputEventKey> k = p_event; + const Ref<InputEventJoypadButton> jb = p_event; + const Ref<InputEventJoypadMotion> jm = p_event; + + return (mb.is_valid() && (allowed_input_types & INPUT_MOUSE_BUTTON)) || + (k.is_valid() && (allowed_input_types & INPUT_KEY)) || + (jb.is_valid() && (allowed_input_types & INPUT_JOY_BUTTON)) || + (jm.is_valid() && (allowed_input_types & INPUT_JOY_MOTION)); +} + +void EventListenerLineEdit::gui_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + LineEdit::gui_input(p_event); + return; + } + + // Allow mouse button click on the clear button without being treated as an event. + const Ref<InputEventMouseButton> b = p_event; + if (b.is_valid() && _is_over_clear_button(b->get_position())) { + LineEdit::gui_input(p_event); + return; + } + + // First event will be an event which is used to focus this control - i.e. a mouse click, or a tab press. + // Ignore the first one so that clicking into the LineEdit does not override the current event. + // Ignore is reset to true when the control is unfocused. + if (ignore) { + ignore = false; + return; + } + + accept_event(); + if (!p_event->is_pressed() || p_event->is_echo() || p_event->is_match(event) || !_is_event_allowed(p_event)) { + return; + } + + event = p_event; + set_text(event->as_text()); + emit_signal("event_changed", event); +} + +void EventListenerLineEdit::_on_text_changed(const String &p_text) { + if (p_text.is_empty()) { + clear_event(); + } +} + +void EventListenerLineEdit::_on_focus() { + set_placeholder(TTR("Listening for input...")); +} + +void EventListenerLineEdit::_on_unfocus() { + ignore = true; + set_placeholder(TTR("Filter by event...")); +} + +Ref<InputEvent> EventListenerLineEdit::get_event() const { + return event; +} + +void EventListenerLineEdit::clear_event() { + if (event.is_valid()) { + event = Ref<InputEvent>(); + set_text(""); + emit_signal("event_changed", event); + } +} + +void EventListenerLineEdit::set_allowed_input_types(int input_types) { + allowed_input_types = input_types; +} + +int EventListenerLineEdit::get_allowed_input_types() const { + return allowed_input_types; +} + +void EventListenerLineEdit::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect("text_changed", callable_mp(this, &EventListenerLineEdit::_on_text_changed)); + connect("focus_entered", callable_mp(this, &EventListenerLineEdit::_on_focus)); + connect("focus_exited", callable_mp(this, &EventListenerLineEdit::_on_unfocus)); + set_right_icon(get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons"))); + set_clear_button_enabled(true); + } break; + } +} + +void EventListenerLineEdit::_bind_methods() { + ADD_SIGNAL(MethodInfo("event_changed", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); +} + +EventListenerLineEdit::EventListenerLineEdit() { + set_caret_blink_enabled(false); + set_placeholder(TTR("Filter by event...")); +} diff --git a/editor/event_listener_line_edit.h b/editor/event_listener_line_edit.h new file mode 100644 index 0000000000..5b415e8a2d --- /dev/null +++ b/editor/event_listener_line_edit.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* event_listener_line_edit.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 EVENT_LISTENER_LINE_EDIT_H +#define EVENT_LISTENER_LINE_EDIT_H + +#include "scene/gui/line_edit.h" + +enum InputType { + INPUT_KEY = 1, + INPUT_MOUSE_BUTTON = 2, + INPUT_JOY_BUTTON = 4, + INPUT_JOY_MOTION = 8 +}; + +class EventListenerLineEdit : public LineEdit { + GDCLASS(EventListenerLineEdit, LineEdit) + + int allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION; + bool ignore = true; + bool share_keycodes = false; + Ref<InputEvent> event; + + bool _is_event_allowed(const Ref<InputEvent> &p_event) const; + + void gui_input(const Ref<InputEvent> &p_event) override; + void _on_text_changed(const String &p_text); + + void _on_focus(); + void _on_unfocus(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + Ref<InputEvent> get_event() const; + void clear_event(); + + void set_allowed_input_types(int input_types); + int get_allowed_input_types() const; + +public: + EventListenerLineEdit(); +}; + +#endif // EVENT_LISTENER_LINE_EDIT_H diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp new file mode 100644 index 0000000000..ed9a5898fb --- /dev/null +++ b/editor/input_event_configuration_dialog.cpp @@ -0,0 +1,700 @@ +/*************************************************************************/ +/* input_event_configuration_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/input_event_configuration_dialog.h" +#include "core/input/input_map.h" +#include "editor/editor_scale.h" +#include "editor/event_listener_line_edit.h" +#include "scene/gui/check_box.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/separator.h" +#include "scene/gui/tree.h" + +// Maps to 2*axis if value is neg, or 2*axis+1 if value is pos. +static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = { + TTRC("Left Stick Left, Joystick 0 Left"), + TTRC("Left Stick Right, Joystick 0 Right"), + TTRC("Left Stick Up, Joystick 0 Up"), + TTRC("Left Stick Down, Joystick 0 Down"), + TTRC("Right Stick Left, Joystick 1 Left"), + TTRC("Right Stick Right, Joystick 1 Right"), + TTRC("Right Stick Up, Joystick 1 Up"), + TTRC("Right Stick Down, Joystick 1 Down"), + TTRC("Joystick 2 Left"), + TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right"), + TTRC("Joystick 2 Up"), + TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down"), + TTRC("Joystick 3 Left"), + TTRC("Joystick 3 Right"), + TTRC("Joystick 3 Up"), + TTRC("Joystick 3 Down"), + TTRC("Joystick 4 Left"), + TTRC("Joystick 4 Right"), + TTRC("Joystick 4 Up"), + TTRC("Joystick 4 Down"), +}; + +String InputEventConfigurationDialog::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) const { + ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent"); + + String text = p_event->as_text(); + + Ref<InputEventKey> key = p_event; + if (key.is_valid() && key->is_command_or_control_autoremap()) { +#ifdef MACOS_ENABLED + text = text.replace("Command", "Command/Ctrl"); +#else + text = text.replace("Ctrl", "Command/Ctrl"); +#endif + } + Ref<InputEventMouse> mouse = p_event; + Ref<InputEventJoypadMotion> jp_motion = p_event; + Ref<InputEventJoypadButton> jp_button = p_event; + if (jp_motion.is_valid()) { + // Joypad motion events will display slightly differently than what the event->as_text() provides. See #43660. + String desc = TTR("Unknown Joypad Axis"); + if (jp_motion->get_axis() < JoyAxis::MAX) { + desc = RTR(_joy_axis_descriptions[2 * (size_t)jp_motion->get_axis() + (jp_motion->get_axis_value() < 0 ? 0 : 1)]); + } + + text = vformat("Joypad Axis %s %s (%s)", itos((int64_t)jp_motion->get_axis()), jp_motion->get_axis_value() < 0 ? "-" : "+", desc); + } + if (p_include_device && (mouse.is_valid() || jp_button.is_valid() || jp_motion.is_valid())) { + String device_string = _get_device_string(p_event->get_device()); + text += vformat(" - %s", device_string); + } + + return text; +} + +void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection) { + if (p_event.is_valid()) { + event = p_event; + + // If the event is changed to something which is not the same as the listener, + // clear out the event from the listener text box to avoid confusion. + const Ref<InputEvent> listener_event = event_listener->get_event(); + if (listener_event.is_valid() && !listener_event->is_match(p_event)) { + event_listener->clear_event(); + } + + // Update Label + event_as_text->set_text(get_event_text(event, true)); + + Ref<InputEventKey> k = p_event; + Ref<InputEventMouseButton> mb = p_event; + Ref<InputEventJoypadButton> joyb = p_event; + Ref<InputEventJoypadMotion> joym = p_event; + Ref<InputEventWithModifiers> mod = p_event; + + // Update option values and visibility + bool show_mods = false; + bool show_device = false; + bool show_phys_key = false; + + if (mod.is_valid()) { + show_mods = true; + mod_checkboxes[MOD_ALT]->set_pressed(mod->is_alt_pressed()); + mod_checkboxes[MOD_SHIFT]->set_pressed(mod->is_shift_pressed()); + mod_checkboxes[MOD_CTRL]->set_pressed(mod->is_ctrl_pressed()); + mod_checkboxes[MOD_META]->set_pressed(mod->is_meta_pressed()); + + autoremap_command_or_control_checkbox->set_pressed(mod->is_command_or_control_autoremap()); + } + + if (k.is_valid()) { + show_phys_key = true; + physical_key_checkbox->set_pressed(k->get_physical_keycode() != Key::NONE && k->get_keycode() == Key::NONE); + + } else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) { + show_device = true; + _set_current_device(event->get_device()); + } + + mod_container->set_visible(show_mods); + device_container->set_visible(show_device); + physical_key_checkbox->set_visible(show_phys_key); + additional_options_container->show(); + + // Update selected item in input list. + if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) { + TreeItem *category = input_list_tree->get_root()->get_first_child(); + while (category) { + TreeItem *input_item = category->get_first_child(); + + if (input_item != nullptr) { + // input_type should always be > 0, unless the tree structure has been misconfigured. + int input_type = input_item->get_parent()->get_meta("__type", 0); + if (input_type == 0) { + return; + } + + // If event type matches input types of this category. + if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) { + // Loop through all items of this category until one matches. + while (input_item) { + bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode")); + bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index"); + bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value"); + bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index"); + if (key_match || joyb_match || joym_match || mb_match) { + category->set_collapsed(false); + input_item->select(0); + input_list_tree->ensure_cursor_is_visible(); + return; + } + input_item = input_item->get_next(); + } + } + } + + category->set_collapsed(true); // Event not in this category, so collapse; + category = category->get_next(); + } + } + } else { + // Event is not valid, reset dialog + event = p_event; + event_listener->clear_event(); + event_as_text->set_text(TTR("No Event Configured")); + + additional_options_container->hide(); + input_list_tree->deselect_all(); + _update_input_list(); + } +} + +void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEvent> &p_event) { + // Ignore if invalid, echo or not pressed + if (p_event.is_null() || p_event->is_echo() || !p_event->is_pressed()) { + return; + } + + // Create an editable reference + Ref<InputEvent> received_event = p_event; + // Check what the type is and if it is allowed. + Ref<InputEventKey> k = received_event; + Ref<InputEventJoypadButton> joyb = received_event; + Ref<InputEventJoypadMotion> joym = received_event; + Ref<InputEventMouseButton> mb = received_event; + + int type = 0; + if (k.is_valid()) { + type = INPUT_KEY; + } else if (joyb.is_valid()) { + type = INPUT_JOY_BUTTON; + } else if (joym.is_valid()) { + type = INPUT_JOY_MOTION; + } else if (mb.is_valid()) { + type = INPUT_MOUSE_BUTTON; + } + + if (!(allowed_input_types & type)) { + return; + } + + if (joym.is_valid()) { + float axis_value = joym->get_axis_value(); + if (ABS(axis_value) < 0.9) { + // Ignore motion below 0.9 magnitude to avoid accidental touches + return; + } else { + // Always make the value 1 or -1 for display consistency + joym->set_axis_value(SIGN(axis_value)); + } + } + + if (k.is_valid()) { + k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway. + // Maintain physical keycode option state + if (physical_key_checkbox->is_pressed()) { + k->set_keycode(Key::NONE); + } else { + k->set_physical_keycode(Key::NONE); + } + } + + Ref<InputEventWithModifiers> mod = received_event; + if (mod.is_valid()) { + mod->set_window_id(0); + } + + // Maintain device selection. + received_event->set_device(_get_current_device()); + + _set_event(received_event); +} + +void InputEventConfigurationDialog::_on_listen_focus_changed() { + if (event_listener->has_focus()) { + set_close_on_escape(false); + } else { + set_close_on_escape(true); + } +} + +void InputEventConfigurationDialog::_search_term_updated(const String &) { + _update_input_list(); +} + +void InputEventConfigurationDialog::_update_input_list() { + input_list_tree->clear(); + + TreeItem *root = input_list_tree->create_item(); + String search_term = input_list_search->get_text(); + + bool collapse = input_list_search->get_text().is_empty(); + + if (allowed_input_types & INPUT_KEY) { + TreeItem *kb_root = input_list_tree->create_item(root); + kb_root->set_text(0, TTR("Keyboard Keys")); + kb_root->set_icon(0, icon_cache.keyboard); + kb_root->set_collapsed(collapse); + kb_root->set_meta("__type", INPUT_KEY); + + for (int i = 0; i < keycode_get_count(); i++) { + String name = keycode_get_name_by_index(i); + + if (!search_term.is_empty() && name.findn(search_term) == -1) { + continue; + } + + TreeItem *item = input_list_tree->create_item(kb_root); + item->set_text(0, name); + item->set_meta("__keycode", keycode_get_value_by_index(i)); + } + } + + if (allowed_input_types & INPUT_MOUSE_BUTTON) { + TreeItem *mouse_root = input_list_tree->create_item(root); + mouse_root->set_text(0, TTR("Mouse Buttons")); + mouse_root->set_icon(0, icon_cache.mouse); + mouse_root->set_collapsed(collapse); + mouse_root->set_meta("__type", INPUT_MOUSE_BUTTON); + + MouseButton mouse_buttons[9] = { MouseButton::LEFT, MouseButton::RIGHT, MouseButton::MIDDLE, MouseButton::WHEEL_UP, MouseButton::WHEEL_DOWN, MouseButton::WHEEL_LEFT, MouseButton::WHEEL_RIGHT, MouseButton::MB_XBUTTON1, MouseButton::MB_XBUTTON2 }; + for (int i = 0; i < 9; i++) { + Ref<InputEventMouseButton> mb; + mb.instantiate(); + mb->set_button_index(mouse_buttons[i]); + String desc = get_event_text(mb, false); + + if (!search_term.is_empty() && desc.findn(search_term) == -1) { + continue; + } + + TreeItem *item = input_list_tree->create_item(mouse_root); + item->set_text(0, desc); + item->set_meta("__index", mouse_buttons[i]); + } + } + + if (allowed_input_types & INPUT_JOY_BUTTON) { + TreeItem *joyb_root = input_list_tree->create_item(root); + joyb_root->set_text(0, TTR("Joypad Buttons")); + joyb_root->set_icon(0, icon_cache.joypad_button); + joyb_root->set_collapsed(collapse); + joyb_root->set_meta("__type", INPUT_JOY_BUTTON); + + for (int i = 0; i < (int)JoyButton::MAX; i++) { + Ref<InputEventJoypadButton> joyb; + joyb.instantiate(); + joyb->set_button_index((JoyButton)i); + String desc = get_event_text(joyb, false); + + if (!search_term.is_empty() && desc.findn(search_term) == -1) { + continue; + } + + TreeItem *item = input_list_tree->create_item(joyb_root); + item->set_text(0, desc); + item->set_meta("__index", i); + } + } + + if (allowed_input_types & INPUT_JOY_MOTION) { + TreeItem *joya_root = input_list_tree->create_item(root); + joya_root->set_text(0, TTR("Joypad Axes")); + joya_root->set_icon(0, icon_cache.joypad_axis); + joya_root->set_collapsed(collapse); + joya_root->set_meta("__type", INPUT_JOY_MOTION); + + for (int i = 0; i < (int)JoyAxis::MAX * 2; i++) { + int axis = i / 2; + int direction = (i & 1) ? 1 : -1; + Ref<InputEventJoypadMotion> joym; + joym.instantiate(); + joym->set_axis((JoyAxis)axis); + joym->set_axis_value(direction); + String desc = get_event_text(joym, false); + + if (!search_term.is_empty() && desc.findn(search_term) == -1) { + continue; + } + + TreeItem *item = input_list_tree->create_item(joya_root); + item->set_text(0, desc); + item->set_meta("__axis", i >> 1); + item->set_meta("__value", (i & 1) ? 1 : -1); + } + } +} + +void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) { + Ref<InputEventWithModifiers> ie = event; + + // Not event with modifiers + if (ie.is_null()) { + return; + } + + if (p_index == 0) { + ie->set_alt_pressed(p_checked); + } else if (p_index == 1) { + ie->set_shift_pressed(p_checked); + } else if (p_index == 2) { + if (!autoremap_command_or_control_checkbox->is_pressed()) { + ie->set_ctrl_pressed(p_checked); + } + } else if (p_index == 3) { + if (!autoremap_command_or_control_checkbox->is_pressed()) { + ie->set_meta_pressed(p_checked); + } + } + + _set_event(ie); +} + +void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) { + Ref<InputEventWithModifiers> ie = event; + if (ie.is_valid()) { + ie->set_command_or_control_autoremap(p_checked); + _set_event(ie); + } + + if (p_checked) { + mod_checkboxes[MOD_META]->hide(); + mod_checkboxes[MOD_CTRL]->hide(); + } else { + mod_checkboxes[MOD_META]->show(); + mod_checkboxes[MOD_CTRL]->show(); + } +} + +void InputEventConfigurationDialog::_physical_keycode_toggled(bool p_checked) { + Ref<InputEventKey> k = event; + + if (k.is_null()) { + return; + } + + if (p_checked) { + k->set_physical_keycode(k->get_keycode()); + k->set_keycode(Key::NONE); + } else { + k->set_keycode((Key)k->get_physical_keycode()); + k->set_physical_keycode(Key::NONE); + } + + _set_event(k); +} + +void InputEventConfigurationDialog::_input_list_item_selected() { + TreeItem *selected = input_list_tree->get_selected(); + + // Invalid tree selection - type only exists on the "category" items, which are not a valid selection. + if (selected->has_meta("__type")) { + return; + } + + InputType input_type = (InputType)(int)selected->get_parent()->get_meta("__type"); + + switch (input_type) { + case INPUT_KEY: { + Key keycode = (Key)(int)selected->get_meta("__keycode"); + Ref<InputEventKey> k; + k.instantiate(); + + if (physical_key_checkbox->is_pressed()) { + k->set_physical_keycode(keycode); + k->set_keycode(Key::NONE); + } else { + k->set_physical_keycode(Key::NONE); + k->set_keycode(keycode); + } + + // Maintain modifier state from checkboxes + k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); + k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); + if (autoremap_command_or_control_checkbox->is_pressed()) { + k->set_command_or_control_autoremap(true); + } else { + k->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); + k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); + } + + _set_event(k, false); + } break; + case INPUT_MOUSE_BUTTON: { + MouseButton idx = (MouseButton)(int)selected->get_meta("__index"); + Ref<InputEventMouseButton> mb; + mb.instantiate(); + mb->set_button_index(idx); + // Maintain modifier state from checkboxes + mb->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed()); + mb->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed()); + if (autoremap_command_or_control_checkbox->is_pressed()) { + mb->set_command_or_control_autoremap(true); + } else { + mb->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed()); + mb->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed()); + } + + // Maintain selected device + mb->set_device(_get_current_device()); + + _set_event(mb, false); + } break; + case INPUT_JOY_BUTTON: { + JoyButton idx = (JoyButton)(int)selected->get_meta("__index"); + Ref<InputEventJoypadButton> jb = InputEventJoypadButton::create_reference(idx); + + // Maintain selected device + jb->set_device(_get_current_device()); + + _set_event(jb, false); + } break; + case INPUT_JOY_MOTION: { + JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis"); + int value = selected->get_meta("__value"); + + Ref<InputEventJoypadMotion> jm; + jm.instantiate(); + jm->set_axis(axis); + jm->set_axis_value(value); + + // Maintain selected device + jm->set_device(_get_current_device()); + + _set_event(jm, false); + } break; + } +} + +void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { + // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) + // and option index 1 corresponds to device 0, etc... + event->set_device(p_option_button_index - 1); + event_as_text->set_text(get_event_text(event, true)); +} + +void InputEventConfigurationDialog::_set_current_device(int p_device) { + device_id_option->select(p_device + 1); +} + +int InputEventConfigurationDialog::_get_current_device() const { + return device_id_option->get_selected() - 1; +} + +String InputEventConfigurationDialog::_get_device_string(int p_device) const { + if (p_device == InputMap::ALL_DEVICES) { + return TTR("All Devices"); + } + return TTR("Device") + " " + itos(p_device); +} + +void InputEventConfigurationDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + input_list_search->set_right_icon(input_list_search->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + + physical_key_checkbox->set_icon(get_theme_icon(SNAME("KeyboardPhysical"), SNAME("EditorIcons"))); + + icon_cache.keyboard = get_theme_icon(SNAME("Keyboard"), SNAME("EditorIcons")); + icon_cache.mouse = get_theme_icon(SNAME("Mouse"), SNAME("EditorIcons")); + icon_cache.joypad_button = get_theme_icon(SNAME("JoyButton"), SNAME("EditorIcons")); + icon_cache.joypad_axis = get_theme_icon(SNAME("JoyAxis"), SNAME("EditorIcons")); + + _update_input_list(); + } break; + } +} + +void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event) { + if (p_event.is_valid()) { + _set_event(p_event); + } else { + // Clear Event + _set_event(p_event); + + // Clear Checkbox Values + for (int i = 0; i < MOD_MAX; i++) { + mod_checkboxes[i]->set_pressed(false); + } + + // Enable the Physical Key checkbox by default to encourage its use. + // Physical Key should be used for most game inputs as it allows keys to work + // on non-QWERTY layouts out of the box. + // This is especially important for WASD movement layouts. + physical_key_checkbox->set_pressed(true); + + autoremap_command_or_control_checkbox->set_pressed(false); + + // Select "All Devices" by default. + device_id_option->select(0); + } + + popup_centered(Size2(0, 400) * EDSCALE); +} + +Ref<InputEvent> InputEventConfigurationDialog::get_event() const { + return event; +} + +void InputEventConfigurationDialog::set_allowed_input_types(int p_type_masks) { + allowed_input_types = p_type_masks; +} + +InputEventConfigurationDialog::InputEventConfigurationDialog() { + allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION; + + set_title(TTR("Event Configuration")); + set_min_size(Size2i(550 * EDSCALE, 0)); // Min width + + VBoxContainer *main_vbox = memnew(VBoxContainer); + add_child(main_vbox); + + event_as_text = memnew(Label); + event_as_text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + event_as_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + event_as_text->add_theme_font_override("font", get_theme_font(SNAME("bold"), SNAME("EditorFonts"))); + event_as_text->add_theme_font_size_override("font_size", 18 * EDSCALE); + main_vbox->add_child(event_as_text); + + event_listener = memnew(EventListenerLineEdit); + event_listener->set_h_size_flags(Control::SIZE_EXPAND_FILL); + event_listener->set_stretch_ratio(0.75); + event_listener->connect("event_changed", callable_mp(this, &InputEventConfigurationDialog::_on_listen_input_changed)); + event_listener->connect("focus_entered", callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); + event_listener->connect("focus_exited", callable_mp(this, &InputEventConfigurationDialog::_on_listen_focus_changed)); + main_vbox->add_child(event_listener); + + main_vbox->add_child(memnew(HSeparator)); + + // List of all input options to manually select from. + VBoxContainer *manual_vbox = memnew(VBoxContainer); + manual_vbox->set_name(TTR("Manual Selection")); + manual_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_vbox->add_child(manual_vbox); + + input_list_search = memnew(LineEdit); + input_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); + input_list_search->set_placeholder(TTR("Filter Inputs")); + input_list_search->set_clear_button_enabled(true); + input_list_search->connect("text_changed", callable_mp(this, &InputEventConfigurationDialog::_search_term_updated)); + manual_vbox->add_child(input_list_search); + + input_list_tree = memnew(Tree); + input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree + input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); + input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + manual_vbox->add_child(input_list_tree); + + input_list_tree->set_hide_root(true); + input_list_tree->set_columns(1); + + _update_input_list(); + + // Additional Options + additional_options_container = memnew(VBoxContainer); + additional_options_container->hide(); + + Label *opts_label = memnew(Label); + opts_label->set_theme_type_variation("HeaderSmall"); + opts_label->set_text(TTR("Additional Options")); + additional_options_container->add_child(opts_label); + + // Device Selection + device_container = memnew(HBoxContainer); + device_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Label *device_label = memnew(Label); + device_label->set_theme_type_variation("HeaderSmall"); + device_label->set_text(TTR("Device:")); + device_container->add_child(device_label); + + device_id_option = memnew(OptionButton); + device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); + for (int i = -1; i < 8; i++) { + device_id_option->add_item(_get_device_string(i)); + } + device_id_option->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); + _set_current_device(InputMap::ALL_DEVICES); + device_container->add_child(device_id_option); + + device_container->hide(); + additional_options_container->add_child(device_container); + + // Modifier Selection + mod_container = memnew(HBoxContainer); + for (int i = 0; i < MOD_MAX; i++) { + String name = mods[i]; + mod_checkboxes[i] = memnew(CheckBox); + mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); + mod_checkboxes[i]->set_text(name); + mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i])); + mod_container->add_child(mod_checkboxes[i]); + } + + mod_container->add_child(memnew(VSeparator)); + + autoremap_command_or_control_checkbox = memnew(CheckBox); + autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); + autoremap_command_or_control_checkbox->set_pressed(false); + autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)")); + autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform.")); + mod_container->add_child(autoremap_command_or_control_checkbox); + + mod_container->hide(); + additional_options_container->add_child(mod_container); + + // Physical Key Checkbox + + physical_key_checkbox = memnew(CheckBox); + physical_key_checkbox->set_text(TTR("Use Physical Keycode")); + physical_key_checkbox->set_tooltip_text(TTR("Stores the physical position of the key on the keyboard rather than the key's value. Used for compatibility with non-latin layouts.\nThis should generally be enabled for most game shortcuts, but not in non-game applications.")); + physical_key_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_physical_keycode_toggled)); + physical_key_checkbox->hide(); + additional_options_container->add_child(physical_key_checkbox); + + main_vbox->add_child(additional_options_container); +} diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h new file mode 100644 index 0000000000..fc590cba26 --- /dev/null +++ b/editor/input_event_configuration_dialog.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* input_event_configuration_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 INPUT_EVENT_CONFIGURATION_DIALOG_H +#define INPUT_EVENT_CONFIGURATION_DIALOG_H + +#include "scene/gui/dialogs.h" + +class OptionButton; +class Tree; +class EventListenerLineEdit; +class CheckBox; + +// Confirmation Dialog used when configuring an input event. +// Separate from ActionMapEditor for code cleanliness and separation of responsibilities. +class InputEventConfigurationDialog : public ConfirmationDialog { + GDCLASS(InputEventConfigurationDialog, ConfirmationDialog) +private: + struct IconCache { + Ref<Texture2D> keyboard; + Ref<Texture2D> mouse; + Ref<Texture2D> joypad_button; + Ref<Texture2D> joypad_axis; + } icon_cache; + + Ref<InputEvent> event = Ref<InputEvent>(); + + // Listening for input + EventListenerLineEdit *event_listener = nullptr; + Label *event_as_text = nullptr; + + // List of All Key/Mouse/Joypad input options. + int allowed_input_types; + Tree *input_list_tree = nullptr; + LineEdit *input_list_search = nullptr; + + // Additional Options, shown depending on event selected + VBoxContainer *additional_options_container = nullptr; + + HBoxContainer *device_container = nullptr; + OptionButton *device_id_option = nullptr; + + HBoxContainer *mod_container = nullptr; // Contains the subcontainer and the store command checkbox. + + enum ModCheckbox { + MOD_ALT, + MOD_SHIFT, + MOD_CTRL, + MOD_META, + MOD_MAX + }; +#if defined(MACOS_ENABLED) + String mods[MOD_MAX] = { "Option", "Shift", "Ctrl", "Command" }; +#elif defined(WINDOWS_ENABLED) + String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Windows" }; +#else + String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Meta" }; +#endif + String mods_tip[MOD_MAX] = { "Alt or Option key", "Shift key", "Control key", "Meta/Windows or Command key" }; + + CheckBox *mod_checkboxes[MOD_MAX]; + CheckBox *autoremap_command_or_control_checkbox = nullptr; + + CheckBox *physical_key_checkbox = nullptr; + + void _set_event(const Ref<InputEvent> &p_event, bool p_update_input_list_selection = true); + void _on_listen_input_changed(const Ref<InputEvent> &p_event); + void _on_listen_focus_changed(); + + void _search_term_updated(const String &p_term); + void _update_input_list(); + void _input_list_item_selected(); + + void _mod_toggled(bool p_checked, int p_index); + void _autoremap_command_or_control_toggled(bool p_checked); + void _physical_keycode_toggled(bool p_checked); + + void _device_selection_changed(int p_option_button_index); + void _set_current_device(int p_device); + int _get_current_device() const; + String _get_device_string(int p_device) const; + +protected: + void _notification(int p_what); + +public: + // Pass an existing event to configure it. Alternatively, pass no event to start with a blank configuration. + void popup_and_configure(const Ref<InputEvent> &p_event = Ref<InputEvent>()); + Ref<InputEvent> get_event() const; + String get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) const; + + void set_allowed_input_types(int p_type_masks); + + InputEventConfigurationDialog(); +}; + +#endif // INPUT_EVENT_CONFIGURATION_DIALOG_H diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 06fd9455df..448a0639b1 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -266,7 +266,7 @@ public: virtual void make_visible(bool p_visible) override; virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } - virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } + virtual void forward_3d_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } AnimationPlayerEditorPlugin(); ~AnimationPlayerEditorPlugin(); diff --git a/editor/plugins/input_event_editor_plugin.cpp b/editor/plugins/input_event_editor_plugin.cpp index 153eab32d2..b67f0f6ad2 100644 --- a/editor/plugins/input_event_editor_plugin.cpp +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -30,6 +30,9 @@ #include "input_event_editor_plugin.h" +#include "editor/event_listener_line_edit.h" +#include "editor/input_event_configuration_dialog.h" + void InputEventConfigContainer::_bind_methods() { } @@ -63,13 +66,13 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { Ref<InputEventJoypadMotion> jm = p_event; if (k.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_KEY); + config_dialog->set_allowed_input_types(INPUT_KEY); } else if (m.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_MOUSE_BUTTON); + config_dialog->set_allowed_input_types(INPUT_MOUSE_BUTTON); } else if (jb.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_BUTTON); + config_dialog->set_allowed_input_types(INPUT_JOY_BUTTON); } else if (jm.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_MOTION); + config_dialog->set_allowed_input_types(INPUT_JOY_MOTION); } input_event = p_event; diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index ec6ea7f39b..4a262d2940 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -231,7 +231,7 @@ void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector< gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel); } -void EditorNode3DGizmo::set_spatial_node(Node3D *p_node) { +void EditorNode3DGizmo::set_node_3d(Node3D *p_node) { ERR_FAIL_NULL(p_node); spatial_node = p_node; } @@ -839,8 +839,8 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorNode3DGizmo::add_collision_triangles); ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale", "modulate"), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("add_handles", "handles", "material", "ids", "billboard", "secondary"), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_spatial_node", "node"), &EditorNode3DGizmo::_set_spatial_node); - ClassDB::bind_method(D_METHOD("get_spatial_node"), &EditorNode3DGizmo::get_spatial_node); + ClassDB::bind_method(D_METHOD("set_node_3d", "node"), &EditorNode3DGizmo::_set_node_3d); + ClassDB::bind_method(D_METHOD("get_node_3d"), &EditorNode3DGizmo::get_node_3d); ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); @@ -1039,7 +1039,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { } ref->set_plugin(this); - ref->set_spatial_node(p_spatial); + ref->set_node_3d(p_spatial); ref->set_hidden(current_state == HIDDEN); current_gizmos.push_back(ref.ptr()); @@ -1217,7 +1217,7 @@ EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() { EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() { for (int i = 0; i < current_gizmos.size(); ++i) { current_gizmos[i]->set_plugin(nullptr); - current_gizmos[i]->get_spatial_node()->remove_gizmo(current_gizmos[i]); + current_gizmos[i]->get_node_3d()->remove_gizmo(current_gizmos[i]); } if (Node3DEditor::get_singleton()) { Node3DEditor::get_singleton()->update_all_gizmos(); @@ -1261,7 +1261,7 @@ String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int } Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); if (p_id == 0) { return light->get_param(Light3D::PARAM_RANGE); } @@ -1300,7 +1300,7 @@ static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vec } void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); Transform3D gt = light->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1344,7 +1344,7 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); if (p_cancel) { light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore); @@ -1364,7 +1364,7 @@ void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_i } void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); Color color = light->get_color().srgb_to_linear() * light->get_correlated_color().srgb_to_linear(); color = color.linear_to_srgb(); @@ -1526,12 +1526,12 @@ String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo * } Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); return player->get_emission_angle(); } void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); Transform3D gt = player->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1568,7 +1568,7 @@ void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo } void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); if (p_cancel) { player->set_emission_angle(p_restore); @@ -1583,7 +1583,7 @@ void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gi } void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); + const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1762,7 +1762,7 @@ int Camera3DGizmoPlugin::get_priority() const { } String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { return "FOV"; @@ -1772,7 +1772,7 @@ String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, in } Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { return camera->get_fov(); @@ -1782,7 +1782,7 @@ Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, } void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); Transform3D gt = camera->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1811,7 +1811,7 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { if (p_cancel) { @@ -1838,7 +1838,7 @@ void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_ } void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1962,7 +1962,7 @@ bool MeshInstance3DGizmoPlugin::can_be_hidden() const { } void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_spatial_node()); + MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1998,7 +1998,7 @@ int OccluderInstance3DGizmoPlugin::get_priority() const { } String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); Ref<Occluder3D> o = cs->get_occluder(); if (o.is_null()) { @@ -2017,7 +2017,7 @@ String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p } Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); Ref<Occluder3D> o = oi->get_occluder(); if (o.is_null()) { @@ -2043,7 +2043,7 @@ Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo } void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); Ref<Occluder3D> o = oi->get_occluder(); if (o.is_null()) { @@ -2130,7 +2130,7 @@ void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, } void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); Ref<Occluder3D> o = oi->get_occluder(); if (o.is_null()) { @@ -2181,7 +2181,7 @@ void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_giz } void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2250,7 +2250,7 @@ bool Sprite3DGizmoPlugin::can_be_hidden() const { } void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_spatial_node()); + Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2282,7 +2282,7 @@ bool Label3DGizmoPlugin::can_be_hidden() const { } void Label3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Label3D *label = Object::cast_to<Label3D>(p_gizmo->get_spatial_node()); + Label3D *label = Object::cast_to<Label3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2393,7 +2393,7 @@ int PhysicalBone3DGizmoPlugin::get_priority() const { void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); - PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_spatial_node()); + PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_node_3d()); if (!physical_bone) { return; @@ -2528,7 +2528,7 @@ int RayCast3DGizmoPlugin::get_priority() const { } void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_spatial_node()); + RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2566,7 +2566,7 @@ int ShapeCast3DGizmoPlugin::get_priority() const { } void ShapeCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - ShapeCast3D *shapecast = Object::cast_to<ShapeCast3D>(p_gizmo->get_spatial_node()); + ShapeCast3D *shapecast = Object::cast_to<ShapeCast3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2584,7 +2584,7 @@ void ShapeCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_spatial_node()); + SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2636,7 +2636,7 @@ int VehicleWheel3DGizmoPlugin::get_priority() const { } void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_spatial_node()); + VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2712,7 +2712,7 @@ bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const { } void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2753,17 +2753,17 @@ String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, } Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); return Variant(soft_body->is_point_pinned(p_id)); } void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); soft_body->pin_point_toggle(p_id); } bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); return soft_body->is_point_pinned(p_id); } @@ -2809,12 +2809,12 @@ String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DG } Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); return notifier->get_aabb(); } void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); Transform3D gt = notifier->get_global_transform(); @@ -2866,7 +2866,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p } void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); if (p_cancel) { notifier->set_aabb(p_restore); @@ -2881,7 +2881,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo } void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -3001,12 +3001,12 @@ String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_giz } Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); return particles->get_visibility_aabb(); } void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); Transform3D gt = particles->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -3057,7 +3057,7 @@ void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int } void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); if (p_cancel) { particles->set_visibility_aabb(p_restore); @@ -3072,7 +3072,7 @@ void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, } void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -3148,7 +3148,7 @@ int GPUParticlesCollision3DGizmoPlugin::get_priority() const { } String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - const Node3D *cs = p_gizmo->get_spatial_node(); + const Node3D *cs = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { return "Radius"; @@ -3162,21 +3162,21 @@ String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGiz } Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - const Node3D *cs = p_gizmo->get_spatial_node(); + const Node3D *cs = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { - return p_gizmo->get_spatial_node()->call("get_radius"); + return p_gizmo->get_node_3d()->call("get_radius"); } if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { - return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); + return Vector3(p_gizmo->get_node_3d()->call("get_extents")); } return Variant(); } void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Node3D *sn = p_gizmo->get_spatial_node(); + Node3D *sn = p_gizmo->get_node_3d(); Transform3D gt = sn->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -3222,7 +3222,7 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g } void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Node3D *sn = p_gizmo->get_spatial_node(); + Node3D *sn = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) { if (p_cancel) { @@ -3252,7 +3252,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * } void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Node3D *cs = p_gizmo->get_spatial_node(); + Node3D *cs = p_gizmo->get_node_3d(); p_gizmo->clear(); @@ -3430,12 +3430,12 @@ String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gi } Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); return AABB(probe->get_extents(), probe->get_origin_offset()); } void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); Transform3D gt = probe->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -3492,7 +3492,7 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in } void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); AABB restore = p_restore; @@ -3512,7 +3512,7 @@ void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, } void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -3609,12 +3609,12 @@ String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p } Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); return decal->get_extents(); } void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); Transform3D gt = decal->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -3645,7 +3645,7 @@ void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bo } void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); Vector3 restore = p_restore; @@ -3662,7 +3662,7 @@ void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -3749,12 +3749,12 @@ String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int } Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); return probe->get_extents(); } void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Transform3D gt = probe->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -3785,7 +3785,7 @@ void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Vector3 restore = p_restore; @@ -3802,7 +3802,7 @@ void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_i } void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Ref<Material> material = get_material("voxel_gi_material", p_gizmo); Ref<Material> icon = get_material("voxel_gi_icon", p_gizmo); @@ -3913,7 +3913,7 @@ int LightmapGIGizmoPlugin::get_priority() const { void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo); - LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_spatial_node()); + LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_node_3d()); Ref<LightmapGIData> data = baker->get_light_data(); p_gizmo->add_unscaled_billboard(icon, 0.05); @@ -4163,7 +4163,7 @@ int CollisionObject3DGizmoPlugin::get_priority() const { } void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_spatial_node()); + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -4214,7 +4214,7 @@ int CollisionShape3DGizmoPlugin::get_priority() const { } String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -4245,7 +4245,7 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g } Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -4281,7 +4281,7 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p } void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -4395,7 +4395,7 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i } void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -4499,7 +4499,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo } void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -4814,7 +4814,7 @@ int CollisionPolygon3DGizmoPlugin::get_priority() const { } void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_spatial_node()); + CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -4861,7 +4861,7 @@ int NavigationRegion3DGizmoPlugin::get_priority() const { } void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - NavigationRegion3D *navigationregion = Object::cast_to<NavigationRegion3D>(p_gizmo->get_spatial_node()); + NavigationRegion3D *navigationregion = Object::cast_to<NavigationRegion3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); Ref<NavigationMesh> navigationmesh = navigationregion->get_navigation_mesh(); @@ -5021,7 +5021,7 @@ int NavigationLink3DGizmoPlugin::get_priority() const { } void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); RID nav_map = link->get_world_3d()->get_navigation_map(); real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map); @@ -5106,12 +5106,12 @@ String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g } Variant NavigationLink3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); return p_id == 0 ? link->get_start_location() : link->get_end_location(); } void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); Transform3D gt = link->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -5144,7 +5144,7 @@ void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i } void NavigationLink3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); if (p_cancel) { if (p_id == 0) { @@ -5444,7 +5444,7 @@ int Joint3DGizmoPlugin::get_priority() const { } void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_spatial_node()); + Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -5877,11 +5877,11 @@ String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, i } Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); + return Vector3(p_gizmo->get_node_3d()->call("get_extents")); } void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - Node3D *sn = p_gizmo->get_spatial_node(); + Node3D *sn = p_gizmo->get_node_3d(); Transform3D gt = sn->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -5910,7 +5910,7 @@ void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id } void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - Node3D *sn = p_gizmo->get_spatial_node(); + Node3D *sn = p_gizmo->get_node_3d(); if (p_cancel) { sn->call("set_extents", p_restore); @@ -5925,11 +5925,11 @@ void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p } void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Node3D *cs = p_gizmo->get_spatial_node(); + Node3D *cs = p_gizmo->get_node_3d(); p_gizmo->clear(); - if (RS::FogVolumeShape(int(p_gizmo->get_spatial_node()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { + if (RS::FogVolumeShape(int(p_gizmo->get_node_3d()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { const Ref<Material> material = get_material("shape_material", p_gizmo); const Ref<Material> material_internal = diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 5924f8571a..b642e1024a 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -72,7 +72,7 @@ class EditorNode3DGizmo : public Node3DGizmo { Vector<Instance> instances; Node3D *spatial_node = nullptr; - void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); } + void _set_node_3d(Node *p_node) { set_node_3d(Object::cast_to<Node3D>(p_node)); } protected: static void _bind_methods(); @@ -116,8 +116,8 @@ public: void set_selected(bool p_selected) { selected = p_selected; } bool is_selected() const { return selected; } - void set_spatial_node(Node3D *p_node); - Node3D *get_spatial_node() const { return spatial_node; } + void set_node_3d(Node3D *p_node); + Node3D *get_node_3d() const { return spatial_node; } Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; } bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum); void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 33aad0ac61..2f49b3a62d 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1356,7 +1356,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { EditorNode *en = EditorNode::get_singleton(); EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_3d_gui_input(camera, p_event, true); if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } @@ -1369,7 +1369,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { EditorNode *en = EditorNode::get_singleton(); EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_3d_gui_input(camera, p_event, false); if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } @@ -2735,12 +2735,12 @@ static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Text void Node3DEditorViewport::_draw() { EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - over_plugin_list->forward_spatial_draw_over_viewport(surface); + over_plugin_list->forward_3d_draw_over_viewport(surface); } EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over(); if (!force_over_plugin_list->is_empty()) { - force_over_plugin_list->forward_spatial_force_draw_over_viewport(surface); + force_over_plugin_list->forward_3d_force_draw_over_viewport(surface); } if (surface->has_focus()) { diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index adfaf11264..0203f2933f 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -297,12 +297,12 @@ void Path3DGizmo::redraw() { Path3DGizmo::Path3DGizmo(Path3D *p_path) { path = p_path; - set_spatial_node(p_path); + set_node_3d(p_path); orig_in_length = 0; orig_out_length = 0; } -EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { return EditorPlugin::AFTER_GUI_INPUT_PASS; } diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index 53e4e2efa8..11a640b79f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -101,7 +101,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index 2b3a5c3e23..dc6ae6be96 100644 --- a/editor/plugins/polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -109,7 +109,7 @@ void Polygon3DEditor::_wip_close() { undo_redo->commit_action(); } -EditorPlugin::AfterGUIInput Polygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { return EditorPlugin::AFTER_GUI_INPUT_PASS; } diff --git a/editor/plugins/polygon_3d_editor_plugin.h b/editor/plugins/polygon_3d_editor_plugin.h index 0eb02a39e2..918072b429 100644 --- a/editor/plugins/polygon_3d_editor_plugin.h +++ b/editor/plugins/polygon_3d_editor_plugin.h @@ -90,7 +90,7 @@ protected: static void _bind_methods(); public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(Node *p_node); Polygon3DEditor(); ~Polygon3DEditor(); @@ -102,7 +102,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { Polygon3DEditor *polygon_editor = nullptr; public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return polygon_editor->forward_3d_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a3f2c2509f..67813a3635 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -566,7 +566,7 @@ void ScriptEditor::_save_history() { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); @@ -601,7 +601,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); @@ -3360,7 +3360,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); @@ -3371,11 +3371,12 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { n = history[history_pos].control; - if (Object::cast_to<ScriptEditorBase>(n)) { - Object::cast_to<ScriptEditorBase>(n)->set_edit_state(history[history_pos].state); - Object::cast_to<ScriptEditorBase>(n)->ensure_focus(); + ScriptEditorBase *seb = Object::cast_to<ScriptEditorBase>(n); + if (seb) { + seb->set_edit_state(history[history_pos].state); + seb->ensure_focus(); - Ref<Script> script = Object::cast_to<ScriptEditorBase>(n)->get_edited_resource(); + Ref<Script> script = seb->get_edited_resource(); if (script != nullptr) { notify_script_changed(script); } diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index aab713c9a3..e69b8f8f82 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -146,6 +146,7 @@ public: virtual bool is_unsaved() = 0; virtual Variant get_edit_state() = 0; virtual void set_edit_state(const Variant &p_state) = 0; + virtual Variant get_navigation_state() = 0; virtual void goto_line(int p_line, bool p_with_error = false) = 0; virtual void set_executing_line(int p_line) = 0; virtual void clear_executing_line() = 0; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index a1d24907e5..9910a97f88 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -267,6 +267,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { void ScriptTextEditor::_error_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { + code_editor->get_text_editor()->remove_secondary_carets(); code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); } } @@ -295,6 +296,7 @@ void ScriptTextEditor::reload_text() { void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) { String code = code_editor->get_text_editor()->get_text(); int pos = script->get_language()->find_function(p_function, code); + code_editor->get_text_editor()->remove_secondary_carets(); if (pos == -1) { //does not exist code_editor->get_text_editor()->deselect(); @@ -347,6 +349,10 @@ void ScriptTextEditor::set_edit_state(const Variant &p_state) { } } +Variant ScriptTextEditor::get_navigation_state() { + return code_editor->get_navigation_state(); +} + void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } @@ -1363,6 +1369,7 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } + tx->remove_secondary_carets(); int line = tx->get_caret_line(); // wrap around @@ -1389,6 +1396,7 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } + tx->remove_secondary_carets(); int line = tx->get_caret_line(); // wrap around if (line <= (int)bpoints[0]) { @@ -1409,21 +1417,21 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case HELP_CONTEXTUAL: { - String text = tx->get_selected_text(); + String text = tx->get_selected_text(0); if (text.is_empty()) { - text = tx->get_word_under_caret(); + text = tx->get_word_under_caret(0); } if (!text.is_empty()) { emit_signal(SNAME("request_help"), text); } } break; case LOOKUP_SYMBOL: { - String text = tx->get_word_under_caret(); + String text = tx->get_word_under_caret(0); if (text.is_empty()) { - text = tx->get_selected_text(); + text = tx->get_selected_text(0); } if (!text.is_empty()) { - _lookup_symbol(text, tx->get_caret_line(), tx->get_caret_column()); + _lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0)); } } break; } @@ -1601,6 +1609,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data int col = pos.x; if (d.has("type") && String(d["type"]) == "resource") { + te->remove_secondary_carets(); Ref<Resource> res = d["resource"]; if (!res.is_valid()) { return; @@ -1618,6 +1627,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) { + te->remove_secondary_carets(); Array files = d["files"]; String text_to_drop; @@ -1641,6 +1651,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && String(d["type"]) == "nodes") { + te->remove_secondary_carets(); Node *scene_root = get_tree()->get_edited_scene_root(); if (!scene_root) { EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene.")); @@ -1725,6 +1736,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && String(d["type"]) == "obj_property") { + te->remove_secondary_carets(); const String text_to_drop = String(d["property"]).c_escape().quote(quote_style); te->set_caret_line(row); @@ -1745,8 +1757,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; } else if (k.is_valid() && k->is_action("ui_menu", true)) { - tx->adjust_viewport_to_caret(); - local_pos = tx->get_caret_draw_pos(); + tx->adjust_viewport_to_caret(0); + local_pos = tx->get_caret_draw_pos(0); create_menu = true; } @@ -1757,6 +1769,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -1776,10 +1789,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { String word_at_pos = tx->get_word_at_pos(local_pos); if (word_at_pos.is_empty()) { - word_at_pos = tx->get_word_under_caret(); + word_at_pos = tx->get_word_under_caret(0); } if (word_at_pos.is_empty()) { - word_at_pos = tx->get_selected_text(); + word_at_pos = tx->get_selected_text(0); } bool has_color = (word_at_pos == "Color"); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 99fafb2192..fb02e5c7c4 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -215,6 +215,7 @@ public: virtual bool is_unsaved() override; virtual Variant get_edit_state() override; virtual void set_edit_state(const Variant &p_state) override; + virtual Variant get_navigation_state() override; virtual void ensure_focus() override; virtual void trim_trailing_whitespace() override; virtual void insert_final_newline() override; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index de1a807721..456c28d887 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -264,6 +264,9 @@ void ShaderEditorPlugin::_menu_item_pressed(int p_index) { } else { EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc); } + if (edited_shaders[index].shader_editor) { + edited_shaders[index].shader_editor->tag_saved_version(); + } } break; case FILE_SAVE_AS: { int index = shader_tabs->get_current_tab(); @@ -282,6 +285,9 @@ void ShaderEditorPlugin::_menu_item_pressed(int p_index) { } EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path); } + if (edited_shaders[index].shader_editor) { + edited_shaders[index].shader_editor->tag_saved_version(); + } } break; case FILE_INSPECT: { int index = shader_tabs->get_current_tab(); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 2478ac9514..99f0ff638e 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1128,7 +1128,7 @@ Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() { Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); } -EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); Node3DEditor *ne = Node3DEditor::get_singleton(); if (se && se->is_edit_mode()) { @@ -1234,7 +1234,7 @@ int Skeleton3DGizmoPlugin::get_priority() const { } int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND_V(!skeleton, -1); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); @@ -1277,14 +1277,14 @@ int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gi } Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND_V(!skeleton, Transform3D()); return skeleton->get_bone_global_pose(p_id); } void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND(!skeleton); // Prepare for global to local. @@ -1313,7 +1313,7 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi } void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND(!skeleton); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); @@ -1346,7 +1346,7 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c } void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); int selected = -1; diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 9747ed8374..9f02d144ed 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -238,7 +238,7 @@ class Skeleton3DEditorPlugin : public EditorPlugin { EditorInspectorPluginSkeleton *skeleton_plugin = nullptr; public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; bool has_main_screen() const override { return false; } virtual bool handles(Object *p_object) const override; diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 9846cd4a84..07f0819c7f 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -222,6 +222,10 @@ void TextEditor::set_edit_state(const Variant &p_state) { ensure_focus(); } +Variant TextEditor::get_navigation_state() { + return code_editor->get_navigation_state(); +} + void TextEditor::trim_trailing_whitespace() { code_editor->trim_trailing_whitespace(); } @@ -441,6 +445,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { bool is_folded = tx->is_line_folded(row); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -467,9 +472,9 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventKey> k = ev; if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { CodeEdit *tx = code_editor->get_text_editor(); - int line = tx->get_caret_line(); - tx->adjust_viewport_to_caret(); - _make_context_menu(tx->has_selection(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); + int line = tx->get_caret_line(0); + tx->adjust_viewport_to_caret(0); + _make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0))); context_menu->grab_focus(); } } diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 9ee6a39b2e..a7a640247f 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -117,6 +117,7 @@ public: virtual bool is_unsaved() override; virtual Variant get_edit_state() override; virtual void set_edit_state(const Variant &p_state) override; + virtual Variant get_navigation_state() override; virtual Vector<String> get_functions() override; virtual PackedInt32Array get_breakpoints() override; virtual void set_breakpoint(int p_line, bool p_enabled) override{}; diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index ef3f451079..9dea990bd8 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -917,6 +917,10 @@ bool TextShaderEditor::is_unsaved() const { return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version(); } +void TextShaderEditor::tag_saved_version() { + shader_editor->get_text_editor()->tag_saved_version(); +} + void TextShaderEditor::apply_shaders() { String editor_code = shader_editor->get_text_editor()->get_text(); if (shader.is_valid()) { @@ -954,6 +958,7 @@ void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h index abeaff1fff..c2094342ed 100644 --- a/editor/plugins/text_shader_editor.h +++ b/editor/plugins/text_shader_editor.h @@ -190,6 +190,7 @@ public: void save_external_data(const String &p_str = ""); void validate_script(); bool is_unsaved() const; + void tag_saved_version(); virtual Size2 get_minimum_size() const override { return Size2(0, 200); } diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 45b2a5eb14..4299ae8d3e 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2043,6 +2043,13 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } void TileSetAtlasSourceEditor::_tile_set_changed() { + if (tile_set->get_source_count() == 0) { + // No sources, so nothing to do here anymore. + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + tile_set = Ref<TileSet>(); + return; + } + tile_set_changed_needs_update = true; } diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 0c0151d1a5..2e28367817 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -230,6 +230,7 @@ static const char *gdscript_function_renames[][2] = { { "add_force", "apply_force" }, //RigidBody2D { "add_icon_override", "add_theme_icon_override" }, // Control { "add_scene_import_plugin", "add_scene_format_importer_plugin" }, //EditorPlugin + { "add_spatial_gizmo_plugin", "add_node_3d_gizmo_plugin" }, // EditorPlugin { "add_stylebox_override", "add_theme_stylebox_override" }, // Control { "add_torque", "apply_torque" }, //RigidBody2D { "agent_set_neighbor_dist", "agent_set_neighbor_distance" }, // NavigationServer2D, NavigationServer3D @@ -367,6 +368,7 @@ static const char *gdscript_function_renames[][2] = { { "get_slide_count", "get_slide_collision_count" }, // CharacterBody2D, CharacterBody3D { "get_slips_on_slope", "get_slide_on_slope" }, // SeparationRayShape2D, SeparationRayShape3D { "get_space_override_mode", "get_gravity_space_override_mode" }, // Area2D + { "get_spatial_node", "get_node_3d" }, // EditorNode3DGizmo { "get_speed", "get_velocity" }, // InputEventMouseMotion { "get_stylebox_types", "get_stylebox_type_list" }, // Theme { "get_surface_material", "get_surface_override_material" }, // MeshInstance3D broke ImporterMesh @@ -466,6 +468,7 @@ static const char *gdscript_function_renames[][2] = { { "remove_font_override", "remove_theme_font_override" }, // Control { "remove_icon_override", "remove_theme_icon_override" }, // Control { "remove_scene_import_plugin", "remove_scene_format_importer_plugin" }, //EditorPlugin + { "remove_spatial_gizmo_plugin", "remove_node_3d_gizmo_plugin" }, // EditorPlugin { "remove_stylebox_override", "remove_theme_stylebox_override" }, // Control { "rename_animation", "rename_animation_library" }, // AnimationPlayer { "rename_dependencies", "_rename_dependencies" }, // ResourceFormatLoader @@ -532,6 +535,7 @@ static const char *gdscript_function_renames[][2] = { { "set_slips_on_slope", "set_slide_on_slope" }, // SeparationRayShape2D, SeparationRayShape3D { "set_sort_enabled", "set_y_sort_enabled" }, // Node2D { "set_space_override_mode", "set_gravity_space_override_mode" }, // Area2D + { "set_spatial_node", "set_node_3d" }, // EditorNode3DGizmo { "set_speed", "set_velocity" }, // InputEventMouseMotion { "set_ssao_edge_sharpness", "set_ssao_sharpness" }, // Environment { "set_surface_material", "set_surface_override_material" }, // MeshInstance3D broke ImporterMesh @@ -651,6 +655,7 @@ static const char *csharp_function_renames[][2] = { // { "SetVOffset", "SetDragVerticalOffset" }, // Camera2D broke Camera3D, PathFollow3D, PathFollow2D // {"GetPoints","GetPointsId"},// Astar, broke Line2D, Convexpolygonshape // {"GetVScroll","GetVScrollBar"},//ItemList, broke TextView + { "AddSpatialGizmoPlugin", "AddNode3dGizmoPlugin" }, // EditorPlugin { "RenderingServer", "GetTabAlignment" }, // Tab { "_AboutToShow", "_AboutToPopup" }, // ColorPickerButton { "_GetConfigurationWarning", "_GetConfigurationWarnings" }, // Node @@ -796,6 +801,7 @@ static const char *csharp_function_renames[][2] = { { "GetSizeOverride", "GetSize2dOverride" }, // SubViewport { "GetSlipsOnSlope", "GetSlideOnSlope" }, // SeparationRayShape2D, SeparationRayShape3D { "GetSpaceOverrideMode", "GetGravitySpaceOverrideMode" }, // Area2D + { "GetSpatialNode", "GetNode3d" }, // EditorNode3DGizmo { "GetSpeed", "GetVelocity" }, // InputEventMouseMotion { "GetStyleboxTypes", "GetStyleboxTypeList" }, // Theme { "GetSurfaceMaterial", "GetSurfaceOverrideMaterial" }, // MeshInstance3D broke ImporterMesh @@ -890,6 +896,7 @@ static const char *csharp_function_renames[][2] = { { "RemoveConstantOverride", "RemoveThemeConstantOverride" }, // Control { "RemoveFontOverride", "RemoveThemeFontOverride" }, // Control { "RemoveSceneImportPlugin", "RemoveSceneFormatImporterPlugin" }, //EditorPlugin + { "RemoveSpatialGizmoPlugin", "RemoveNode3dGizmoPlugin" }, // EditorPlugin { "RemoveStyleboxOverride", "RemoveThemeStyleboxOverride" }, // Control { "RenameAnimation", "RenameAnimationLibrary" }, // AnimationPlayer { "RenameDependencies", "_RenameDependencies" }, // ResourceFormatLoader @@ -952,6 +959,7 @@ static const char *csharp_function_renames[][2] = { { "SetSlipsOnSlope", "SetSlideOnSlope" }, // SeparationRayShape2D, SeparationRayShape3D { "SetSortEnabled", "SetYSortEnabled" }, // Node2D { "SetSpaceOverrideMode", "SetGravitySpaceOverrideMode" }, // Area2D + { "SetSpatialNode", "SetNode3d" }, // EditorNode3DGizmo { "SetSpeed", "SetVelocity" }, // InputEventMouseMotion { "SetSsaoEdgeSharpness", "SetSsaoSharpness" }, // Environment { "SetSurfaceMaterial", "SetSurfaceOverrideMaterial" }, // MeshInstance3D broke ImporterMesh diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 2da49f11cc..81f37eaeec 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -36,6 +36,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "scene/gui/check_button.h" #include "servers/movie_writer/movie_writer.h" ProjectSettingsEditor *ProjectSettingsEditor::singleton = nullptr; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index d1dc188be9..967334f183 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1187,6 +1187,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } break; default: { + _filter_option_selected(p_tool); + if (p_tool >= EDIT_SUBRESOURCE_BASE) { int idx = p_tool - EDIT_SUBRESOURCE_BASE; @@ -2883,11 +2885,83 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { void SceneTreeDock::_update_tree_menu() { PopupMenu *tree_menu = button_tree_menu->get_popup(); - tree_menu->set_item_checked(tree_menu->get_item_idx_from_text(TTR("Auto Expand to Selected")), EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected")); + tree_menu->clear(); + + _append_filter_options_to(tree_menu); + + tree_menu->add_separator(); + tree_menu->add_check_item(TTR("Auto Expand to Selected"), TOOL_AUTO_EXPAND); + tree_menu->set_item_checked(tree_menu->get_item_index(TOOL_AUTO_EXPAND), EditorSettings::get_singleton()->get("docks/scene_tree/auto_expand_to_selected")); +} + +void SceneTreeDock::_update_filter_menu() { + _append_filter_options_to(filter->get_menu()); } void SceneTreeDock::_filter_changed(const String &p_filter) { scene_tree->set_filter(p_filter); + + String warning = scene_tree->get_filter_term_warning(); + if (!warning.is_empty()) { + filter->add_theme_icon_override(SNAME("clear"), get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons"))); + filter->set_tooltip_text(warning); + } else { + filter->remove_theme_icon_override(SNAME("clear")); + filter->set_tooltip_text(""); + } +} + +void SceneTreeDock::_filter_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_null()) { + return; + } + + if (mb->is_pressed() && mb->get_button_index() == MouseButton::MIDDLE) { + filter_quick_menu->clear(); + + _append_filter_options_to(filter_quick_menu, false); + filter_quick_menu->set_position(get_screen_position() + get_local_mouse_position()); + filter_quick_menu->reset_size(); + filter_quick_menu->popup(); + filter_quick_menu->grab_focus(); + accept_event(); + } +} + +void SceneTreeDock::_filter_option_selected(int p_option) { + String filter_parameter; + switch (p_option) { + case FILTER_BY_TYPE: { + filter_parameter = "type"; + } break; + case FILTER_BY_GROUP: { + filter_parameter = "group"; + } break; + } + + if (!filter_parameter.is_empty()) { + set_filter((get_filter() + " " + filter_parameter + ":").strip_edges()); + filter->set_caret_column(filter->get_text().length()); + filter->grab_focus(); + } +} + +void SceneTreeDock::_append_filter_options_to(PopupMenu *p_menu, bool p_include_separator) { + if (p_include_separator) { + p_menu->add_separator(); + + p_menu->set_item_text(-1, TTR("Filters")); + p_menu->set_item_icon(-1, get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + p_menu->set_item_indent(-1, -2); + } + + p_menu->add_item(TTR("Filter by Type"), FILTER_BY_TYPE); + p_menu->add_item(TTR("Filter by Group"), FILTER_BY_GROUP); + p_menu->set_item_icon(p_menu->get_item_index(FILTER_BY_TYPE), get_theme_icon(SNAME("Node"), SNAME("EditorIcons"))); + p_menu->set_item_icon(p_menu->get_item_index(FILTER_BY_GROUP), get_theme_icon(SNAME("Groups"), SNAME("EditorIcons"))); + p_menu->set_item_tooltip(p_menu->get_item_index(FILTER_BY_TYPE), TTR("Selects all Nodes of the given type.")); + p_menu->set_item_tooltip(p_menu->get_item_index(FILTER_BY_GROUP), TTR("Selects all Nodes belonging to the given group.\nIf empty, selects any Node belonging to any group.")); } String SceneTreeDock::get_filter() { @@ -3399,14 +3473,22 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec button_instance->set_tooltip_text(TTR("Instantiate a scene file as a Node. Creates an inherited scene if no root node exists.")); button_instance->set_shortcut(ED_GET_SHORTCUT("scene_tree/instance_scene")); filter_hbc->add_child(button_instance); - vbc->add_child(filter_hbc); + + // The "Filter Nodes" text input above the Scene Tree Editor. filter = memnew(LineEdit); filter->set_h_size_flags(SIZE_EXPAND_FILL); filter->set_placeholder(TTR("Filter Nodes")); filter_hbc->add_child(filter); filter->add_theme_constant_override("minimum_character_width", 0); filter->connect("text_changed", callable_mp(this, &SceneTreeDock::_filter_changed)); + filter->connect("gui_input", callable_mp(this, &SceneTreeDock::_filter_gui_input)); + filter->get_menu()->connect("about_to_popup", callable_mp(this, &SceneTreeDock::_update_filter_menu)); + filter->get_menu()->connect("id_pressed", callable_mp(this, &SceneTreeDock::_filter_option_selected)); + + filter_quick_menu = memnew(PopupMenu); + filter_quick_menu->connect("id_pressed", callable_mp(this, &SceneTreeDock::_filter_option_selected)); + filter->add_child(filter_quick_menu); button_create_script = memnew(Button); button_create_script->set_flat(true); @@ -3430,7 +3512,6 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec filter_hbc->add_child(button_tree_menu); PopupMenu *tree_menu = button_tree_menu->get_popup(); - tree_menu->add_check_item(TTR("Auto Expand to Selected"), TOOL_AUTO_EXPAND); tree_menu->connect("id_pressed", callable_mp(this, &SceneTreeDock::_tool_selected).bind(false)); button_hb = memnew(HBoxContainer); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index e48b518252..04bb4d93e7 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -160,7 +160,13 @@ class SceneTreeDock : public VBoxContainer { EditorQuickOpen *quick_open = nullptr; EditorFileDialog *new_scene_from_dialog = nullptr; + enum FilterMenuItems { + FILTER_BY_TYPE = 64, // Used in the same menus as the Tool enum. + FILTER_BY_GROUP, + }; + LineEdit *filter = nullptr; + PopupMenu *filter_quick_menu = nullptr; TextureRect *filter_icon = nullptr; PopupMenu *menu = nullptr; @@ -243,8 +249,12 @@ class SceneTreeDock : public VBoxContainer { void _tree_rmb(const Vector2 &p_menu_pos); void _update_tree_menu(); + void _update_filter_menu(); void _filter_changed(const String &p_filter); + void _filter_gui_input(const Ref<InputEvent> &p_event); + void _filter_option_selected(int option); + void _append_filter_options_to(PopupMenu *p_menu, bool p_include_separator = true); void _perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos); void _replace_with_branch_scene(const String &p_file, Node *base); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 689570bcf6..abebd306e6 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -612,6 +612,7 @@ void SceneTreeEditor::_update_tree(bool p_scroll_to_selected) { bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_selected) { if (!p_parent) { p_parent = tree->get_root(); + filter_term_warning.clear(); } if (!p_parent) { @@ -704,8 +705,8 @@ bool SceneTreeEditor::_item_matches_all_terms(TreeItem *p_item, PackedStringArra return false; } } - } else { - WARN_PRINT(vformat(TTR("Special Node filter \"%s\" is not recognised. Available filters include \"type\" and \"group\"."), parameter)); + } else if (filter_term_warning.is_empty()) { + filter_term_warning = vformat(TTR("\"%s\" is not a known filter."), parameter); continue; } } else { @@ -1029,6 +1030,10 @@ String SceneTreeEditor::get_filter() const { return filter; } +String SceneTreeEditor::get_filter_term_warning() { + return filter_term_warning; +} + void SceneTreeEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { undo_redo = p_undo_redo; } diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index 8fbc3ab6d6..dcdfead885 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -62,6 +62,7 @@ class SceneTreeEditor : public Control { ObjectID instance_node; String filter; + String filter_term_warning; AcceptDialog *error = nullptr; AcceptDialog *warning = nullptr; @@ -142,6 +143,7 @@ public: void set_filter(const String &p_filter); String get_filter() const; + String get_filter_term_warning(); void set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); void set_display_foreign_nodes(bool p_display); diff --git a/gles3_builders.py b/gles3_builders.py index 84f11532e0..46ab1bbc03 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -436,7 +436,7 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da ) fd.write( - """_FORCE_INLINE_ void version_set_uniform(Uniforms p_uniform, const Projection& p_matrix,RID p_version,ShaderVariant p_variant""" + """_FORCE_INLINE_ void version_set_uniform(Uniforms p_uniform, const Projection& p_matrix, RID p_version, ShaderVariant p_variant""" + defvariant + """,uint64_t p_specialization=""" + str(defspec) @@ -444,13 +444,13 @@ def build_gles3_header(filename: str, include: str, class_suffix: str, header_da GLfloat matrix[16]; - for (int i=0;i<4;i++) { - for (int j=0;j<4;j++) { - matrix[i*4+j]=p_matrix.matrix[i][j]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + matrix[i * 4 + j] = p_matrix.columns[i][j]; } } - glUniformMatrix4fv(version_get_uniform(p_uniform,p_version,p_variant,p_specialization),1,false,matrix); + glUniformMatrix4fv(version_get_uniform(p_uniform, p_version, p_variant, p_specialization), 1, false, matrix); }""" ) diff --git a/methods.py b/methods.py index bec1b803e9..f4afead9f4 100644 --- a/methods.py +++ b/methods.py @@ -7,15 +7,6 @@ from collections import OrderedDict from collections.abc import Mapping from typing import Iterator -# We need to define our own `Action` method to control the verbosity of output -# and whenever we need to run those commands in a subprocess on some platforms. -from SCons import Node -from SCons.Script import Action -from SCons.Script import ARGUMENTS -from SCons.Script import Glob -from SCons.Variables.BoolVariable import _text2bool -from platform_methods import run_in_subprocess - def add_source_files(self, sources, files): # Convert string to list of absolute paths (including expanding wildcard) @@ -220,6 +211,9 @@ def get_cmdline_bool(option, default): """We use `ARGUMENTS.get()` to check if options were manually overridden on the command line, and SCons' _text2bool helper to convert them to booleans, otherwise they're handled as strings. """ + from SCons.Script import ARGUMENTS + from SCons.Variables.BoolVariable import _text2bool + cmdline_val = ARGUMENTS.get(option) if cmdline_val is not None: return _text2bool(cmdline_val) @@ -703,6 +697,9 @@ def generate_cpp_hint_file(filename): def glob_recursive(pattern, node="."): + from SCons import Node + from SCons.Script import Glob + results = [] for f in Glob(str(node) + "/*", source=True): if type(f) is Node.FS.Dir: @@ -777,7 +774,7 @@ def generate_vs_project(env, num_jobs): for platform in ModuleConfigs.PLATFORMS ] self.arg_dict["runfile"] += [ - f'bin\\godot.windows.{config}{ModuleConfigs.DEV_SUFFIX}.{plat_id}{f".{name}" if name else ""}.exe' + f'bin\\godot.windows.{config}{ModuleConfigs.DEV_SUFFIX}{".double" if env["float"] == "64" else ""}.{plat_id}{f".{name}" if name else ""}.exe' for config in ModuleConfigs.CONFIGURATIONS for plat_id in ModuleConfigs.PLATFORM_IDS ] @@ -814,12 +811,18 @@ def generate_vs_project(env, num_jobs): if env["dev_build"]: common_build_postfix.append("dev_build=yes") - if env["tests"]: + if env["dev_mode"]: + common_build_postfix.append("dev_mode=yes") + + elif env["tests"]: common_build_postfix.append("tests=yes") if env["custom_modules"]: common_build_postfix.append("custom_modules=%s" % env["custom_modules"]) + if env["float"] == "64": + common_build_postfix.append("float=64") + result = " ^& ".join(common_build_prefix + [" ".join([commands] + common_build_postfix)]) return result @@ -907,6 +910,9 @@ def CommandNoCache(env, target, sources, command, **args): def Run(env, function, short_message, subprocess=True): + from SCons.Script import Action + from platform_methods import run_in_subprocess + output_print = short_message if not env["verbose"] else "" if not subprocess: return Action(function, output_print) diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp index ba9b96db74..73771b3639 100644 --- a/modules/csg/editor/csg_gizmos.cpp +++ b/modules/csg/editor/csg_gizmos.cpp @@ -57,7 +57,7 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() { } String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node()); + CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); if (Object::cast_to<CSGSphere3D>(cs)) { return "Radius"; @@ -79,7 +79,7 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, } Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { - CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node()); + CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); if (Object::cast_to<CSGSphere3D>(cs)) { CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); @@ -105,7 +105,7 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo } void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { - CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node()); + CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); Transform3D gt = cs->get_global_transform(); //gt.orthonormalize(); @@ -208,7 +208,7 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i } void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { - CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node()); + CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); if (Object::cast_to<CSGSphere3D>(cs)) { CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); @@ -308,7 +308,7 @@ bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const { void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); - CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_spatial_node()); + CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); Vector<Vector3> faces = cs->get_brush_faces(); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index d0b4632ebe..bc44479f93 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -507,7 +507,7 @@ <param index="3" name="extra_hints" type="String" default="""" /> <description> Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. - If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"no_slider"[/code] hint will hide the slider element of the editor widget. + If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. [codeblock] diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 54cadf7df3..340f2af693 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -396,9 +396,9 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { if (top->native.is_valid()) { if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); + GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type '" + p_this->get_class() + "'" + "."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be assigned to an object of type '" + p_this->get_class() + "'" + "."); } } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c00036c9f0..3d8e2170ed 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -766,7 +766,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptLanguage::CodeCompletionOption slider2("or_less", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); - ScriptLanguage::CodeCompletionOption slider3("no_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + ScriptLanguage::CodeCompletionOption slider3("hide_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider3.insert_text = slider3.display.quote(p_quote_style); r_result.insert(slider3.display, slider3); } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 46a9b33eb0..fa7e5924f9 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -437,11 +437,11 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { if (!p_docs_down) { // inline comment String inline_comment = lines[p_line]; - int comment_start = inline_comment.find("#"); + int comment_start = inline_comment.find("##"); if (comment_start != -1) { inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges(); if (inline_comment.length() > 1) { - doc_lines.push_back(inline_comment.substr(1, inline_comment.length())); + doc_lines.push_back(inline_comment.substr(2, inline_comment.length())); } } } @@ -454,8 +454,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { } String line_comment = lines[i].strip_edges(true, false); - if (line_comment.begins_with("#")) { - line_comment = line_comment.substr(1, line_comment.length()); + if (line_comment.begins_with("##")) { + line_comment = line_comment.substr(2, line_comment.length()); if (p_docs_down) { doc_lines.push_back(line_comment); } else { diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.out b/modules/gdscript/tests/scripts/parser/features/dictionary.out index 5f999f573a..e1eeb46f78 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary.out @@ -7,8 +7,8 @@ null false empty array zero Vector2i -{22:{4:["nesting", "arrays"]}} -{4:["nesting", "arrays"]} +{ 22: { 4: ["nesting", "arrays"] } } +{ 4: ["nesting", "arrays"] } ["nesting", "arrays"] nesting arrays diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out index 5143d040a9..553d40d953 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -1,2 +1,2 @@ GDTEST_OK -{"a":1, "b":2, "with spaces":3, "2":4} +{ "a": 1, "b": 2, "with spaces": 3, "2": 4 } diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out index dd28609850..cf79845f53 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out @@ -1,2 +1,2 @@ GDTEST_OK -{"hello":{"world":{"is":"beautiful"}}} +{ "hello": { "world": { "is": "beautiful" } } } diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out index 8b8c33202f..508f0ff217 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out +++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out @@ -1,5 +1,5 @@ GDTEST_OK -{8:{"key":"value"}} -{"key":"value"} +{ 8: { "key": "value" } } +{ "key": "value" } value value diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out index 5e7ccf534a..22929bf636 100644 --- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out +++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out @@ -1,6 +1,6 @@ GDTEST_OK -{1:(2, 0)} -{3:(4, 0)} +{ 1: (2, 0) } +{ 3: (4, 0) } [[(5, 0)]] [[(6, 0)]] [[(7, 0)]] diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out index d4468737a5..1f33de00cc 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.out +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out @@ -21,7 +21,7 @@ hello/world RID(0) Node::get_name Node::[signal]property_list_changed -{"hello":123} +{ "hello": 123 } ["hello", 123] [255, 0, 1] [-1, 0, 1] diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index f79731dd22..dcf59bce24 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -77,20 +77,19 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_arg += "export_extras=False,"; } - if (p_options.has(SNAME("blender/meshes/skins")) && p_options[SNAME("blender/meshes/skins")]) { + if (p_options.has(SNAME("blender/meshes/skins"))) { int32_t skins = p_options["blender/meshes/skins"]; if (skins == BLEND_BONE_INFLUENCES_NONE) { - parameters_arg += "export_all_influences=False,"; + parameters_arg += "export_skins=False,"; } else if (skins == BLEND_BONE_INFLUENCES_COMPATIBLE) { - parameters_arg += "export_all_influences=False,"; + parameters_arg += "export_all_influences=False,export_skins=True,"; } else if (skins == BLEND_BONE_INFLUENCES_ALL) { - parameters_arg += "export_all_influences=True,"; + parameters_arg += "export_all_influences=True,export_skins=True,"; } - parameters_arg += "export_skins=True,"; } else { parameters_arg += "export_skins=False,"; } - if (p_options.has(SNAME("blender/materials/export_materials")) && p_options[SNAME("blender/materials/export_materials")]) { + if (p_options.has(SNAME("blender/materials/export_materials"))) { int32_t exports = p_options["blender/materials/export_materials"]; if (exports == BLEND_MATERIAL_EXPORT_PLACEHOLDER) { parameters_arg += "export_materials='PLACEHOLDER',"; @@ -115,7 +114,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_arg += "export_colors=False,"; } - if (p_options.has(SNAME("blender/nodes/visible")) && p_options[SNAME("blender/nodes/visible")]) { + if (p_options.has(SNAME("blender/nodes/visible"))) { int32_t visible = p_options["blender/nodes/visible"]; if (visible == BLEND_VISIBLE_VISIBLE_ONLY) { parameters_arg += "use_visible=True,"; diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h index a64dc4a80b..6fd38d9445 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.h +++ b/modules/gridmap/editor/grid_map_editor_plugin.h @@ -241,7 +241,7 @@ protected: void _notification(int p_what); public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } virtual String get_name() const override { return "GridMap"; } bool has_main_screen() const override { return false; } virtual void edit(Object *p_object) override; diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore index fa6d00cbbb..2d62f9f88a 100644 --- a/modules/mono/.gitignore +++ b/modules/mono/.gitignore @@ -1,2 +1,5 @@ # Do not ignore solution files inside the mono module. Overrides Godot's global gitignore. !*.sln + +# Generated by build_assemblies.py. +SdkPackageVersions.props diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props deleted file mode 100644 index 65094aa34f..0000000000 --- a/modules/mono/SdkPackageVersions.props +++ /dev/null @@ -1,8 +0,0 @@ -<Project> - <PropertyGroup> - <PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot> - <PackageVersion_GodotSharp>4.0.0-dev</PackageVersion_GodotSharp> - <PackageVersion_Godot_NET_Sdk>4.0.0-dev8</PackageVersion_Godot_NET_Sdk> - <PackageVersion_Godot_SourceGenerators>4.0.0-dev8</PackageVersion_Godot_SourceGenerators> - </PropertyGroup> -</Project> diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index d28c3a0c3a..7343af0b39 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -256,7 +256,57 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, flo return 0 +def generate_sdk_package_versions(): + # I can't believe importing files in Python is so convoluted when not + # following the golden standard for packages/modules. + import os + import sys + from os.path import dirname + + # We want ../../../methods.py. + script_path = dirname(os.path.abspath(__file__)) + root_path = dirname(dirname(dirname(script_path))) + + sys.path.insert(0, root_path) + from methods import get_version_info + + version_info = get_version_info("") + sys.path.remove(root_path) + + version_str = "{major}.{minor}.{patch}".format(**version_info) + version_status = version_info["status"] + if version_status != "stable": # Pre-release + # If version was overridden to be e.g. "beta3", we insert a dot between + # "beta" and "3" to follow SemVer 2.0. + import re + + match = re.search(r"[\d]+$", version_status) + if match: + pos = match.start() + version_status = version_status[:pos] + "." + version_status[pos:] + version_str += "-" + version_status + + props = """<Project> + <PropertyGroup> + <PackageVersion_GodotSharp>{0}</PackageVersion_GodotSharp> + <PackageVersion_Godot_NET_Sdk>{0}</PackageVersion_Godot_NET_Sdk> + <PackageVersion_Godot_SourceGenerators>{0}</PackageVersion_Godot_SourceGenerators> + </PropertyGroup> +</Project> +""".format( + version_str + ) + + # We write in ../SdkPackageVersions.props. + with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w") as f: + f.write(props) + f.close() + + def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, float_size): + # Generate SdkPackageVersions.props + generate_sdk_package_versions() + # Godot API exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size) if exit_code != 0: diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 97a1d5c8d8..345d2e4694 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2398,9 +2398,9 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { if (EngineDebugger::is_active()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + String(native_name) + - "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'."); } Callable::CallError unchecked_error; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 95a44d3b7e..d29e0d69ab 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -3326,11 +3326,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::PROJECTION: { - Projection transform = p_val.operator Projection(); - if (transform == Projection()) { + Projection projection = p_val.operator Projection(); + if (projection == Projection()) { r_iarg.default_argument = "Projection.Identity"; } else { - r_iarg.default_argument = "new Projection(new Vector4" + transform.matrix[0].operator String() + ", new Vector4" + transform.matrix[1].operator String() + ", new Vector4" + transform.matrix[2].operator String() + ", new Vector4" + transform.matrix[3].operator String() + ")"; + r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index d4469b110b..5ac167ad98 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -92,6 +92,7 @@ if env["vulkan"]: env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp new file mode 100644 index 0000000000..0f6aaf8afb --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* openxr_composition_layer_depth_extension.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 "openxr_composition_layer_depth_extension.h" + +OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::singleton = nullptr; + +OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::get_singleton() { + return singleton; +} + +OpenXRCompositionLayerDepthExtension::OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME] = &available; +} + +OpenXRCompositionLayerDepthExtension::~OpenXRCompositionLayerDepthExtension() { + singleton = nullptr; +} + +bool OpenXRCompositionLayerDepthExtension::is_available() { + return available; +} + +XrCompositionLayerBaseHeader *OpenXRCompositionLayerDepthExtension::get_composition_layer() { + // Seems this is all done in our base layer... Just in case this changes... + + return nullptr; +} diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.h b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h new file mode 100644 index 0000000000..9533783d83 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* openxr_composition_layer_depth_extension.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 OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H +#define OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H + +#include "openxr_composition_layer_provider.h" +#include "openxr_extension_wrapper.h" + +class OpenXRCompositionLayerDepthExtension : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { +public: + static OpenXRCompositionLayerDepthExtension *get_singleton(); + + OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRCompositionLayerDepthExtension() override; + + bool is_available(); + virtual XrCompositionLayerBaseHeader *get_composition_layer() override; + +private: + static OpenXRCompositionLayerDepthExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h index ba51389f7d..a4c4cbe0c6 100644 --- a/modules/openxr/extensions/openxr_composition_layer_provider.h +++ b/modules/openxr/extensions/openxr_composition_layer_provider.h @@ -31,6 +31,7 @@ #ifndef OPENXR_COMPOSITION_LAYER_PROVIDER_H #define OPENXR_COMPOSITION_LAYER_PROVIDER_H +#include "openxr_extension_wrapper.h" #include <openxr/openxr.h> // Interface for OpenXR extensions that provide a composition layer. diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 623c264e6e..c417c90d11 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -100,11 +100,12 @@ public: class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper { public: virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) = 0; + virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) = 0; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) = 0; - virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0; + virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) = 0; OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api){}; diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp index f9e771c934..ee5bda2881 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -228,6 +228,12 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usab p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_UINT); } +void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) { + p_usable_swap_chains.push_back(VK_FORMAT_R32_SFLOAT); + p_usable_swap_chains.push_back(VK_FORMAT_D24_UNORM_S8_UINT); + p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); +} + bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { XrSwapchainImageVulkanKHR *images = nullptr; @@ -271,7 +277,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; - uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT; switch (p_swapchain_format) { case VK_FORMAT_R8G8B8A8_SRGB: @@ -283,16 +289,32 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in // will thus do an sRGB -> Linear conversion as expected. // format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_B8G8R8A8_SRGB: // format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB; format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_R8G8B8A8_UINT: format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_B8G8R8A8_UINT: format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + break; + case VK_FORMAT_R32_SFLOAT: + format = RenderingDevice::DATA_FORMAT_R32_SFLOAT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + break; + case VK_FORMAT_D24_UNORM_S8_UINT: + format = RenderingDevice::DATA_FORMAT_D24_UNORM_S8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + break; + case VK_FORMAT_D32_SFLOAT_S8_UINT: + format = RenderingDevice::DATA_FORMAT_D32_SFLOAT_S8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; default: // continue with our default value @@ -328,8 +350,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in break; } - Vector<RID> image_rids; - Vector<RID> framebuffers; + Vector<RID> texture_rids; // create Godot texture objects for each entry in our swapchain for (uint64_t i = 0; i < swapchain_length; i++) { @@ -344,19 +365,10 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in 1, p_array_size); - image_rids.push_back(image_rid); - - { - Vector<RID> fb; - fb.push_back(image_rid); - - RID fb_rid = rendering_device->framebuffer_create(fb, RenderingDevice::INVALID_ID, p_array_size); - framebuffers.push_back(fb_rid); - } + texture_rids.push_back(image_rid); } - data->image_rids = image_rids; - data->framebuffers = framebuffers; + data->texture_rids = texture_rids; memfree(images); @@ -370,33 +382,19 @@ bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z for (int j = 0; j < 4; j++) { for (int i = 0; i < 4; i++) { - r_camera_matrix.matrix[j][i] = matrix.m[j * 4 + i]; + r_camera_matrix.columns[j][i] = matrix.m[j * 4 + i]; } } return true; } -bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) { +RID OpenXRVulkanExtension::get_texture(void *p_swapchain_graphics_data, int p_image_index) { SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; - ERR_FAIL_NULL_V(data, false); - ERR_FAIL_COND_V(p_from_render_target.is_null(), false); + ERR_FAIL_NULL_V(data, RID()); - RID source_image = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(p_from_render_target); - ERR_FAIL_COND_V(source_image.is_null(), false); - - RID depth_image; // TODO implement - - ERR_FAIL_INDEX_V(p_image_index, data->framebuffers.size(), false); - RID fb = data->framebuffers[p_image_index]; - ERR_FAIL_COND_V(fb.is_null(), false); - - // Our vulkan extension can only be used in conjunction with our vulkan renderer. - RendererRD::CopyEffects *copy_effects = RendererRD::CopyEffects::get_singleton(); - ERR_FAIL_NULL_V(copy_effects, false); - copy_effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); - - return true; + ERR_FAIL_INDEX_V(p_image_index, data->texture_rids.size(), RID()); + return data->texture_rids[p_image_index]; } void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { @@ -411,17 +409,11 @@ void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g RenderingDevice *rendering_device = rendering_server->get_rendering_device(); ERR_FAIL_NULL(rendering_device); - for (int i = 0; i < data->image_rids.size(); i++) { - // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain - rendering_device->free(data->image_rids[i]); - } - data->image_rids.clear(); - - for (int i = 0; i < data->framebuffers.size(); i++) { + for (int i = 0; i < data->texture_rids.size(); i++) { // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain - rendering_device->free(data->framebuffers[i]); + rendering_device->free(data->texture_rids[i]); } - data->framebuffers.clear(); + data->texture_rids.clear(); memdelete(data); *p_swapchain_graphics_data = nullptr; diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index d6e9917900..71abe3b166 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -69,11 +69,12 @@ public: virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override; virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override; - virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override; + virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override; private: static OpenXRVulkanExtension *singleton; @@ -81,8 +82,7 @@ private: struct SwapchainGraphicsData { bool is_multiview; - Vector<RID> image_rids; - Vector<RID> framebuffers; + Vector<RID> texture_rids; }; bool check_graphics_api_support(XrVersion p_desired_version); diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 16879ac4e5..01107ebcc9 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -49,6 +49,7 @@ #include "extensions/openxr_vulkan_extension.h" #endif +#include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" @@ -244,7 +245,7 @@ bool OpenXRAPI::create_instance() { if (!is_extension_supported(requested_extension.key)) { if (requested_extension.value == nullptr) { // nullptr means this is a manditory extension so we fail - ERR_FAIL_V_MSG(false, "OpenXR: OpenXR Runtime does not support OpenGL extension!"); + ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!")); } else { // set this extension as not supported *requested_extension.value = false; @@ -663,7 +664,7 @@ bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { return false; } -bool OpenXRAPI::create_main_swapchain() { +bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V(graphics_extension, false); ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -681,34 +682,36 @@ bool OpenXRAPI::create_main_swapchain() { already rendering the next frame. Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, - as we render 3D content into internal buffers that are copied into the swapchain, we don't get any of the performance gains - until such time as we implement VRS. + as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support */ - // Build a vector with swapchain formats we want to use, from best fit to worst - Vector<int64_t> usable_swapchain_formats; - int64_t swapchain_format_to_use = 0; + Size2 recommended_size = get_recommended_target_size(); - graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); + // We start with our color swapchain... + { + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; - // now find out which one is supported - for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { - if (is_swapchain_format_supported(usable_swapchain_formats[i])) { - swapchain_format_to_use = usable_swapchain_formats[i]; - } - } + graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); - if (swapchain_format_to_use == 0) { - swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... - print_line("Couldn't find usable swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); - } else { - print_line("Using swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); - } + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } - Size2 recommended_size = get_recommended_target_size(); + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable color swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_line("Using color swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); + } - if (!create_swapchain(swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchain, &swapchain_graphics_data)) { - return false; + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { + return false; + } } views = (XrView *)memalloc(sizeof(XrView) * view_count); @@ -717,18 +720,73 @@ bool OpenXRAPI::create_main_swapchain() { projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + // We create our depth swapchain if: + // - we support our depth layer extension + // - we have our spacewarp extension (not yet implemented) + if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; + + graphics_extension->get_usable_depth_formats(usable_swapchain_formats); + + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } + + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable depth swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_line("Using depth swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); + } + + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { + return false; + } + + depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + } + + // We create our velocity swapchain if: + // - we have our spacewarp extension (not yet implemented) + { + // TBD + } + for (uint32_t i = 0; i < view_count; i++) { views[i].type = XR_TYPE_VIEW; views[i].next = nullptr; projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; projection_views[i].next = nullptr; - projection_views[i].subImage.swapchain = swapchain; + projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; projection_views[i].subImage.imageArrayIndex = i; projection_views[i].subImage.imageRect.offset.x = 0; projection_views[i].subImage.imageRect.offset.y = 0; projection_views[i].subImage.imageRect.extent.width = recommended_size.width; projection_views[i].subImage.imageRect.extent.height = recommended_size.height; + + if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { + projection_views[i].next = &depth_views[i]; + + depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; + depth_views[i].next = nullptr; + depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; + depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageRect.offset.x = 0; + depth_views[i].subImage.imageRect.offset.y = 0; + depth_views[i].subImage.imageRect.extent.width = recommended_size.width; + depth_views[i].subImage.imageRect.extent.height = recommended_size.height; + depth_views[i].minDepth = 0.0; + depth_views[i].maxDepth = 1.0; + depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix + depth_views[i].farZ = 100.0; + } }; return true; @@ -740,7 +798,7 @@ void OpenXRAPI::destroy_session() { } if (graphics_extension) { - graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + graphics_extension->cleanup_swapchain_graphics_data(&swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data); } if (views != nullptr) { @@ -753,9 +811,16 @@ void OpenXRAPI::destroy_session() { projection_views = nullptr; } - if (swapchain != XR_NULL_HANDLE) { - xrDestroySwapchain(swapchain); - swapchain = XR_NULL_HANDLE; + if (depth_views != nullptr) { + memfree(depth_views); + depth_views = nullptr; + } + + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + if (swapchains[i].swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(swapchains[i].swapchain); + swapchains[i].swapchain = XR_NULL_HANDLE; + } } if (supported_swapchain_formats != nullptr) { @@ -789,7 +854,7 @@ void OpenXRAPI::destroy_session() { } } -bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { +bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); ERR_FAIL_NULL_V(graphics_extension, false); @@ -807,7 +872,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u XR_TYPE_SWAPCHAIN_CREATE_INFO, // type next_pointer, // next 0, // createFlags - XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags + p_usage_flags, // usageFlags p_swapchain_format, // format p_sample_count, // sampleCount p_width, // width @@ -871,7 +936,7 @@ bool OpenXRAPI::on_state_ready() { // That will be very very ugly // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module - if (!create_main_swapchain()) { + if (!create_swapchains()) { return false; } @@ -1304,6 +1369,15 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z return false; } + // if we're using depth views, make sure we update our near and far there... + if (depth_views != nullptr) { + for (uint32_t i = 0; i < view_count; i++) { + depth_views[i].nearZ = p_z_near; + depth_views[i].farZ = p_z_far; + } + } + + // now update our projection return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); } @@ -1442,15 +1516,15 @@ bool OpenXRAPI::process() { return true; } -bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) { - ERR_FAIL_COND_V(image_acquired, true); // this was not released when it should be, error out and re-use... +bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { + ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // this was not released when it should be, error out and re-use... XrResult result; XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type nullptr // next }; - result = xrAcquireSwapchainImage(p_swapchain, &swapchain_image_acquire_info, &r_image_index); + result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); if (XR_FAILED(result)) { print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); return false; @@ -1462,7 +1536,7 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) 17000000 // timeout in nanoseconds }; - result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info); + result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info); if (XR_FAILED(result)) { print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); return false; @@ -1471,12 +1545,12 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) return true; } -bool OpenXRAPI::release_image(XrSwapchain p_swapchain) { +bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) { XrSwapchainImageReleaseInfo swapchain_image_release_info = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type nullptr // next }; - XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + XrResult result = xrReleaseSwapchainImage(p_swapchain.swapchain, &swapchain_image_release_info); if (XR_FAILED(result)) { print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); return false; @@ -1590,28 +1664,41 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + // Acquire our images + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + if (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) { + if (!acquire_image(swapchains[i])) { + return false; + } + swapchains[i].image_acquired = true; + } + } + return true; } +RID OpenXRAPI::get_color_texture() { + if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { + return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index); + } else { + return RID(); + } +} + +RID OpenXRAPI::get_depth_texture() { + if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); + } else { + return RID(); + } +} + void OpenXRAPI::post_draw_viewport(RID p_render_target) { if (!can_render()) { return; } - // TODO: at some point in time we may support multiple viewports in which case we need to handle that... - - // TODO: if we can get PR 51179 to work properly we can change away from this approach and move this into get_external_texture or something - if (!image_acquired) { - if (!acquire_image(swapchain, image_index)) { - return; - } - image_acquired = true; - - // print_line("OpenXR: acquired image " + itos(image_index) + ", copying..."); - - // Copy our buffer into our swap chain (remove once PR 51179 is done) - graphics_extension->copy_render_target_to_image(p_render_target, swapchain_graphics_data, image_index); - } + // Nothing to do here at this point in time... }; void OpenXRAPI::end_frame() { @@ -1623,7 +1710,7 @@ void OpenXRAPI::end_frame() { return; } - if (frame_state.shouldRender && view_pose_valid && !image_acquired) { + if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); } @@ -1631,7 +1718,7 @@ void OpenXRAPI::end_frame() { // - shouldRender set to true // - a valid view pose for projection_views[eye].pose to submit layer // - an image to render - if (!frame_state.shouldRender || !view_pose_valid || !image_acquired) { + if (!frame_state.shouldRender || !view_pose_valid || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { // submit 0 layers when we shouldn't render XrFrameEndInfo frame_end_info = { XR_TYPE_FRAME_END_INFO, // type @@ -1652,10 +1739,12 @@ void OpenXRAPI::end_frame() { } // release our swapchain image if we acquired it - if (image_acquired) { - image_acquired = false; // whether we succeed or not, consider this released. + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + if (swapchains[i].image_acquired) { + swapchains[i].image_acquired = false; // whether we succeed or not, consider this released. - release_image(swapchain); + release_image(swapchains[i]); + } } for (uint32_t eye = 0; eye < view_count; eye++) { @@ -1763,6 +1852,7 @@ OpenXRAPI::OpenXRAPI() { // register our other extensions register_extension_wrapper(memnew(OpenXRPalmPoseExtension(this))); + register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension(this))); register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension(this))); register_extension_wrapper(memnew(OpenXRHandTrackingExtension(this))); register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper(this))); diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 316886239e..bd69432dcb 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -120,15 +120,28 @@ private: OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; XrSystemGraphicsProperties graphics_properties; - void *swapchain_graphics_data = nullptr; - uint32_t image_index = 0; - bool image_acquired = false; uint32_t view_count = 0; XrViewConfigurationView *view_configuration_views = nullptr; XrView *views = nullptr; XrCompositionLayerProjectionView *projection_views = nullptr; - XrSwapchain swapchain = XR_NULL_HANDLE; + XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available + + enum OpenXRSwapChainTypes { + OPENXR_SWAPCHAIN_COLOR, + OPENXR_SWAPCHAIN_DEPTH, + // OPENXR_SWAPCHAIN_VELOCITY, + OPENXR_SWAPCHAIN_MAX + }; + + struct OpenXRSwapChainInfo { + XrSwapchain swapchain = XR_NULL_HANDLE; + void *swapchain_graphics_data = nullptr; + uint32_t image_index = 0; + bool image_acquired = false; + }; + + OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; XrSpace play_space = XR_NULL_HANDLE; XrSpace view_space = XR_NULL_HANDLE; @@ -212,13 +225,13 @@ private: bool setup_spaces(); bool load_supported_swapchain_formats(); bool is_swapchain_format_supported(int64_t p_swapchain_format); - bool create_main_swapchain(); + bool create_swapchains(); void destroy_session(); // swapchains - bool create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); - bool acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index); - bool release_image(XrSwapchain p_swapchain); + bool create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); + bool acquire_image(OpenXRSwapChainInfo &p_swapchain); + bool release_image(OpenXRSwapChainInfo &p_swapchain); // action map struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. @@ -318,6 +331,8 @@ public: void pre_render(); bool pre_draw_viewport(RID p_render_target); + RID get_color_texture(); + RID get_depth_texture(); void post_draw_viewport(RID p_render_target); void end_frame(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 01e148e00f..31dc2bbf43 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -648,6 +648,22 @@ Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_as return cm; } +RID OpenXRInterface::get_color_texture() { + if (openxr_api) { + return openxr_api->get_color_texture(); + } else { + return RID(); + } +} + +RID OpenXRInterface::get_depth_texture() { + if (openxr_api) { + return openxr_api->get_depth_texture(); + } else { + return RID(); + } +} + void OpenXRInterface::process() { if (openxr_api) { // do our normal process @@ -707,6 +723,7 @@ bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; +#ifndef ANDROID_ENABLED // If separate HMD we should output one eye to screen if (p_screen_rect != Rect2()) { BlitToScreen blit; @@ -732,6 +749,7 @@ Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, co blit.dst_rect = dst_rect; blit_to_screen.push_back(blit); } +#endif if (openxr_api) { openxr_api->post_draw_viewport(p_render_target); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 3765f18637..72935b039c 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -126,6 +126,9 @@ public: virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + virtual RID get_color_texture() override; + virtual RID get_depth_texture() override; + virtual void process() override; virtual void pre_render() override; bool pre_draw_viewport(RID p_render_target) override; diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 7ca8859ee6..6220e35b54 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -68,7 +68,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]: env.Append(CPPDEFINES=["MODULE_MSDFGEN_ENABLED"]) lib = env_msdfgen.Library( - f'msdfgen_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'msdfgen_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_msdfgen_sources, ) env.Append(LIBS=[lib]) @@ -199,7 +199,7 @@ if env["freetype_enabled"]: env.Append(CPPDEFINES=["MODULE_FREETYPE_ENABLED"]) lib = env_freetype.Library( - f'freetype_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'freetype_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_freetype_sources, ) env.Append(LIBS=[lib]) @@ -321,7 +321,7 @@ if env["freetype_enabled"]: env.Append(CPPPATH=["../../../thirdparty/harfbuzz/src"]) lib = env_harfbuzz.Library( - f'harfbuzz_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'harfbuzz_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_harfbuzz_sources, ) env.Prepend(LIBS=[lib]) @@ -381,7 +381,7 @@ if env["graphite_enabled"] and env["freetype_enabled"]: ) lib = env_graphite.Library( - f'graphite_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'graphite_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_graphite_sources, ) env.Append(LIBS=[lib]) @@ -640,9 +640,7 @@ env.Append(CPPPATH=["../../../thirdparty/icu4c/common/", "../../../thirdparty/ic if env["platform"] == "windows": env.Append(LIBS=["advapi32"]) -lib = env_icu.Library( - f'icu_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', thirdparty_icu_sources -) +lib = env_icu.Library(f'icu_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_icu_sources) env.Append(LIBS=[lib]) env.Append(CPPDEFINES=["GDEXTENSION"]) @@ -662,7 +660,7 @@ if env["platform"] == "macos": ) else: library = env.SharedLibrary( - f'./bin/libtextserver_advanced.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["SHLIBSUFFIX"]}', + f'./bin/libtextserver_advanced{env["suffix"]}{env["SHLIBSUFFIX"]}', source=sources, ) diff --git a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension index 11ed271ae9..c12fcfdfdf 100644 --- a/modules/text_server_adv/gdextension_build/text_server_adv.gdextension +++ b/modules/text_server_adv/gdextension_build/text_server_adv.gdextension @@ -4,9 +4,21 @@ entry_symbol = "textserver_advanced_init" [libraries] -linux.64.debug = "bin/libtextserver_advanced.linux.debug.64.so" -linux.64.release = "bin/libtextserver_advanced.linux.release.64.so" -windows.64.debug = "bin/libtextserver_advanced.windows.debug.64.dll" -windows.64.release = "bin/libtextserver_advanced.windows.release.64.dll" -macos.debug = "bin/libtextserver_advanced.macos.debug.framework" -macos.release = "bin/libtextserver_advanced.macos.release.framework" +linux.x86_64.debug = "bin/libtextserver_advanced.linux.template_debug.x86_64.so" +linux.x86_64.release = "bin/libtextserver_advanced.linux.template_release.x86_64.so" +linux.x86_32.debug = "bin/libtextserver_advanced.linux.template_debug.x86_32.so" +linux.x86_32.release = "bin/libtextserver_advanced.linux.template_release.x86_32.so" +linux.arm64.debug = "bin/libtextserver_advanced.linux.template_debug.arm64.so" +linux.arm64.release = "bin/libtextserver_advanced.linux.template_release.arm64.so" +linux.rv64.debug = "bin/libtextserver_advanced.linux.template_debug.rv64.so" +linux.rv64.release = "bin/libtextserver_advanced.linux.template_release.rv64.so" + +windows.x86_64.debug = "bin/libtextserver_advanced.windows.template_debug.x86_64.dll" +windows.x86_64.release = "bin/libtextserver_advanced.windows.template_release.x86_64.dll" +windows.x86_32.debug = "bin/libtextserver_advanced.windows.template_debug.x86_32.dll" +windows.x86_32.release = "bin/libtextserver_advanced.windows.template_release.x86_32.dll" +windows.arm64.debug = "bin/libtextserver_advanced.windows.template_debug.arm64.dll" +windows.arm64.release = "bin/libtextserver_advanced.windows.template_release.arm64.dll" + +macos.debug = "bin/libtextserver_advanced.macos.template_debug.framework" +macos.release = "bin/libtextserver_advanced.macos.template_release.framework" diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index eae8e031bf..8ed8f61a43 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -63,7 +63,7 @@ if env["msdfgen_enabled"] and env["freetype_enabled"]: env.Append(CPPDEFINES=["MODULE_MSDFGEN_ENABLED"]) lib = env_msdfgen.Library( - f'msdfgen_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'msdfgen_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_msdfgen_sources, ) env.Append(LIBS=[lib]) @@ -194,7 +194,7 @@ if env["freetype_enabled"]: env.Append(CPPDEFINES=["MODULE_FREETYPE_ENABLED"]) lib = env_freetype.Library( - f'freetype_builtin.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["LIBSUFFIX"]}', + f'freetype_builtin{env["suffix"]}{env["LIBSUFFIX"]}', thirdparty_freetype_sources, ) env.Append(LIBS=[lib]) @@ -217,7 +217,7 @@ if env["platform"] == "macos": ) else: library = env.SharedLibrary( - f'./bin/libtextserver_fallback.{env["platform"]}.{env["target"]}.{env["arch_suffix"]}{env["SHLIBSUFFIX"]}', + f'./bin/libtextserver_fallback{env["suffix"]}{env["SHLIBSUFFIX"]}', source=sources, ) diff --git a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension index 9236555d63..58a92e403b 100644 --- a/modules/text_server_fb/gdextension_build/text_server_fb.gdextension +++ b/modules/text_server_fb/gdextension_build/text_server_fb.gdextension @@ -4,9 +4,21 @@ entry_symbol = "textserver_fallback_init" [libraries] -linux.64.debug = "bin/libtextserver_fallback.linux.debug.64.so" -linux.64.release = "bin/libtextserver_fallback.linux.release.64.so" -windows.64.debug = "bin/libtextserver_fallback.windows.debug.64.dll" -windows.64.release = "bin/libtextserver_fallback.windows.release.64.dll" -macos.debug = "bin/libtextserver_fallback.macos.debug.framework" -macos.release = "bin/libtextserver_fallback.macos.release.framework" +linux.x86_64.debug = "bin/libtextserver_fallback.linux.template_debug.x86_64.so" +linux.x86_64.release = "bin/libtextserver_fallback.linux.template_release.x86_64.so" +linux.x86_32.debug = "bin/libtextserver_fallback.linux.template_debug.x86_32.so" +linux.x86_32.release = "bin/libtextserver_fallback.linux.template_release.x86_32.so" +linux.arm64.debug = "bin/libtextserver_fallback.linux.template_debug.arm64.so" +linux.arm64.release = "bin/libtextserver_fallback.linux.template_release.arm64.so" +linux.rv64.debug = "bin/libtextserver_fallback.linux.template_debug.rv64.so" +linux.rv64.release = "bin/libtextserver_fallback.linux.template_release.rv64.so" + +windows.x86_64.debug = "bin/libtextserver_fallback.windows.template_debug.x86_64.dll" +windows.x86_64.release = "bin/libtextserver_fallback.windows.template_release.x86_64.dll" +windows.x86_32.debug = "bin/libtextserver_fallback.windows.template_debug.x86_32.dll" +windows.x86_32.release = "bin/libtextserver_fallback.windows.template_release.x86_32.dll" +windows.arm64.debug = "bin/libtextserver_fallback.windows.template_debug.arm64.dll" +windows.arm64.release = "bin/libtextserver_fallback.windows.template_release.arm64.dll" + +macos.debug = "bin/libtextserver_fallback.macos.template_debug.framework" +macos.release = "bin/libtextserver_fallback.macos.template_release.framework" diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 3efb653651..9e096ec8b7 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -76,7 +76,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback { th_info ti; th_comment tc; th_dec_ctx *td = nullptr; - vorbis_info vi; + vorbis_info vi = {}; vorbis_dsp_state vd; vorbis_block vb; vorbis_comment vc; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index d0c7484aa1..6b671c1660 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -388,15 +388,15 @@ Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_a int k = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - eye.matrix[i][j] = js_matrix[k++]; + eye.columns[i][j] = js_matrix[k++]; } } free(js_matrix); // Copied from godot_oculus_mobile's ovr_mobile_session.cpp - eye.matrix[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near); - eye.matrix[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near); + eye.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near); + eye.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near); return eye; } diff --git a/platform/android/SCsub b/platform/android/SCsub index 9f5b8d8387..e4d04f1df9 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -53,10 +53,10 @@ else: print("WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin") if lib_arch_dir != "": - if env.debug_features: - lib_type_dir = "debug" - elif env.dev_build: + if env.dev_build: lib_type_dir = "dev" + elif env.debug_features: + lib_type_dir = "debug" else: # Release lib_type_dir = "release" diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index d3bce12de1..059c296cb7 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -580,6 +580,9 @@ void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) { } void DisplayServerAndroid::mouse_set_mode(MouseMode p_mode) { + if (!OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_update_pointer_icon() || !OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_capture_pointer()) { + return; + } if (mouse_mode == p_mode) { return; } @@ -612,6 +615,9 @@ MouseButton DisplayServerAndroid::mouse_get_button_state() const { } void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) { + if (!OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_update_pointer_icon()) { + return; + } if (cursor_shape == p_shape) { return; } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 81c7130c03..05608883d7 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -29,8 +29,13 @@ allprojects { ext { supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"] - supportedTargetsMap = [release: "release", dev: "debug", debug: "release_debug"] supportedFlavors = ["editor", "template"] + supportedFlavorsBuildTypes = [ + // The editor can't be used with target=release as debugging tools are then not + // included, and it would crash on errors instead of reporting them. + "editor": ["dev", "debug"], + "template": ["dev", "debug", "release"] + ] // Used by gradle to specify which architecture to build for by default when running // `./gradlew build` (this command is usually used by Android Studio). @@ -88,7 +93,7 @@ task copyDebugAARToAppModule(type: Copy) { dependsOn ':lib:assembleTemplateDebug' from('lib/build/outputs/aar') into('app/libs/debug') - include('godot-lib.debug.aar') + include('godot-lib.template_debug.aar') } /** @@ -99,7 +104,7 @@ task copyDebugAARToBin(type: Copy) { dependsOn ':lib:assembleTemplateDebug' from('lib/build/outputs/aar') into(binDir) - include('godot-lib.debug.aar') + include('godot-lib.template_debug.aar') } /** @@ -110,7 +115,7 @@ task copyDevAARToAppModule(type: Copy) { dependsOn ':lib:assembleTemplateDev' from('lib/build/outputs/aar') into('app/libs/dev') - include('godot-lib.dev.aar') + include('godot-lib.template_debug.dev.aar') } /** @@ -121,7 +126,7 @@ task copyDevAARToBin(type: Copy) { dependsOn ':lib:assembleTemplateDev' from('lib/build/outputs/aar') into(binDir) - include('godot-lib.dev.aar') + include('godot-lib.template_debug.dev.aar') } /** @@ -132,7 +137,7 @@ task copyReleaseAARToAppModule(type: Copy) { dependsOn ':lib:assembleTemplateRelease' from('lib/build/outputs/aar') into('app/libs/release') - include('godot-lib.release.aar') + include('godot-lib.template_release.aar') } /** @@ -143,7 +148,7 @@ task copyReleaseAARToBin(type: Copy) { dependsOn ':lib:assembleTemplateRelease' from('lib/build/outputs/aar') into(binDir) - include('godot-lib.release.aar') + include('godot-lib.template_release.aar') } /** @@ -168,13 +173,8 @@ def templateExcludedBuildTask() { if (!isAndroidStudio()) { logger.lifecycle("Excluding Android studio build tasks") for (String flavor : supportedFlavors) { - for (String buildType : supportedTargetsMap.keySet()) { - if (buildType == "release" && flavor == "editor") { - // The editor can't be used with target=release as debugging tools are then not - // included, and it would crash on errors instead of reporting them. - continue - } - + String[] supportedBuildTypes = supportedFlavorsBuildTypes[flavor] + for (String buildType : supportedBuildTypes) { for (String abi : selectedAbis) { excludedTasks += ":lib:" + getSconsTaskName(flavor, buildType, abi) } @@ -188,7 +188,7 @@ def templateBuildTasks() { def tasks = [] // Only build the apks and aar files for which we have native shared libraries. - for (String target : supportedTargetsMap.keySet()) { + for (String target : supportedFlavorsBuildTypes["template"]) { File targetLibs = new File("lib/libs/" + target) if (targetLibs != null && targetLibs.isDirectory() @@ -240,12 +240,7 @@ task generateGodotEditor { def tasks = [] - for (String target : supportedTargetsMap.keySet()) { - if (target == "release") { - // The editor can't be used with target=release as debugging tools are then not - // included, and it would crash on errors instead of reporting them. - continue - } + for (String target : supportedFlavorsBuildTypes["editor"]) { File targetLibs = new File("lib/libs/tools/" + target) if (targetLibs != null && targetLibs.isDirectory() @@ -322,9 +317,9 @@ task cleanGodotTemplates(type: Delete) { delete("$binDir/android_dev.apk") delete("$binDir/android_release.apk") delete("$binDir/android_source.zip") - delete("$binDir/godot-lib.debug.aar") - delete("$binDir/godot-lib.dev.aar") - delete("$binDir/godot-lib.release.aar") + delete("$binDir/godot-lib.template_debug.aar") + delete("$binDir/godot-lib.template_debug.dev.aar") + delete("$binDir/godot-lib.template_release.aar") finalizedBy getTasksByName("clean", true) } diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 318ae1143f..c9e2a5d7d2 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -100,25 +100,34 @@ android { throw new GradleException("Invalid product flavor: $flavorName") } - boolean toolsFlag = flavorName == "editor" - def buildType = variant.buildType.name - if (buildType == null || buildType == "" || !supportedTargetsMap.containsKey(buildType)) { + if (buildType == null || buildType == "" || !supportedFlavorsBuildTypes[flavorName].contains(buildType)) { throw new GradleException("Invalid build type: $buildType") } - def sconsTarget = supportedTargetsMap[buildType] - if (sconsTarget == null || sconsTarget == "") { - throw new GradleException("Invalid scons target: $sconsTarget") + boolean devBuild = buildType == "dev" + + def sconsTarget = flavorName + if (sconsTarget == "template") { + switch (buildType) { + case "release": + sconsTarget += "_release" + break + case "debug": + case "dev": + default: + sconsTarget += "_debug" + break; + } } // Update the name of the generated library - def outputSuffix = "${buildType}.aar" - if (toolsFlag) { - outputSuffix = "tools.$outputSuffix" + def outputSuffix = "${sconsTarget}" + if (devBuild) { + outputSuffix = "${outputSuffix}.dev" } variant.outputs.all { output -> - output.outputFileName = "godot-lib.${outputSuffix}" + output.outputFileName = "godot-lib.${outputSuffix}.aar" } // Find scons' executable path @@ -159,7 +168,7 @@ android { def taskName = getSconsTaskName(flavorName, buildType, selectedAbi) tasks.create(name: taskName, type: Exec) { executable sconsExecutableFile.absolutePath - args "--directory=${pathToRootDir}", "platform=android", "tools=${toolsFlag}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() + args "--directory=${pathToRootDir}", "platform=android", "dev_mode=${devBuild}", "dev_build=${devBuild}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() } // Schedule the tasks so the generated libs are present before the aar file is packaged. diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 513021f1d1..3dfc37f6b0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -74,7 +74,6 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView private final Godot godot; private final GodotInputHandler inputHandler; private final GodotRenderer godotRenderer; - private PointerIcon pointerIcon; public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) { super(context); @@ -84,7 +83,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView this.inputHandler = new GodotInputHandler(this); this.godotRenderer = new GodotRenderer(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); + setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } init(xrMode, false); } @@ -175,13 +174,16 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Keep public void setPointerIcon(int pointerType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); + setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType)); } } @Override public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { - return pointerIcon; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return getPointerIcon(); + } + return super.onResolvePointerIcon(me, pointerIndex); } private void init(XRMode xrMode, boolean translucent) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index fa6c3280b9..0becf00d93 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -48,7 +48,6 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV private final Godot godot; private final GodotInputHandler mInputHandler; private final VkRenderer mRenderer; - private PointerIcon pointerIcon; public GodotVulkanRenderView(Context context, Godot godot) { super(context); @@ -57,7 +56,7 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV mInputHandler = new GodotInputHandler(this); mRenderer = new VkRenderer(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); + setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } setFocusableInTouchMode(true); startRenderer(mRenderer); @@ -149,13 +148,16 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Keep public void setPointerIcon(int pointerType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); + setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType)); } } @Override public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { - return pointerIcon; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return getPointerIcon(); + } + return super.onResolvePointerIcon(me, pointerIndex); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index c959b5f28c..01ad5ee415 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -122,7 +122,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) { - if (mEdit == pTextView && isFullScreenEdit()) { + if (mEdit == pTextView && isFullScreenEdit() && pKeyEvent != null) { final String characters = pKeyEvent.getCharacters(); for (int i = 0; i < characters.length(); i++) { diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index 0153ba96fc..762840a4b1 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -40,13 +40,24 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { _cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_view)); - if (android_get_device_api_level() >= __ANDROID_API_O__) { + int android_device_api_level = android_get_device_api_level(); + if (android_device_api_level >= __ANDROID_API_N__) { + _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V"); + } + if (android_device_api_level >= __ANDROID_API_O__) { _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V"); _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V"); - _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V"); } } +bool GodotJavaViewWrapper::can_update_pointer_icon() const { + return _set_pointer_icon != nullptr; +} + +bool GodotJavaViewWrapper::can_capture_pointer() const { + return _request_pointer_capture != nullptr && _release_pointer_capture != nullptr; +} + void GodotJavaViewWrapper::request_pointer_capture() { if (_request_pointer_capture != nullptr) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index c52f459d64..b398c73cac 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -50,6 +50,9 @@ private: public: GodotJavaViewWrapper(jobject godot_view); + bool can_update_pointer_icon() const; + bool can_capture_pointer() const; + void request_pointer_capture(); void release_pointer_capture(); void set_pointer_icon(int pointer_type); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 07b0d75921..416b98c895 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -78,13 +78,23 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)V"); + _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); // get some Activity method pointers... _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); } GodotJavaWrapper::~GodotJavaWrapper() { - // nothing to do here for now + if (godot_view) { + delete godot_view; + } + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + env->DeleteGlobalRef(godot_instance); + env->DeleteGlobalRef(godot_class); + env->DeleteGlobalRef(activity); + env->DeleteGlobalRef(activity_class); } jobject GodotJavaWrapper::get_activity() { @@ -115,14 +125,18 @@ jobject GodotJavaWrapper::get_class_loader() { } GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { - if (_godot_view != nullptr) { - return _godot_view; + if (godot_view != nullptr) { + return godot_view; } - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, nullptr); - jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); - _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); - return _godot_view; + if (_get_render_view) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, nullptr); + jobject godot_render_view = env->CallObjectMethod(godot_instance, _get_render_view); + if (!env->IsSameObject(godot_render_view, nullptr)) { + godot_view = new GodotJavaViewWrapper(godot_render_view); + } + } + return godot_view; } bool GodotJavaWrapper::on_video_init(JNIEnv *p_env) { diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 0e96a4e1f3..fb9c4c77fc 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -46,7 +46,7 @@ private: jclass godot_class; jclass activity_class; - GodotJavaViewWrapper *_godot_view = nullptr; + GodotJavaViewWrapper *godot_view = nullptr; jmethodID _on_video_init = nullptr; jmethodID _restart = nullptr; @@ -69,6 +69,7 @@ private: jmethodID _on_godot_main_loop_started = nullptr; jmethodID _get_class_loader = nullptr; jmethodID _create_new_godot_instance = nullptr; + jmethodID _get_render_view = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index 61b471866f..9f87303341 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -63,7 +63,7 @@ static void term_thread() { void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { java_vm = p_jvm; env = p_env; - Thread::_set_platform_funcs(nullptr, nullptr, &init_thread, &term_thread); + Thread::_set_platform_functions({ .init = init_thread, .term = &term_thread }); } void setup_android_thread() { diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index 33da094860..8c8c8588b8 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -89,7 +89,7 @@ static void handle_crash(int sig) { // Try to demangle the function name to provide a more readable one if (dladdr(bt_buffer[i], &info) && info.dli_sname) { if (info.dli_sname[0] == '_') { - int status; + int status = 0; char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); if (status == 0 && demangled) { diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index dfde0d249c..86ae1f2d9c 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -363,8 +363,10 @@ def configure(env: "Environment"): if platform.system() == "Linux": env.Append(LIBS=["dl"]) - if platform.system().find("BSD") >= 0: - env["execinfo"] = True + if not env["execinfo"] and platform.libc_ver()[0] != "glibc": + # The default crash handler depends on glibc, so if the host uses + # a different libc (BSD libc, musl), fall back to libexecinfo. + print("Note: Using `execinfo=yes` for the crash handler as required on platforms where glibc is missing.") if env["execinfo"]: env.Append(LIBS=["execinfo"]) diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 1ef1deb971..5256d91a80 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -3142,7 +3142,7 @@ void DisplayServerX11::_window_changed(XEvent *event) { { //the position in xconfigure is not useful here, obtain it manually - int x, y; + int x = 0, y = 0; Window child; XTranslateCoordinates(x11_display, wd.x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); new_rect.position.x = x; @@ -3305,7 +3305,7 @@ void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) { XFlush(x11_display); // Non-blocking wait for next event and remove it from the queue. - XEvent ev; + XEvent ev = {}; while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { // Check if the input manager wants to process the event. if (XFilterEvent(&ev, None)) { diff --git a/platform/web/package-lock.json b/platform/web/package-lock.json index f8c67b206f..9a7d871c64 100644 --- a/platform/web/package-lock.json +++ b/platform/web/package-lock.json @@ -12,8 +12,7 @@ "eslint": "^7.28.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.23.4", - "jsdoc": "^3.6.7", - "serve": "^13.0.2" + "jsdoc": "^3.6.7" } }, "node_modules/@babel/code-frame": { @@ -131,25 +130,6 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, - "node_modules/@zeit/schemas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", - "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -187,15 +167,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -226,32 +197,6 @@ "node": ">=4" } }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/arg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", - "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", - "dev": true - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -318,28 +263,6 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -350,15 +273,6 @@ "concat-map": "0.0.1" } }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -381,18 +295,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -475,32 +377,6 @@ "node": ">=8" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", - "dev": true, - "dependencies": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -516,51 +392,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.14", - "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -573,15 +404,6 @@ "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", "dev": true }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -613,15 +435,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -658,15 +471,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -1075,91 +879,6 @@ "node": ">=0.10.0" } }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1178,21 +897,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", - "dev": true, - "dependencies": { - "punycode": "^1.3.2" - } - }, - "node_modules/fast-url-parser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1268,18 +972,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -1431,12 +1123,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1503,21 +1189,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1588,15 +1259,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-string": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", @@ -1624,18 +1286,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1883,27 +1533,6 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1946,21 +1575,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -1982,27 +1596,6 @@ "semver": "bin/semver" } }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/object-inspect": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", @@ -2070,15 +1663,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2105,15 +1689,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -2190,12 +1765,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2211,12 +1780,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", - "dev": true - }, "node_modules/path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -2280,16 +1843,6 @@ "node": ">=0.4.0" } }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2299,39 +1852,6 @@ "node": ">=6" } }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -2371,28 +1891,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dev": true, - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -2448,12 +1946,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -2469,86 +1961,6 @@ "node": ">=10" } }, - "node_modules/serve": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", - "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", - "dev": true, - "dependencies": { - "@zeit/schemas": "2.6.0", - "ajv": "6.12.6", - "arg": "2.0.0", - "boxen": "5.1.2", - "chalk": "2.4.1", - "clipboardy": "2.3.0", - "compression": "1.7.3", - "serve-handler": "6.1.3", - "update-check": "1.5.2" - }, - "bin": { - "serve": "bin/serve.js" - } - }, - "node_modules/serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", - "dev": true, - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.0.4", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve/node_modules/chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2570,12 +1982,6 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "dev": true - }, "node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -2725,15 +2131,6 @@ "node": ">=4" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2872,16 +2269,6 @@ "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, - "node_modules/update-check": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", - "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", - "dev": true, - "dependencies": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2907,15 +2294,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2947,18 +2325,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -2968,56 +2334,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3134,22 +2450,6 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, - "@zeit/schemas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", - "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -3175,15 +2475,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3205,18 +2496,6 @@ "color-convert": "^1.9.0" } }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true - }, - "arg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", - "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3268,22 +2547,6 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3294,12 +2557,6 @@ "concat-map": "0.0.1" } }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -3316,12 +2573,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, "catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -3382,23 +2633,6 @@ } } }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, - "clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", - "dev": true, - "requires": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3414,47 +2648,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.14", - "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3467,12 +2660,6 @@ "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", "dev": true }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3493,12 +2680,6 @@ "ms": "2.1.2" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -3529,15 +2710,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -3862,72 +3034,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3946,23 +3052,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", - "dev": true, - "requires": { - "punycode": "^1.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4026,15 +3115,6 @@ "has-symbols": "^1.0.1" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -4144,12 +3224,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4192,12 +3266,6 @@ "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", "dev": true }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4241,12 +3309,6 @@ "has-symbols": "^1.0.2" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, "is-string": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", @@ -4262,15 +3324,6 @@ "has-symbols": "^1.0.2" } }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4480,21 +3533,6 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "requires": { - "mime-db": "1.51.0" - } - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -4528,18 +3566,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -4560,23 +3586,6 @@ } } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - } - } - }, "object-inspect": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", @@ -4623,12 +3632,6 @@ "es-abstract": "^1.18.2" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4652,12 +3655,6 @@ "word-wrap": "^1.2.3" } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -4713,12 +3710,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4731,12 +3722,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", - "dev": true - }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -4782,48 +3767,12 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -4851,25 +3800,6 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dev": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4910,12 +3840,6 @@ "glob": "^7.1.3" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -4925,75 +3849,6 @@ "lru-cache": "^6.0.0" } }, - "serve": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", - "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", - "dev": true, - "requires": { - "@zeit/schemas": "2.6.0", - "ajv": "6.12.6", - "arg": "2.0.0", - "boxen": "5.1.2", - "chalk": "2.4.1", - "clipboardy": "2.3.0", - "compression": "1.7.3", - "serve-handler": "6.1.3", - "update-check": "1.5.2" - }, - "dependencies": { - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - } - } - }, - "serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.0.4", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "~1.33.0" - } - } - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5009,12 +3864,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", - "dev": true - }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -5136,12 +3985,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -5254,16 +4097,6 @@ "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, - "update-check": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", - "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", - "dev": true, - "requires": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5289,12 +4122,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5317,58 +4144,12 @@ "is-symbol": "^1.0.3" } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/platform/web/package.json b/platform/web/package.json index 0a8d9e4334..8d4983663b 100644 --- a/platform/web/package.json +++ b/platform/web/package.json @@ -14,8 +14,7 @@ "format:engine": "npm run lint:engine -- --fix", "format:libs": "npm run lint:libs -- --fix", "format:modules": "npm run lint:modules -- --fix", - "format:tools": "npm run lint:tools -- --fix", - "serve": "serve" + "format:tools": "npm run lint:tools -- --fix" }, "author": "Godot Engine contributors", "license": "MIT", @@ -23,7 +22,6 @@ "eslint": "^7.28.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.23.4", - "jsdoc": "^3.6.7", - "serve": "^13.0.2" + "jsdoc": "^3.6.7" } } diff --git a/platform/web/serve.json b/platform/web/serve.json deleted file mode 100644 index f2ef24751f..0000000000 --- a/platform/web/serve.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "public": "../../bin", - "headers": [{ - "source": "**/*", - "headers": [ - { - "key": "Cross-Origin-Embedder-Policy", - "value": "require-corp" - }, { - "key": "Cross-Origin-Opener-Policy", - "value": "same-origin" - }, { - "key": "Access-Control-Allow-Origin", - "value": "*" - }, { - "key": "Cache-Control", - "value": "no-store, max-age=0" - } - ] - }] -} diff --git a/platform/web/serve.py b/platform/web/serve.py new file mode 100755 index 0000000000..14e87e9ea1 --- /dev/null +++ b/platform/web/serve.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore +from pathlib import Path +import os +import sys +import argparse +import subprocess + + +class CORSRequestHandler(SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + self.send_header("Access-Control-Allow-Origin", "*") + super().end_headers() + + +def shell_open(url): + if sys.platform == "win32": + os.startfile(url) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, url]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--port", help="port to listen on", default=8060, type=int) + parser.add_argument( + "-r", "--root", help="path to serve as root (relative to `platform/web/`)", default="../../bin", type=Path + ) + browser_parser = parser.add_mutually_exclusive_group(required=False) + browser_parser.add_argument( + "-n", "--no-browser", help="don't open default web browser automatically", dest="browser", action="store_false" + ) + parser.set_defaults(browser=True) + args = parser.parse_args() + + # Change to the directory where the script is located, + # so that the script can be run from any location. + os.chdir(Path(__file__).resolve().parent) + + if args.root: + os.chdir(args.root) + + if args.browser: + # Open the served page in the user's default browser. + print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).") + shell_open(f"http://127.0.0.1:{args.port}") + + test(CORSRequestHandler, HTTPServer, port=args.port) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 9e958be26f..22a562c21c 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1361,7 +1361,8 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W if (p_enabled) { //enable per-pixel alpha - DWM_BLURBEHIND bb = { 0 }; + DWM_BLURBEHIND bb; + ZeroMemory(&bb, sizeof(bb)); HRGN hRgn = CreateRectRgn(0, 0, -1, -1); bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; bb.hRgnBlur = hRgn; @@ -1373,7 +1374,8 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W //disable per-pixel alpha wd.layered_window = false; - DWM_BLURBEHIND bb = { 0 }; + DWM_BLURBEHIND bb; + ZeroMemory(&bb, sizeof(bb)); HRGN hRgn = CreateRectRgn(0, 0, -1, -1); bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; bb.hRgnBlur = hRgn; @@ -1390,7 +1392,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); wd.is_popup = p_enabled; } break; - case WINDOW_FLAG_MAX: + default: break; } } @@ -1419,7 +1421,7 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window case WINDOW_FLAG_POPUP: { return wd.is_popup; } break; - case WINDOW_FLAG_MAX: + default: break; } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 1978ec5ab6..241e0b382e 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -295,11 +295,12 @@ String OS_Windows::get_distribution_name() const { } String OS_Windows::get_version() const { - typedef LONG NTSTATUS, *PNTSTATUS; + typedef LONG NTSTATUS; typedef NTSTATUS(WINAPI * RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); RtlGetVersionPtr version_ptr = (RtlGetVersionPtr)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlGetVersion"); if (version_ptr != nullptr) { - RTL_OSVERSIONINFOW fow = { 0 }; + RTL_OSVERSIONINFOW fow; + ZeroMemory(&fow, sizeof(fow)); fow.dwOSVersionInfoSize = sizeof(fow); if (version_ptr(&fow) == 0x00000000) { return vformat("%d.%d.%d", (int64_t)fow.dwMajorVersion, (int64_t)fow.dwMinorVersion, (int64_t)fow.dwBuildNumber); @@ -870,17 +871,6 @@ BOOL is_wow64() { return wow64; } -int OS_Windows::get_processor_count() const { - SYSTEM_INFO sysinfo; - if (is_wow64()) { - GetNativeSystemInfo(&sysinfo); - } else { - GetSystemInfo(&sysinfo); - } - - return sysinfo.dwNumberOfProcessors; -} - String OS_Windows::get_processor_name() const { const String id = "Hardware\\Description\\System\\CentralProcessor\\0"; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 419871f8bf..b792f6fa44 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -175,7 +175,6 @@ public: virtual String get_locale() const override; - virtual int get_processor_count() const override; virtual String get_processor_name() const override; virtual uint64_t get_embedded_pck_offset() const override; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index e120aa871b..aeaaaf3aec 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -171,9 +171,14 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2()); - real_t angle = get_global_rotation(); if (!ignore_rotation) { - screen_offset = screen_offset.rotated(angle); + if (rotation_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { + real_t step = rotation_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + camera_angle = Math::lerp_angle(camera_angle, get_global_rotation(), step); + } else { + camera_angle = get_global_rotation(); + } + screen_offset = screen_offset.rotated(camera_angle); } Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom_scale); @@ -205,7 +210,7 @@ Transform2D Camera2D::get_camera_transform() { Transform2D xform; xform.scale_basis(zoom_scale); if (!ignore_rotation) { - xform.set_rotation(angle); + xform.set_rotation(camera_angle); } xform.set_origin(screen_rect.position); @@ -366,6 +371,12 @@ Camera2D::AnchorMode Camera2D::get_anchor_mode() const { void Camera2D::set_ignore_rotation(bool p_ignore) { ignore_rotation = p_ignore; Point2 old_smoothed_camera_pos = smoothed_camera_pos; + + // Reset back to zero so it matches the camera rotation when ignore_rotation is enabled. + if (ignore_rotation) { + camera_angle = 0.0; + } + _update_scroll(); smoothed_camera_pos = old_smoothed_camera_pos; } @@ -415,6 +426,14 @@ void Camera2D::set_current(bool p_current) { } } +void Camera2D::_update_process_internal_for_smoothing() { + bool is_not_in_scene_or_editor = !(is_inside_tree() && Engine::get_singleton()->is_editor_hint()); + bool is_any_smoothing_valid = smoothing > 0 || rotation_smoothing_speed > 0; + + bool enabled = is_any_smoothing_valid && is_not_in_scene_or_editor; + set_process_internal(enabled); +} + bool Camera2D::is_current() const { return current; } @@ -508,17 +527,31 @@ void Camera2D::align() { void Camera2D::set_follow_smoothing(real_t p_speed) { smoothing = p_speed; - if (smoothing > 0 && !(is_inside_tree() && Engine::get_singleton()->is_editor_hint())) { - set_process_internal(true); - } else { - set_process_internal(false); - } + _update_process_internal_for_smoothing(); } real_t Camera2D::get_follow_smoothing() const { return smoothing; } +void Camera2D::set_rotation_smoothing_speed(real_t p_speed) { + rotation_smoothing_speed = p_speed; + _update_process_internal_for_smoothing(); +} + +real_t Camera2D::get_rotation_smoothing_speed() const { + return rotation_smoothing_speed; +} + +void Camera2D::set_rotation_smoothing_enabled(bool p_enabled) { + rotation_smoothing_enabled = p_enabled; + notify_property_list_changed(); +} + +bool Camera2D::is_rotation_smoothing_enabled() const { + return rotation_smoothing_enabled; +} + Point2 Camera2D::get_camera_screen_center() const { return camera_screen_center; } @@ -659,6 +692,9 @@ void Camera2D::_validate_property(PropertyInfo &p_property) const { if (!smoothing_enabled && p_property.name == "smoothing_speed") { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } + if (!rotation_smoothing_enabled && p_property.name == "rotation_smoothing_speed") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } void Camera2D::_bind_methods() { @@ -716,6 +752,12 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enable_follow_smoothing", "follow_smoothing"), &Camera2D::set_enable_follow_smoothing); ClassDB::bind_method(D_METHOD("is_follow_smoothing_enabled"), &Camera2D::is_follow_smoothing_enabled); + ClassDB::bind_method(D_METHOD("set_rotation_smoothing_enabled", "enabled"), &Camera2D::set_rotation_smoothing_enabled); + ClassDB::bind_method(D_METHOD("is_rotation_smoothing_enabled"), &Camera2D::is_rotation_smoothing_enabled); + + ClassDB::bind_method(D_METHOD("set_rotation_smoothing_speed", "speed"), &Camera2D::set_rotation_smoothing_speed); + ClassDB::bind_method(D_METHOD("get_rotation_smoothing_speed"), &Camera2D::get_rotation_smoothing_speed); + ClassDB::bind_method(D_METHOD("force_update_scroll"), &Camera2D::force_update_scroll); ClassDB::bind_method(D_METHOD("reset_smoothing"), &Camera2D::reset_smoothing); ClassDB::bind_method(D_METHOD("align"), &Camera2D::align); @@ -750,6 +792,10 @@ void Camera2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smoothing_enabled"), "set_enable_follow_smoothing", "is_follow_smoothing_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_follow_smoothing", "get_follow_smoothing"); + ADD_GROUP("Rotation Smoothing", "rotation_smoothing_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotation_smoothing_enabled"), "set_rotation_smoothing_enabled", "is_rotation_smoothing_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_smoothing_speed"), "set_rotation_smoothing_speed", "get_rotation_smoothing_speed"); + ADD_GROUP("Drag", "drag_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_horizontal_enabled"), "set_drag_horizontal_enabled", "is_drag_horizontal_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_vertical_enabled"), "set_drag_vertical_enabled", "is_drag_vertical_enabled"); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 1ce622388c..1411175af2 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -67,6 +67,11 @@ protected: bool current = false; real_t smoothing = 5.0; bool smoothing_enabled = false; + + real_t camera_angle = 0.0; + real_t rotation_smoothing_speed = 5.0; + bool rotation_smoothing_enabled = false; + int limit[4]; bool limit_smoothing_enabled = false; @@ -87,6 +92,8 @@ protected: void _set_old_smoothing(real_t p_enable); + void _update_process_internal_for_smoothing(); + bool screen_drawing_enabled = true; bool limit_drawing_enabled = false; bool margin_drawing_enabled = false; @@ -139,6 +146,12 @@ public: void set_follow_smoothing(real_t p_speed); real_t get_follow_smoothing() const; + void set_rotation_smoothing_speed(real_t p_speed); + real_t get_rotation_smoothing_speed() const; + + void set_rotation_smoothing_enabled(bool p_enabled); + bool is_rotation_smoothing_enabled() const; + void set_process_callback(Camera2DProcessCallback p_mode); Camera2DProcessCallback get_process_callback() const; diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index ea735411a8..141e5f9139 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -152,8 +152,8 @@ private: Vector2 direction = Vector2(1, 0); real_t spread = 45.0; - real_t parameters_min[PARAM_MAX]; - real_t parameters_max[PARAM_MAX]; + real_t parameters_min[PARAM_MAX] = {}; + real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 7765533016..2518069b78 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -437,7 +437,7 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,no_slider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 4e6b37a860..d0e35c2ea2 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -1134,6 +1134,7 @@ bool CharacterBody2D::move_and_slide() { if (!current_platform_velocity.is_zero_approx()) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), current_platform_velocity * delta, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.exclude_bodies.insert(platform_rid); if (platform_object_id.is_valid()) { parameters.exclude_objects.insert(platform_object_id); @@ -1192,6 +1193,7 @@ void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. Vector2 prev_position = parameters.from.columns[2]; @@ -1348,6 +1350,7 @@ void CharacterBody2D::_move_and_slide_floating(double p_delta) { bool first_slide = true; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer2D::MotionResult result; bool collided = move_and_collide(parameters, result, false, false); @@ -1394,7 +1397,7 @@ void CharacterBody2D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_ real_t length = MAX(floor_snap_length, margin); PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); - parameters.recovery_as_collision = true; // Report margin recovery as collision to improve floor detection. + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer2D::MotionResult result; @@ -1430,7 +1433,7 @@ bool CharacterBody2D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_f real_t length = MAX(floor_snap_length, margin); PhysicsServer2D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); - parameters.recovery_as_collision = true; // Report margin recovery as collision to improve floor detection. + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer2D::MotionResult result; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index bdcd1f2f28..5acf661425 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1007,6 +1007,22 @@ void TileMap::_recompute_rect_cache() { void TileMap::_rendering_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_CANVAS: { + bool visible = is_visible_in_tree(); + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layers[layer].quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + Transform2D xform; + xform.set_origin(map_to_local(kv.key)); + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, get_canvas()); + RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, visible); + } + } + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { bool visible = is_visible_in_tree(); for (int layer = 0; layer < (int)layers.size(); layer++) { @@ -1017,8 +1033,8 @@ void TileMap::_rendering_notification(int p_what) { for (const KeyValue<Vector2i, Vector2i> &E_cell : q.local_to_map) { Transform2D xform; xform.set_origin(E_cell.key); - for (const RID &occluder : q.occluders) { - RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, visible); + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, visible); } } } @@ -1034,12 +1050,10 @@ void TileMap::_rendering_notification(int p_what) { TileMapQuadrant &q = E_quadrant.value; // Update occluders transform. - for (const KeyValue<Vector2i, Vector2i> &E_cell : q.local_to_map) { + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { Transform2D xform; - xform.set_origin(E_cell.key); - for (const RID &occluder : q.occluders) { - RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); - } + xform.set_origin(map_to_local(kv.key)); + RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); } } } @@ -1050,6 +1064,17 @@ void TileMap::_rendering_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); } } break; + + case NOTIFICATION_EXIT_CANVAS: { + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layers[layer].quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID()); + } + } + } + } break; } } @@ -1106,8 +1131,8 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q.canvas_items.clear(); // Free the occluders. - for (const RID &occluder : q.occluders) { - rs->free(occluder); + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + rs->free(kv.value); } q.occluders.clear(); @@ -1211,7 +1236,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); - q.occluders.push_back(occluder_id); + q.occluders[E_cell.value] = occluder_id; } } } @@ -1259,8 +1284,8 @@ void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->canvas_items.clear(); // Free the occluders. - for (const RID &occluder : p_quadrant->occluders) { - RenderingServer::get_singleton()->free(occluder); + for (const KeyValue<Vector2i, RID> &kv : p_quadrant->occluders) { + RenderingServer::get_singleton()->free(kv.value); } p_quadrant->occluders.clear(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 902926291d..d468675e91 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -68,7 +68,7 @@ struct TileMapQuadrant { // Rendering. List<RID> canvas_items; - List<RID> occluders; + HashMap<Vector2i, RID> occluders; // Physics. List<RID> bodies; diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 26c702172b..6b70137736 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -156,7 +156,7 @@ private: real_t spread = 45.0; real_t flatness = 0.0; - real_t parameters_min[PARAM_MAX]; + real_t parameters_min[PARAM_MAX] = {}; real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 12d2e66b41..5f515acead 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -1054,7 +1054,7 @@ void Node3D::_bind_methods() { ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,no_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_HIDE_QUATERNION_EDIT, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis"); diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 594e94644c..91f58b3976 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -1200,6 +1200,7 @@ bool CharacterBody3D::move_and_slide() { if (!current_platform_velocity.is_zero_approx()) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), current_platform_velocity * delta, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.exclude_bodies.insert(platform_rid); if (platform_object_id.is_valid()) { @@ -1264,6 +1265,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin); parameters.max_collisions = 6; // There can be 4 collisions between 2 walls + 2 more for the floor. + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer3D::MotionResult result; bool collided = move_and_collide(parameters, result, false, !sliding_enabled); @@ -1508,6 +1510,7 @@ void CharacterBody3D::_move_and_slide_floating(double p_delta) { bool first_slide = true; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin); + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer3D::MotionResult result; bool collided = move_and_collide(parameters, result, false, false); @@ -1562,7 +1565,7 @@ void CharacterBody3D::_snap_on_floor(bool p_was_on_floor, bool p_vel_dir_facing_ PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); parameters.max_collisions = 4; - parameters.recovery_as_collision = true; // Report margin recovery as collision to improve floor detection. + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer3D::MotionResult result; @@ -1598,7 +1601,7 @@ bool CharacterBody3D::_on_floor_if_snapped(bool p_was_on_floor, bool p_vel_dir_f PhysicsServer3D::MotionParameters parameters(get_global_transform(), -up_direction * length, margin); parameters.max_collisions = 4; - parameters.recovery_as_collision = true; // Report margin recovery as collision to improve floor detection. + parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. parameters.collide_separation_ray = true; PhysicsServer3D::MotionResult result; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 8069ab465b..94d296977b 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -609,72 +609,74 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { /* Text manipulation */ // Overridable actions -void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { - bool had_selection = has_selection(); - String selection_text = (had_selection ? get_selected_text() : ""); +void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { + start_action(EditAction::ACTION_TYPING); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (had_selection) { - begin_complex_operation(); - delete_selection(); - } + bool had_selection = has_selection(i); + String selection_text = (had_selection ? get_selected_text(i) : ""); - // Remove the old character if in overtype mode and no selection. - if (is_overtype_mode_enabled() && !had_selection) { - begin_complex_operation(); + if (had_selection) { + delete_selection(i); + } - /* Make sure we don't try and remove empty space. */ - if (get_caret_column() < get_line(get_caret_line()).length()) { - remove_text(get_caret_line(), get_caret_column(), get_caret_line(), get_caret_column() + 1); + // Remove the old character if in overtype mode and no selection. + if (is_overtype_mode_enabled() && !had_selection) { + // Make sure we don't try and remove empty space. + if (get_caret_column(i) < get_line(get_caret_line(i)).length()) { + remove_text(get_caret_line(i), get_caret_column(i), get_caret_line(i), get_caret_column(i) + 1); + } } - } - const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; - if (auto_brace_completion_enabled) { - int cl = get_caret_line(); - int cc = get_caret_column(); + if (auto_brace_completion_enabled) { + int cl = get_caret_line(i); + int cc = get_caret_column(i); - if (had_selection) { - insert_text_at_caret(chr); + if (had_selection) { + insert_text_at_caret(chr, i); - String close_key = get_auto_brace_completion_close_key(chr); - if (!close_key.is_empty()) { - insert_text_at_caret(selection_text + close_key); - set_caret_column(get_caret_column() - 1); - } - } else { - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - - if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { - insert_text_at_caret(chr); + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key, i); + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr, i); + } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { + insert_text_at_caret(chr, i); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr, i); + } else { + insert_text_at_caret(chr, i); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); + } } + set_caret_column(cc + caret_move_offset, i == 0, i); } - set_caret_column(cc + caret_move_offset); + } else { + insert_text_at_caret(chr, i); } - } else { - insert_text_at_caret(chr); - } - - if ((is_overtype_mode_enabled() && !had_selection) || (had_selection)) { - end_complex_operation(); } + end_action(); } -void CodeEdit::_backspace_internal() { +void CodeEdit::_backspace_internal(int p_caret) { if (!is_editable()) { return; } @@ -684,51 +686,65 @@ void CodeEdit::_backspace_internal() { return; } - int cc = get_caret_column(); - int cl = get_caret_line(); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (cc == 0 && cl == 0) { - return; - } + int cc = get_caret_column(i); + int cl = get_caret_line(i); - if (cl > 0 && _is_line_hidden(cl - 1)) { - unfold_line(get_caret_line() - 1); - } + if (cc == 0 && cl == 0) { + continue; + } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + if (cl > 0 && _is_line_hidden(cl - 1)) { + unfold_line(get_caret_line(i) - 1); + } - merge_gutters(prev_line, cl); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); - if (auto_brace_completion_enabled && cc > 0) { - int idx = _get_auto_brace_pair_open_at_pos(cl, cc); - if (idx != -1) { - prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + merge_gutters(prev_line, cl); - if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { - remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); - } else { - remove_text(prev_line, prev_column, cl, cc); + if (auto_brace_completion_enabled && cc > 0) { + int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (idx != -1) { + prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + + if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { + remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + } else { + remove_text(prev_line, prev_column, cl, cc); + } + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); + + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + continue; } - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); - return; } - } - // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the - // same way as tabs. - if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) >= cc) { - prev_column = cc - _calculate_spaces_till_next_left_indent(cc); - prev_line = cl; + // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the + // same way as tabs. + if (indent_using_spaces && cc != 0) { + if (get_first_non_whitespace_column(cl) >= cc) { + prev_column = cc - _calculate_spaces_till_next_left_indent(cc); + prev_line = cl; + } } - } - remove_text(prev_line, prev_column, cl, cc); + remove_text(prev_line, prev_column, cl, cc); + + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + } + merge_overlapping_carets(); + end_complex_operation(); } /* Indent management */ @@ -803,10 +819,15 @@ void CodeEdit::do_indent() { return; } - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column()); - if (spaces_to_add > 0) { - insert_text_at_caret(String(" ").repeat(spaces_to_add)); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i)); + if (spaces_to_add > 0) { + insert_text_at_caret(String(" ").repeat(spaces_to_add), i); + } } + end_complex_operation(); } void CodeEdit::indent_lines() { @@ -815,48 +836,49 @@ void CodeEdit::indent_lines() { } begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + // This value informs us by how much we changed selection position by indenting right. + // Default is 1 for tab indentation. + int selection_offset = 1; + + int start_line = get_caret_line(c); + int end_line = start_line; + if (has_selection(c)) { + start_line = get_selection_from_line(c); + end_line = get_selection_to_line(c); + + // Ignore the last line if the selection is not past the first column. + if (get_selection_to_column(c) == 0) { + selection_offset = 0; + end_line--; + } + } - /* This value informs us by how much we changed selection position by indenting right. */ - /* Default is 1 for tab indentation. */ - int selection_offset = 1; - - int start_line = get_caret_line(); - int end_line = start_line; - if (has_selection()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); + for (int i = start_line; i <= end_line; i++) { + const String line_text = get_line(i); + if (line_text.size() == 0 && has_selection(c)) { + continue; + } - /* Ignore the last line if the selection is not past the first column. */ - if (get_selection_to_column() == 0) { - selection_offset = 0; - end_line--; - } - } + if (!indent_using_spaces) { + set_line(i, '\t' + line_text); + continue; + } - for (int i = start_line; i <= end_line; i++) { - const String line_text = get_line(i); - if (line_text.size() == 0 && has_selection()) { - continue; + // We don't really care where selection is - we just need to know indentation level at the beginning of the line. + // Since we will add this many spaces, we want to move the whole selection and caret by this much. + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); + set_line(i, String(" ").repeat(spaces_to_add) + line_text); + selection_offset = spaces_to_add; } - if (!indent_using_spaces) { - set_line(i, '\t' + line_text); - continue; + // Fix selection and caret being off after shifting selection right. + if (has_selection(c)) { + select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c); } - - /* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */ - /* Since we will add this many spaces, we want to move the whole selection and caret by this much. */ - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); - set_line(i, String(" ").repeat(spaces_to_add) + line_text); - selection_offset = spaces_to_add; + set_caret_column(get_caret_column(c) + selection_offset, false, c); } - - /* Fix selection and caret being off after shifting selection right.*/ - if (has_selection()) { - select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset); - } - set_caret_column(get_caret_column() + selection_offset, false); - end_complex_operation(); } @@ -872,30 +894,36 @@ void CodeEdit::do_unindent() { return; } - int cl = get_caret_line(); - const String &line = get_line(cl); - - if (line[cc - 1] == '\t') { - remove_text(cl, cc - 1, cl, cc); - set_caret_column(MAX(0, cc - 1)); - return; - } + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + int cl = get_caret_line(c); + const String &line = get_line(cl); + + if (line[cc - 1] == '\t') { + remove_text(cl, cc - 1, cl, cc); + set_caret_column(MAX(0, cc - 1), c == 0, c); + adjust_carets_after_edit(c, cl, cc, cl, cc - 1); + continue; + } - if (line[cc - 1] != ' ') { - return; - } + if (line[cc - 1] != ' ') { + continue; + } - int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); - if (spaces_to_remove > 0) { - for (int i = 1; i <= spaces_to_remove; i++) { - if (line[cc - i] != ' ') { - spaces_to_remove = i - 1; - break; + int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); + if (spaces_to_remove > 0) { + for (int i = 1; i <= spaces_to_remove; i++) { + if (line[cc - i] != ' ') { + spaces_to_remove = i - 1; + break; + } } + remove_text(cl, cc - spaces_to_remove, cl, cc); + set_caret_column(MAX(0, cc - spaces_to_remove), c == 0, c); } - remove_text(cl, cc - spaces_to_remove, cl, cc); - set_caret_column(MAX(0, cc - spaces_to_remove)); } + end_complex_operation(); } void CodeEdit::unindent_lines() { @@ -905,71 +933,73 @@ void CodeEdit::unindent_lines() { begin_complex_operation(); - /* Moving caret and selection after unindenting can get tricky because */ - /* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */ - /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */ - int removed_characters = 0; - int initial_selection_end_column = 0; - int initial_cursor_column = get_caret_column(); - - int start_line = get_caret_line(); - int end_line = start_line; - if (has_selection()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); - - /* Ignore the last line if the selection is not past the first column. */ - initial_selection_end_column = get_selection_to_column(); - if (initial_selection_end_column == 0) { - end_line--; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + // Moving caret and selection after unindenting can get tricky because + // changing content of line can move caret and selection on its own (if new line ends before previous position of either) + // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. + int removed_characters = 0; + int initial_selection_end_column = 0; + int initial_cursor_column = get_caret_column(c); + + int start_line = get_caret_line(c); + int end_line = start_line; + if (has_selection(c)) { + start_line = get_selection_from_line(c); + end_line = get_selection_to_line(c); + + // Ignore the last line if the selection is not past the first column. + initial_selection_end_column = get_selection_to_column(c); + if (initial_selection_end_column == 0) { + end_line--; + } } - } - bool first_line_edited = false; - bool last_line_edited = false; + bool first_line_edited = false; + bool last_line_edited = false; - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); + for (int i = start_line; i <= end_line; i++) { + String line_text = get_line(i); - if (line_text.begins_with("\t")) { - line_text = line_text.substr(1, line_text.length()); + if (line_text.begins_with("\t")) { + line_text = line_text.substr(1, line_text.length()); - set_line(i, line_text); - removed_characters = 1; + set_line(i, line_text); + removed_characters = 1; - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; - continue; - } + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + continue; + } - if (line_text.begins_with(" ")) { - /* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */ - /* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */ - /* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */ - int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); - line_text = line_text.substr(spaces_to_remove, line_text.length()); + if (line_text.begins_with(" ")) { + // When unindenting we aim to remove spaces before line that has selection no matter what is selected. + // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. + // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); + line_text = line_text.substr(spaces_to_remove, line_text.length()); - set_line(i, line_text); - removed_characters = spaces_to_remove; + set_line(i, line_text); + removed_characters = spaces_to_remove; - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + } } - } - if (has_selection()) { - /* Fix selection being off by one on the first line. */ - if (first_line_edited) { - select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column); - } + if (has_selection(c)) { + // Fix selection being off by one on the first line. + if (first_line_edited) { + select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c); + } - /* Fix selection being off by one on the last line. */ - if (last_line_edited) { - select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters); + // Fix selection being off by one on the last line. + if (last_line_edited) { + select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c); + } } + set_caret_column(initial_cursor_column - removed_characters, false, c); } - set_caret_column(initial_cursor_column - removed_characters, false); - end_complex_operation(); } @@ -990,106 +1020,108 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { return; } - /* When not splitting the line, we need to factor in indentation from the end of the current line. */ - const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length(); - const int cl = get_caret_line(); - - const String line = get_line(cl); - - String ins = "\n"; + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + // When not splitting the line, we need to factor in indentation from the end of the current line. + const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length(); + const int cl = get_caret_line(i); - /* Append current indentation. */ - int space_count = 0; - int line_col = 0; - for (; line_col < cc; line_col++) { - if (line[line_col] == '\t') { - ins += indent_text; - space_count = 0; - continue; - } + const String line = get_line(cl); - if (line[line_col] == ' ') { - space_count++; + String ins = "\n"; - if (space_count == indent_size) { + // Append current indentation. + int space_count = 0; + int line_col = 0; + for (; line_col < cc; line_col++) { + if (line[line_col] == '\t') { ins += indent_text; space_count = 0; + continue; } - continue; - } - break; - } - - if (is_line_folded(cl)) { - unfold_line(cl); - } - /* Indent once again if the previous line needs it, ie ':'. */ - /* Then add an addition new line for any closing pairs aka '()'. */ - /* Skip this in comments or if we are going above. */ - bool brace_indent = false; - if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) { - bool should_indent = false; - char32_t indent_char = ' '; + if (line[line_col] == ' ') { + space_count++; - for (; line_col < cc; line_col++) { - char32_t c = line[line_col]; - if (auto_indent_prefixes.has(c)) { - should_indent = true; - indent_char = c; + if (space_count == indent_size) { + ins += indent_text; + space_count = 0; + } continue; } + break; + } - /* Make sure this is the last char, trailing whitespace or comments are okay. */ - /* Increment column for comments because the delimiter (#) should be ignored. */ - if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { - should_indent = false; - } + if (is_line_folded(cl)) { + unfold_line(cl); } - if (should_indent) { - ins += indent_text; + // Indent once again if the previous line needs it, ie ':'. + // Then add an addition new line for any closing pairs aka '()'. + // Skip this in comments or if we are going above. + bool brace_indent = false; + if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) { + bool should_indent = false; + char32_t indent_char = ' '; - String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); - if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { - /* No need to move the brace below if we are not taking the text with us. */ - if (p_split_current_line) { - brace_indent = true; - ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); - } else { - brace_indent = false; - ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); + for (; line_col < cc; line_col++) { + char32_t c = line[line_col]; + if (auto_indent_prefixes.has(c)) { + should_indent = true; + indent_char = c; + continue; + } + + // Make sure this is the last char, trailing whitespace or comments are okay. + // Increment column for comments because the delimiter (#) should be ignored. + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { + should_indent = false; } } - } - } - begin_complex_operation(); + if (should_indent) { + ins += indent_text; + + String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); + if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { + // No need to move the brace below if we are not taking the text with us. + if (p_split_current_line) { + brace_indent = true; + ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); + } else { + brace_indent = false; + ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); + } + } + } + } - bool first_line = false; - if (!p_split_current_line) { - deselect(); + bool first_line = false; + if (!p_split_current_line) { + deselect(i); - if (p_above) { - if (cl > 0) { - set_caret_line(cl - 1, false); - set_caret_column(get_line(get_caret_line()).length()); + if (p_above) { + if (cl > 0) { + set_caret_line(cl - 1, false, true, 0, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); + } else { + set_caret_column(0, i == 0, i); + first_line = true; + } } else { - set_caret_column(0); - first_line = true; + set_caret_column(line.length(), i == 0, i); } - } else { - set_caret_column(line.length()); } - } - insert_text_at_caret(ins); + insert_text_at_caret(ins, i); - if (first_line) { - set_caret_line(0); - } else if (brace_indent) { - set_caret_line(get_caret_line() - 1, false); - set_caret_column(get_line(get_caret_line()).length()); + if (first_line) { + set_caret_line(0, i == 0, true, 0, i); + } else if (brace_indent) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); + } } end_complex_operation(); @@ -1522,22 +1554,26 @@ void CodeEdit::fold_line(int p_line) { _set_line_as_hidden(i, true); } - /* Fix selection. */ - if (has_selection()) { - if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) { - deselect(); - } else if (_is_line_hidden(get_selection_from_line())) { - select(p_line, 9999, get_selection_to_line(), get_selection_to_column()); - } else if (_is_line_hidden(get_selection_to_line())) { - select(get_selection_from_line(), get_selection_from_column(), p_line, 9999); + for (int i = 0; i < get_caret_count(); i++) { + // Fix selection. + if (has_selection(i)) { + if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) { + deselect(i); + } else if (_is_line_hidden(get_selection_from_line(i))) { + select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i); + } else if (_is_line_hidden(get_selection_to_line(i))) { + select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i); + } } - } - /* Reset caret. */ - if (_is_line_hidden(get_caret_line())) { - set_caret_line(p_line, false, false); - set_caret_column(get_line(p_line).length(), false); + // Reset caret. + if (_is_line_hidden(get_caret_line(i))) { + set_caret_line(p_line, false, false, 0, i); + set_caret_column(get_line(p_line).length(), false, i); + } } + + merge_overlapping_carets(); queue_redraw(); } @@ -1950,98 +1986,108 @@ void CodeEdit::confirm_code_completion(bool p_replace) { return; } + char32_t caret_last_completion_char; begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + int caret_line = get_caret_line(i); + + const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; + const String &display_text = code_completion_options[code_completion_current_selected].display; + + if (p_replace) { + // Find end of current section. + const String line = get_line(caret_line); + int caret_col = get_caret_column(i); + int caret_remove_line = caret_line; + + bool merge_text = true; + int in_string = is_in_string(caret_line, caret_col); + if (in_string != -1) { + Point2 string_end = get_delimiter_end_position(caret_line, caret_col); + if (string_end.x != -1) { + merge_text = false; + caret_remove_line = string_end.y; + caret_col = string_end.x - 1; + } + } - int caret_line = get_caret_line(); - - const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; - const String &display_text = code_completion_options[code_completion_current_selected].display; - - if (p_replace) { - /* Find end of current section */ - const String line = get_line(caret_line); - int caret_col = get_caret_column(); - int caret_remove_line = caret_line; - - bool merge_text = true; - int in_string = is_in_string(caret_line, caret_col); - if (in_string != -1) { - Point2 string_end = get_delimiter_end_position(caret_line, caret_col); - if (string_end.x != -1) { - merge_text = false; - caret_remove_line = string_end.y; - caret_col = string_end.x - 1; + if (merge_text) { + for (; caret_col < line.length(); caret_col++) { + if (is_symbol(line[caret_col])) { + break; + } + } } - } - if (merge_text) { - for (; caret_col < line.length(); caret_col++) { - if (is_symbol(line[caret_col])) { + // Replace. + remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col); + adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col); + set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); + insert_text_at_caret(insert_text, i); + } else { + // Get first non-matching char. + const String line = get_line(caret_line); + int caret_col = get_caret_column(i); + int matching_chars = code_completion_base.length(); + for (; matching_chars <= insert_text.length(); matching_chars++) { + if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { break; } + caret_col++; } + + // Remove base completion text. + remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); + adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); + set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); + + // Merge with text. + insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i); + set_caret_column(caret_col, false, i); + insert_text_at_caret(insert_text.substr(matching_chars), i); } - /* Replace. */ - remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_remove_line, caret_col); - set_caret_column(get_caret_column() - code_completion_base.length(), false); - insert_text_at_caret(insert_text); - } else { - /* Get first non-matching char. */ + //* Handle merging of symbols eg strings, brackets. const String line = get_line(caret_line); - int caret_col = get_caret_column(); - int matching_chars = code_completion_base.length(); - for (; matching_chars <= insert_text.length(); matching_chars++) { - if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { - break; - } - caret_col++; + char32_t next_char = line[get_caret_column(i)]; + char32_t last_completion_char = insert_text[insert_text.length() - 1]; + if (i == 0) { + caret_last_completion_char = last_completion_char; } + char32_t last_completion_char_display = display_text[display_text.length() - 1]; - /* Remove base completion text. */ - remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_line, get_caret_column()); - set_caret_column(get_caret_column() - code_completion_base.length(), false); - - /* Merge with text. */ - insert_text_at_caret(insert_text.substr(0, code_completion_base.length())); - set_caret_column(caret_col, false); - insert_text_at_caret(insert_text.substr(matching_chars)); - } - - /* Handle merging of symbols eg strings, brackets. */ - const String line = get_line(caret_line); - char32_t next_char = line[get_caret_column()]; - char32_t last_completion_char = insert_text[insert_text.length() - 1]; - char32_t last_completion_char_display = display_text[display_text.length() - 1]; + int pre_brace_pair = get_caret_column(i) > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i)) : -1; + int post_brace_pair = get_caret_column(i) < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i)) : -1; - int pre_brace_pair = get_caret_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column()) : -1; - int post_brace_pair = get_caret_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column()) : -1; - - if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { - remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); - } + if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + } - if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { - remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); - } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); - set_caret_column(get_caret_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length()); - } + if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); + set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i); + } - if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { - pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); - if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { - remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); - if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { - set_caret_column(get_caret_column() - 1); + if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column(i) > 0 && get_caret_column(i) < get_line(caret_line).length()) { + pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1); + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) { + remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); + adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); + if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) { + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } } } } - end_complex_operation(); cancel_code_completion(); - if (code_completion_prefixes.has(last_completion_char)) { + if (code_completion_prefixes.has(caret_last_completion_char)) { request_code_completion(); } } @@ -2399,6 +2445,7 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { } if (p_gutter == line_number_gutter) { + remove_secondary_carets(); set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); select(p_line, 0, p_line + 1, 0); set_caret_line(p_line + 1); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 2065f3e681..09c7ef80bf 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -261,8 +261,8 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; - virtual void _backspace_internal() override; + virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override; + virtual void _backspace_internal(int p_caret) override; GDVIRTUAL1(_confirm_code_completion, bool) GDVIRTUAL1(_request_code_completion, bool) diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index a4d5205f81..861c7f273b 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -199,8 +199,6 @@ private: float base_scale = 1.0; } theme_cache; - bool _is_over_clear_button(const Point2 &p_pos) const; - void _clear_undo_stack(); void _clear_redo(); void _create_undo_state(); @@ -240,6 +238,7 @@ private: void _ensure_menu(); protected: + bool _is_over_clear_button(const Point2 &p_pos) const; virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 38302136d6..cc0fa200a6 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -536,145 +536,145 @@ void TextEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), background_color); } - int brace_open_match_line = -1; - int brace_open_match_column = -1; - bool brace_open_matching = false; - bool brace_open_mismatch = false; - int brace_close_match_line = -1; - int brace_close_match_column = -1; - bool brace_close_matching = false; - bool brace_close_mismatch = false; - - if (highlight_matching_braces_enabled && caret.line >= 0 && caret.line < text.size() && caret.column >= 0) { - if (caret.column < text[caret.line].length()) { - // Check for open. - char32_t c = text[caret.line][caret.column]; - char32_t closec = 0; - - if (c == '[') { - closec = ']'; - } else if (c == '{') { - closec = '}'; - } else if (c == '(') { - closec = ')'; + Vector<BraceMatchingData> brace_matching; + if (highlight_matching_braces_enabled) { + brace_matching.resize(carets.size()); + + for (int caret = 0; caret < carets.size(); caret++) { + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { + continue; } - if (closec != 0) { - int stack = 1; - - for (int i = caret.line; i < text.size(); i++) { - int from = i == caret.line ? caret.column + 1 : 0; - for (int j = from; j < text[i].length(); j++) { - char32_t cc = text[i][j]; - // Ignore any brackets inside a string. - if (cc == '"' || cc == '\'') { - char32_t quotation = cc; - do { - j++; - if (!(j < text[i].length())) { - break; - } - cc = text[i][j]; - // Skip over escaped quotation marks inside strings. - if (cc == '\\') { - bool escaped = true; - while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { - escaped = !escaped; - j++; + if (get_caret_column(caret) < text[get_caret_line(caret)].length()) { + // Check for open. + char32_t c = text[get_caret_line(caret)][get_caret_column(caret)]; + char32_t closec = 0; + + if (c == '[') { + closec = ']'; + } else if (c == '{') { + closec = '}'; + } else if (c == '(') { + closec = ')'; + } + + if (closec != 0) { + int stack = 1; + + for (int i = get_caret_line(caret); i < text.size(); i++) { + int from = i == get_caret_line(caret) ? get_caret_column(caret) + 1 : 0; + for (int j = from; j < text[i].length(); j++) { + char32_t cc = text[i][j]; + // Ignore any brackets inside a string. + if (cc == '"' || cc == '\'') { + char32_t quotation = cc; + do { + j++; + if (!(j < text[i].length())) { + break; } - if (escaped) { - j++; - continue; + cc = text[i][j]; + // Skip over escaped quotation marks inside strings. + if (cc == '\\') { + bool escaped = true; + while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { + escaped = !escaped; + j++; + } + if (escaped) { + j++; + continue; + } } - } - } while (cc != quotation); - } else if (cc == c) { - stack++; - } else if (cc == closec) { - stack--; - } + } while (cc != quotation); + } else if (cc == c) { + stack++; + } else if (cc == closec) { + stack--; + } - if (stack == 0) { - brace_open_match_line = i; - brace_open_match_column = j; - brace_open_matching = true; + if (stack == 0) { + brace_matching.write[caret].open_match_line = i; + brace_matching.write[caret].open_match_column = j; + brace_matching.write[caret].open_matching = true; + break; + } + } + if (brace_matching.write[caret].open_match_line != -1) { break; } } - if (brace_open_match_line != -1) { - break; - } - } - if (!brace_open_matching) { - brace_open_mismatch = true; + if (!brace_matching.write[caret].open_matching) { + brace_matching.write[caret].open_mismatch = true; + } } } - } - if (caret.column > 0) { - char32_t c = text[caret.line][caret.column - 1]; - char32_t closec = 0; + if (get_caret_column(caret) > 0) { + char32_t c = text[get_caret_line(caret)][get_caret_column(caret) - 1]; + char32_t closec = 0; - if (c == ']') { - closec = '['; - } else if (c == '}') { - closec = '{'; - } else if (c == ')') { - closec = '('; - } + if (c == ']') { + closec = '['; + } else if (c == '}') { + closec = '{'; + } else if (c == ')') { + closec = '('; + } - if (closec != 0) { - int stack = 1; - - for (int i = caret.line; i >= 0; i--) { - int from = i == caret.line ? caret.column - 2 : text[i].length() - 1; - for (int j = from; j >= 0; j--) { - char32_t cc = text[i][j]; - // Ignore any brackets inside a string. - if (cc == '"' || cc == '\'') { - char32_t quotation = cc; - do { - j--; - if (!(j >= 0)) { - break; - } - cc = text[i][j]; - // Skip over escaped quotation marks inside strings. - if (cc == quotation) { - bool escaped = false; - while (j - 1 >= 0 && text[i][j - 1] == '\\') { - escaped = !escaped; - j--; + if (closec != 0) { + int stack = 1; + + for (int i = get_caret_line(caret); i >= 0; i--) { + int from = i == get_caret_line(caret) ? get_caret_column(caret) - 2 : text[i].length() - 1; + for (int j = from; j >= 0; j--) { + char32_t cc = text[i][j]; + // Ignore any brackets inside a string. + if (cc == '"' || cc == '\'') { + char32_t quotation = cc; + do { + j--; + if (!(j >= 0)) { + break; } - if (escaped) { - cc = '\\'; - continue; + cc = text[i][j]; + // Skip over escaped quotation marks inside strings. + if (cc == quotation) { + bool escaped = false; + while (j - 1 >= 0 && text[i][j - 1] == '\\') { + escaped = !escaped; + j--; + } + if (escaped) { + cc = '\\'; + continue; + } } - } - } while (cc != quotation); - } else if (cc == c) { - stack++; - } else if (cc == closec) { - stack--; - } + } while (cc != quotation); + } else if (cc == c) { + stack++; + } else if (cc == closec) { + stack--; + } - if (stack == 0) { - brace_close_match_line = i; - brace_close_match_column = j; - brace_close_matching = true; + if (stack == 0) { + brace_matching.write[caret].close_match_line = i; + brace_matching.write[caret].close_match_column = j; + brace_matching.write[caret].close_matching = true; + break; + } + } + if (brace_matching.write[caret].close_match_line != -1) { break; } } - if (brace_close_match_line != -1) { - break; - } - } - if (!brace_close_matching) { - brace_close_mismatch = true; + if (!brace_matching.write[caret].close_matching) { + brace_matching.write[caret].close_mismatch = true; + } } } } @@ -683,12 +683,20 @@ void TextEdit::_notification(int p_what) { bool draw_placeholder = text.size() == 1 && text[0].length() == 0; // Get the highlighted words. - String highlighted_text = get_selected_text(); + String highlighted_text = get_selected_text(0); // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - const int caret_wrap_index = get_caret_wrap_index(); + HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<int> carets_wrap_index; + carets_wrap_index.resize(carets.size()); + for (int i = 0; i < carets.size(); i++) { + carets.write[i].visible = false; + int wrap_index = get_caret_wrap_index(i); + caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index); + carets_wrap_index.write[i] = wrap_index; + } int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); @@ -783,7 +791,7 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (minimap_line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), current_line_color); } else { @@ -875,7 +883,6 @@ void TextEdit::_notification(int p_what) { } // Draw main text. - caret.visible = false; line_drawing_cache.clear(); int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height(); int line = first_visible_line; @@ -921,7 +928,7 @@ void TextEdit::_notification(int p_what) { } const String &str = wrap_rows[line_wrap_index]; - int char_margin = xmargin_beg - caret.x_ofs; + int char_margin = xmargin_beg - first_visible_col; int ofs_x = 0; int ofs_y = 0; @@ -934,7 +941,7 @@ void TextEdit::_notification(int p_what) { } ofs_y += i * row_height + line_spacing / 2; - ofs_y -= caret.wrap_ofs * row_height; + ofs_y -= first_visible_line_wrap_ofs * row_height; ofs_y -= _get_v_scroll_offset() * row_height; bool clipped = false; @@ -960,7 +967,7 @@ void TextEdit::_notification(int p_what) { if (str.length() == 0) { // Draw line background if empty as we won't loop at all. - if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { @@ -969,17 +976,19 @@ void TextEdit::_notification(int p_what) { } // Give visual indication of empty selected line. - if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - float char_w = font->get_char_size(' ', font_size).width; - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color); + for (int c = 0; c < carets.size(); c++) { + if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) { + float char_w = font->get_char_size(' ', font_size).width; + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); + } else { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color); + } } } } else { // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { @@ -1074,23 +1083,25 @@ void TextEdit::_notification(int p_what) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; } - if (!clipped && selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection - int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column; - int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column; - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } - if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + for (int c = 0; c < carets.size(); c++) { + if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection + int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); + int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } + if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + draw_rect(rect, selection_color, true); } - draw_rect(rect, selection_color, true); } } @@ -1203,34 +1214,38 @@ void TextEdit::_notification(int p_what) { } Color gl_color = current_color; - if (selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection - int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column; - int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column; + for (int c = 0; c < carets.size(); c++) { + if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection + int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); + int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); - if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { - gl_color = font_selected_color; + if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { + gl_color = font_selected_color; + } } } float char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { - if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) || - (caret.column == glyphs[j].start && caret.line == line && caret_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { - if (brace_open_mismatch) { - gl_color = brace_mismatch_color; + for (int c = 0; c < carets.size(); c++) { + if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) { + if (brace_matching[c].open_mismatch) { + gl_color = brace_mismatch_color; + } + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); + draw_rect(rect, gl_color); } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); - draw_rect(rect, gl_color); - } - if ((brace_close_match_line == line && brace_close_match_column == glyphs[j].start) || - (caret.column == glyphs[j].start + 1 && caret.line == line && caret_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { - if (brace_close_mismatch) { - gl_color = brace_mismatch_color; + if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) { + if (brace_matching[c].close_mismatch) { + gl_color = brace_mismatch_color; + } + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); + draw_rect(rect, gl_color); } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); - draw_rect(rect, gl_color); } } @@ -1289,137 +1304,139 @@ void TextEdit::_notification(int p_what) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale()); - if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) { - caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); + for (int c = 0; c < carets.size(); c++) { + if (!clipped && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index) { + carets.write[c].draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); - if (ime_text.length() == 0) { - CaretInfo ts_caret; - if (str.length() != 0) { - // Get carets. - ts_caret = TS->shaped_text_get_carets(rid, caret.column); - } else { - // No carets, add one at the start. - int h = font->get_height(font_size); - if (rtl) { - ts_caret.l_dir = TextServer::DIRECTION_RTL; - ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h)); + if (ime_text.length() == 0) { + CaretInfo ts_caret; + if (str.length() != 0) { + // Get carets. + ts_caret = TS->shaped_text_get_carets(rid, get_caret_column(c)); } else { - ts_caret.l_dir = TextServer::DIRECTION_LTR; - ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h)); + // No carets, add one at the start. + int h = font->get_height(font_size); + if (rtl) { + ts_caret.l_dir = TextServer::DIRECTION_RTL; + ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h)); + } else { + ts_caret.l_dir = TextServer::DIRECTION_LTR; + ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h)); + } } - } - if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { - caret.draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x; - } else { - caret.draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x; - } + if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { + carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x; + } else { + carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x; + } - if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) { - caret.visible = true; - if (draw_caret || drag_caret_force_displayed) { - if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { - //Block or underline caret, draw trailing carets at full height. - int h = font->get_height(font_size); - - if (ts_caret.t_caret != Rect2()) { - if (overtype_mode) { - ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid); - ts_caret.t_caret.size.y = caret_width; - } else { - ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid); - ts_caret.t_caret.size.y = h; - } - ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); - draw_rect(ts_caret.t_caret, caret_color, overtype_mode); + if (get_caret_draw_pos(c).x >= xmargin_beg && get_caret_draw_pos(c).x < xmargin_end) { + carets.write[c].visible = true; + if (draw_caret || drag_caret_force_displayed) { + if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { + //Block or underline caret, draw trailing carets at full height. + int h = font->get_height(font_size); - if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { - ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, caret_color * Color(1, 1, 1, 0.5)); - } - } else { // End of the line. - if (gl_size > 0) { - // Adjust for actual line dimensions. + if (ts_caret.t_caret != Rect2()) { if (overtype_mode) { - ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.t_caret.size.y = caret_width; + } else { + ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid); + ts_caret.t_caret.size.y = h; + } + ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + draw_rect(ts_caret.t_caret, caret_color, overtype_mode); + + if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { + ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + ts_caret.l_caret.size.x = caret_width; + draw_rect(ts_caret.l_caret, caret_color * Color(1, 1, 1, 0.5)); + } + } else { // End of the line. + if (gl_size > 0) { + // Adjust for actual line dimensions. + if (overtype_mode) { + ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.l_caret.size.y = caret_width; + } else { + ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid); + ts_caret.l_caret.size.y = h; + } + } else if (overtype_mode) { + ts_caret.l_caret.position.y += ts_caret.l_caret.size.y; ts_caret.l_caret.size.y = caret_width; + } + if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { + ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; } else { - ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid); - ts_caret.l_caret.size.y = h; + ts_caret.l_caret.size.x = 3 * caret_width; + } + ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + if (ts_caret.l_dir == TextServer::DIRECTION_RTL) { + ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x; } - } else if (overtype_mode) { - ts_caret.l_caret.position.y += ts_caret.l_caret.size.y; - ts_caret.l_caret.size.y = caret_width; + draw_rect(ts_caret.l_caret, caret_color, overtype_mode); } - if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { - ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; - } else { - ts_caret.l_caret.size.x = 3 * caret_width; + } else { + // Normal caret. + if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) { + // Draw extra marker on top of mid caret. + Rect2 trect = Rect2(ts_caret.l_caret.position.x - 3 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - if (ts_caret.l_dir == TextServer::DIRECTION_RTL) { - ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x; - } - draw_rect(ts_caret.l_caret, caret_color, overtype_mode); - } - } else { - // Normal caret. - if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) { - // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(ts_caret.l_caret.position.x - 3 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); - trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); - } - ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.l_caret.size.x = caret_width; + ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, caret_color); + draw_rect(ts_caret.l_caret, caret_color); - ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.t_caret.size.x = caret_width; + ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + ts_caret.t_caret.size.x = caret_width; - draw_rect(ts_caret.t_caret, caret_color); + draw_rect(ts_caret.t_caret, caret_color); + } } } - } - } else { - { - // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column, caret.column + ime_text.length()); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } else if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + } else { + { + // IME Intermediate text range. + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } else if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + rect.size.y = caret_width; + draw_rect(rect, caret_color); + carets.write[c].draw_pos.x = rect.position.x; } - rect.size.y = caret_width; - draw_rect(rect, caret_color); - caret.draw_pos.x = rect.position.x; } - } - { - // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column + ime_selection.x, caret.column + ime_selection.x + ime_selection.y); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } else if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + { + // IME caret. + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } else if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + rect.size.y = caret_width * 3; + draw_rect(rect, caret_color); + carets.write[c].draw_pos.x = rect.position.x; } - rect.size.y = caret_width * 3; - draw_rect(rect, caret_color); - caret.draw_pos.x = rect.position.x; } } } @@ -1434,7 +1451,7 @@ void TextEdit::_notification(int p_what) { if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret.draw_pos, get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id()); } } } break; @@ -1455,13 +1472,13 @@ void TextEdit::_notification(int p_what) { int caret_start = -1; int caret_end = -1; - if (!selection.active) { - String full_text = _base_get_text(0, 0, caret.line, caret.column); + if (!has_selection(0)) { + String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); caret_start = full_text.length(); } else { - String pre_text = _base_get_text(0, 0, selection.from_line, selection.from_column); - String post_text = get_selected_text(); + String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); + String post_text = get_selected_text(0); caret_start = pre_text.length(); caret_end = caret_start + post_text.length(); @@ -1483,14 +1500,16 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { ime_text = ""; ime_selection = Point2(); - text.invalidate_cache(caret.line, caret.column, true, ime_text); + for (int i = 0; i < carets.size(); i++) { + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text); + } } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } - if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { + if (deselect_on_focus_loss_enabled && !selection_drag_attempt) { deselect(); } } break; @@ -1500,20 +1519,21 @@ void TextEdit::_notification(int p_what) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); - String t; - if (caret.column >= 0) { - t = text[caret.line].substr(0, caret.column) + ime_text + text[caret.line].substr(caret.column, text[caret.line].length()); - } else { - t = ime_text; + for (int i = 0; i < carets.size(); i++) { + String t; + if (get_caret_column(i) >= 0) { + t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length()); + } else { + t = ime_text; + } + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t)); } - - text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t)); queue_redraw(); } } break; case NOTIFICATION_DRAG_BEGIN: { - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + selecting_mode = SelectionMode::SELECTION_MODE_NONE; drag_action = true; dragging_minimap = false; dragging_selection = false; @@ -1523,8 +1543,8 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_DRAG_END: { if (is_drag_successful()) { - if (selection.drag_attempt) { - selection.drag_attempt = false; + if (selection_drag_attempt) { + selection_drag_attempt = false; if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { delete_selection(); } else if (deselect_on_focus_loss_enabled) { @@ -1532,7 +1552,7 @@ void TextEdit::_notification(int p_what) { } } } else { - selection.drag_attempt = false; + selection_drag_attempt = false; } drag_action = false; drag_caret_force_displayed = false; @@ -1672,77 +1692,103 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - int prev_col = caret.column; - int prev_line = caret.line; - - set_caret_line(row, false, false); - set_caret_column(col); - selection.drag_attempt = false; - - if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { - if (!selection.active) { - selection.active = true; - selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; - selection.from_column = prev_col; - selection.from_line = prev_line; - selection.to_column = caret.column; - selection.to_line = caret.line; - - if (selection.from_line > selection.to_line || (selection.from_line == selection.to_line && selection.from_column > selection.to_column)) { - SWAP(selection.from_column, selection.to_column); - SWAP(selection.from_line, selection.to_line); - selection.shiftclick_left = false; + int caret = carets.size() - 1; + int prev_col = get_caret_column(caret); + int prev_line = get_caret_line(caret); + + const int triple_click_timeout = 600; + const int triple_click_tolerance = 5; + bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance); + + if (!is_mouse_over_selection() && !mb->is_double_click() && !is_triple_click) { + if (mb->is_alt_pressed()) { + prev_line = row; + prev_col = col; + + caret = add_caret(row, col); + if (caret == -1) { + return; + } + + carets.write[caret].selection.selecting_line = row; + carets.write[caret].selection.selecting_column = col; + + last_dblclk = 0; + } else if (!mb->is_shift_pressed()) { + caret = 0; + remove_secondary_carets(); + } + } + + set_caret_line(row, false, true, 0, caret); + set_caret_column(col, false, caret); + selection_drag_attempt = false; + + if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) { + if (!has_selection(caret)) { + carets.write[caret].selection.active = true; + selecting_mode = SelectionMode::SELECTION_MODE_POINTER; + carets.write[caret].selection.from_column = prev_col; + carets.write[caret].selection.from_line = prev_line; + carets.write[caret].selection.to_column = carets[caret].column; + carets.write[caret].selection.to_line = carets[caret].line; + + if (carets[caret].selection.from_line > carets[caret].selection.to_line || (carets[caret].selection.from_line == carets[caret].selection.to_line && carets[caret].selection.from_column > carets[caret].selection.to_column)) { + SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); + SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); + carets.write[caret].selection.shiftclick_left = false; } else { - selection.shiftclick_left = true; + carets.write[caret].selection.shiftclick_left = true; } - selection.selecting_line = prev_line; - selection.selecting_column = prev_col; + carets.write[caret].selection.selecting_line = prev_line; + carets.write[caret].selection.selecting_column = prev_col; + caret_index_edit_dirty = true; + merge_overlapping_carets(); queue_redraw(); } else { - if (caret.line < selection.selecting_line || (caret.line == selection.selecting_line && caret.column < selection.selecting_column)) { - if (selection.shiftclick_left) { - selection.shiftclick_left = !selection.shiftclick_left; + if (carets[caret].line < carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column < carets[caret].selection.selecting_column)) { + if (carets[caret].selection.shiftclick_left) { + carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; } - selection.from_column = caret.column; - selection.from_line = caret.line; - - } else if (caret.line > selection.selecting_line || (caret.line == selection.selecting_line && caret.column > selection.selecting_column)) { - if (!selection.shiftclick_left) { - SWAP(selection.from_column, selection.to_column); - SWAP(selection.from_line, selection.to_line); - selection.shiftclick_left = !selection.shiftclick_left; + carets.write[caret].selection.from_column = carets[caret].column; + carets.write[caret].selection.from_line = carets[caret].line; + + } else if (carets[caret].line > carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column > carets[caret].selection.selecting_column)) { + if (!carets[caret].selection.shiftclick_left) { + SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); + SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); + carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; } - selection.to_column = caret.column; - selection.to_line = caret.line; + carets.write[caret].selection.to_column = carets[caret].column; + carets.write[caret].selection.to_line = carets[caret].line; } else { - selection.active = false; + deselect(caret); } - + caret_index_edit_dirty = true; + merge_overlapping_carets(); queue_redraw(); } } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; - selection.drag_attempt = true; - } else { - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; - selection.selecting_line = row; - selection.selecting_column = col; + set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret); + // We use the main caret for dragging, so reset this one. + set_caret_line(prev_line, false, true, 0, caret); + set_caret_column(prev_col, false, caret); + selection_drag_attempt = true; + } else if (caret == 0) { + deselect(); + set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col); } - const int triple_click_timeout = 600; - const int triple_click_tolerance = 5; - - if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) { + if (is_triple_click) { // Triple-click select line. - selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; - selection.drag_attempt = false; + selecting_mode = SelectionMode::SELECTION_MODE_LINE; + selection_drag_attempt = false; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_double_click() && text[caret.line].length()) { + } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) { // Double-click select word. - selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD; + selecting_mode = SelectionMode::SELECTION_MODE_WORD; _update_selection_mode_word(); last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); @@ -1760,23 +1806,25 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; + int caret = carets.size() - 1; if (is_move_caret_on_right_click_enabled()) { - if (has_selection()) { - int from_line = get_selection_from_line(); - int to_line = get_selection_to_line(); - int from_column = get_selection_from_column(); - int to_column = get_selection_to_column(); + if (has_selection(caret)) { + int from_line = get_selection_from_line(caret); + int to_line = get_selection_to_line(caret); + int from_column = get_selection_from_column(caret); + int to_column = get_selection_to_column(caret); if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { // Right click is outside the selected text. - deselect(); + deselect(caret); } } - if (!has_selection()) { - set_caret_line(row, true, false); - set_caret_column(col); + if (!has_selection(caret)) { + set_caret_line(row, true, false, 0, caret); + set_caret_column(col, true, caret); } + merge_overlapping_carets(); } if (context_menu_enabled) { @@ -1789,15 +1837,15 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } else { if (mb->get_button_index() == MouseButton::LEFT) { - if (selection.drag_attempt && is_mouse_over_selection()) { - selection.active = false; + if (selection_drag_attempt && is_mouse_over_selection()) { + deselect(); } dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); if (!drag_action) { - selection.drag_attempt = false; + selection_drag_attempt = false; } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); @@ -1841,7 +1889,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (!dragging_minimap) { - switch (selection.selecting_mode) { + switch (selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); } break; @@ -1887,8 +1935,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { drag_caret_force_displayed = true; Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - set_caret_line(pos.y, false); - set_caret_column(pos.x); + set_caret_line(pos.y, false, true, 0, 0); + set_caret_column(pos.x, true, 0); dragging_selection = true; } } @@ -1923,8 +1971,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { // * No Modifiers are pressed (except shift) bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); - selection.selecting_text = false; - // Check and handle all built in shortcuts. // NEWLINES. @@ -2146,7 +2192,9 @@ void TextEdit::_swap_current_input_direction() { } else { input_direction = TEXT_DIRECTION_LTR; } - set_caret_column(caret.column); + for (int i = 0; i < carets.size(); i++) { + set_caret_column(get_caret_column(i), i == 0, i); + } queue_redraw(); } @@ -2156,325 +2204,429 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { } begin_complex_operation(); - - bool first_line = false; - if (!p_split_current_line) { - deselect(); - if (p_above) { - if (caret.line > 0) { - set_caret_line(caret.line - 1, false); - set_caret_column(text[caret.line].length()); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + bool first_line = false; + if (!p_split_current_line) { + deselect(i); + if (p_above) { + if (get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } else { + set_caret_column(0, i == 0, i); + first_line = true; + } } else { - set_caret_column(0); - first_line = true; + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } - } else { - set_caret_column(text[caret.line].length()); } - } - insert_text_at_caret("\n"); + insert_text_at_caret("\n", i); - if (first_line) { - set_caret_line(0); + if (first_line) { + set_caret_line(0, i == 0, true, 0, i); + } } - end_complex_operation(); } void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else if (selection.active && !p_move_by_word) { - // If a selection is active, move caret to start of selection - set_caret_line(selection.from_line); - set_caret_column(selection.from_column); - deselect(); - return; - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = caret.column; - // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. - if (cc == 0 && caret.line > 0) { - set_caret_line(caret.line - 1); - set_caret_column(text[caret.line].length()); + for (int i = 0; i < carets.size(); i++) { + // Handle selection. + if (p_select) { + _pre_shift_selection(i); + } else if (has_selection(i) && !p_move_by_word) { + // If a selection is active, move caret to start of selection. + set_caret_line(get_selection_from_line(i), false, true, 0, i); + set_caret_column(get_selection_from_column(i), i == 0, i); + deselect(i); + continue; } else { - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || cc <= words[0]) { - // This solves the scenario where there are no words but glyfs that can be ignored. - cc = 0; + deselect(i); + } + + if (p_move_by_word) { + int cc = get_caret_column(i); + // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. + if (cc == 0 && get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } else { - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); + if (words.is_empty() || cc <= words[0]) { + // This solves the scenario where there are no words but glyfs that can be ignored. + cc = 0; + } else { + for (int j = words.size() - 2; j >= 0; j = j - 2) { + if (words[j] < cc) { + cc = words[j]; + break; + } } } - } - set_caret_column(cc); - } - } else { - // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. - if (caret.column == 0) { - if (caret.line > 0) { - set_caret_line(caret.line - get_next_visible_line_offset_from(CLAMP(caret.line - 1, 0, text.size() - 1), -1)); - set_caret_column(text[caret.line].length()); + set_caret_column(cc, i == 0, i); } } else { - if (caret_mid_grapheme_enabled) { - set_caret_column(get_caret_column() - 1); + // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. + if (get_caret_column(i) == 0) { + if (get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } } else { - set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } else { + set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i); + } } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else if (selection.active && !p_move_by_word) { - // If a selection is active, move caret to end of selection - set_caret_line(selection.to_line); - set_caret_column(selection.to_column); - deselect(); - return; - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = caret.column; - // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line. - if (cc == text[caret.line].length() && caret.line < text.size() - 1) { - set_caret_line(caret.line + 1); - set_caret_column(0); + for (int i = 0; i < carets.size(); i++) { + // Handle selection + if (p_select) { + _pre_shift_selection(i); + } else if (has_selection(i) && !p_move_by_word) { + // If a selection is active, move caret to end of selection + set_caret_line(get_selection_to_line(i), false, true, 0, i); + set_caret_column(get_selection_to_column(i), i == 0, i); + deselect(i); + continue; } else { - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || cc >= words[words.size() - 1]) { - // This solves the scenario where there are no words but glyfs that can be ignored. - cc = text[caret.line].length(); + deselect(i); + } + + if (p_move_by_word) { + int cc = get_caret_column(i); + // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line. + if (cc == text[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) { + set_caret_line(get_caret_line(i) + 1, false, true, 0, i); + set_caret_column(0, i == 0, i); } else { - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); + if (words.is_empty() || cc >= words[words.size() - 1]) { + // This solves the scenario where there are no words but glyfs that can be ignored. + cc = text[get_caret_line(i)].length(); + } else { + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > cc) { + cc = words[j]; + break; + } } } - } - set_caret_column(cc); - } - } else { - // If we are at the end of the line, move the caret to the next line down. - if (caret.column == text[caret.line].length()) { - if (caret.line < text.size() - 1) { - set_caret_line(get_caret_line() + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1), true, false); - set_caret_column(0); + set_caret_column(cc, i == 0, i); } } else { - if (caret_mid_grapheme_enabled) { - set_caret_column(get_caret_column() + 1); + // If we are at the end of the line, move the caret to the next line down. + if (get_caret_column(i) == text[get_caret_line(i)].length()) { + if (get_caret_line(i) < text.size() - 1) { + set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i); + set_caret_column(0, i == 0, i); + } } else { - set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column(i) + 1, i == 0, i); + } else { + set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i); + } } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - int cur_wrap_index = get_caret_wrap_index(); - if (cur_wrap_index > 0) { - set_caret_line(caret.line, true, false, cur_wrap_index - 1); - } else if (caret.line == 0) { - set_caret_column(0); - } else { - int new_line = caret.line - get_next_visible_line_offset_from(caret.line - 1, -1); - if (is_line_wrapped(new_line)) { - set_caret_line(new_line, true, false, get_line_wrap_count(new_line)); + int cur_wrap_index = get_caret_wrap_index(i); + if (cur_wrap_index > 0) { + set_caret_line(get_caret_line(i), true, false, cur_wrap_index - 1, i); + } else if (get_caret_line(i) == 0) { + set_caret_column(0, i == 0, i); } else { - set_caret_line(new_line, true, false); + int new_line = get_caret_line(i) - get_next_visible_line_offset_from(get_caret_line(i) - 1, -1); + if (is_line_wrapped(new_line)) { + set_caret_line(new_line, i == 0, false, get_line_wrap_count(new_line), i); + } else { + set_caret_line(new_line, i == 0, false, 0, i); + } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - int cur_wrap_index = get_caret_wrap_index(); - if (cur_wrap_index < get_line_wrap_count(caret.line)) { - set_caret_line(caret.line, true, false, cur_wrap_index + 1); - } else if (caret.line == get_last_unhidden_line()) { - set_caret_column(text[caret.line].length()); - } else { - int new_line = caret.line + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1); - set_caret_line(new_line, true, false, 0); - } + int cur_wrap_index = get_caret_wrap_index(i); + if (cur_wrap_index < get_line_wrap_count(get_caret_line(i))) { + set_caret_line(get_caret_line(i), i == 0, false, cur_wrap_index + 1, i); + } else if (get_caret_line(i) == get_last_unhidden_line()) { + set_caret_column(text[get_caret_line(i)].length()); + } else { + int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1); + set_caret_line(new_line, i == 0, false, 0, i); + } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_start(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - // Move caret column to start of wrapped row and then to start of text. - Vector<String> rows = get_line_wrapped_text(caret.line); - int wi = get_caret_wrap_index(); - int row_start_col = 0; - for (int i = 0; i < wi; i++) { - row_start_col += rows[i].length(); - } - if (caret.column == row_start_col || wi == 0) { - // Compute whitespace symbols sequence length. - int current_line_whitespace_len = get_first_non_whitespace_column(caret.line); - if (get_caret_column() == current_line_whitespace_len) { - set_caret_column(0); + // Move caret column to start of wrapped row and then to start of text. + Vector<String> rows = get_line_wrapped_text(get_caret_line(i)); + int wi = get_caret_wrap_index(i); + int row_start_col = 0; + for (int j = 0; j < wi; j++) { + row_start_col += rows[j].length(); + } + if (get_caret_column(i) == row_start_col || wi == 0) { + // Compute whitespace symbols sequence length. + int current_line_whitespace_len = get_first_non_whitespace_column(get_caret_line(i)); + if (get_caret_column(i) == current_line_whitespace_len) { + set_caret_column(0, i == 0, i); + } else { + set_caret_column(current_line_whitespace_len, i == 0, i); + } } else { - set_caret_column(current_line_whitespace_len); + set_caret_column(row_start_col, i == 0, i); } - } else { - set_caret_column(row_start_col); - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_end(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - // Move caret column to end of wrapped row and then to end of text. - Vector<String> rows = get_line_wrapped_text(caret.line); - int wi = get_caret_wrap_index(); - int row_end_col = -1; - for (int i = 0; i < wi + 1; i++) { - row_end_col += rows[i].length(); - } - if (wi == rows.size() - 1 || caret.column == row_end_col) { - set_caret_column(text[caret.line].length()); - } else { - set_caret_column(row_end_col); - } + // Move caret column to end of wrapped row and then to end of text. + Vector<String> rows = get_line_wrapped_text(get_caret_line(i)); + int wi = get_caret_wrap_index(i); + int row_end_col = -1; + for (int j = 0; j < wi + 1; j++) { + row_end_col += rows[j].length(); + } + if (wi == rows.size() - 1 || get_caret_column(i) == row_end_col) { + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } else { + set_caret_column(row_end_col, i == 0, i); + } - if (p_select) { - _post_shift_selection(); + carets.write[i].last_fit_x = INT_MAX; + + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_page_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), -get_visible_line_count()); - int n_line = caret.line - next_line.x + 1; - set_caret_line(n_line, true, false, next_line.y); + Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count()); + int n_line = get_caret_line(i) - next_line.x + 1; + set_caret_line(n_line, i == 0, false, next_line.y, i); - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_page_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), get_visible_line_count()); - int n_line = caret.line + next_line.x - 1; - set_caret_line(n_line, true, false, next_line.y); + Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count()); + int n_line = get_caret_line(i) + next_line.x - 1; + set_caret_line(n_line, i == 0, false, next_line.y, i); - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { - if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) { + if (!editable) { return; } - if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) { - backspace(); - return; - } + start_action(EditAction::ACTION_BACKSPACE); + Vector<int> carets_to_remove; - if (p_all_to_left) { - int caret_current_column = caret.column; - set_caret_column(0); - _remove_text(caret.line, 0, caret.line, caret_current_column); - return; - } + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int caret_idx = caret_edit_order[i]; + if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) { + continue; + } - if (p_word) { - int column = caret.column; - // Check for the case "<word><space><caret>" and ignore the space. - // No need to check for column being 0 since it is checked above. - if (is_whitespace(text[caret.line][caret.column - 1])) { - column -= 1; + if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) { + backspace(caret_idx); + continue; } - // Get a list with the indices of the word bounds of the given text line. - const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || column <= words[0]) { - // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. - column = 0; - } else { - // Otherwise search for the first word break that is smaller than the index from which we're currently deleting. - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < column) { - column = words[i]; + + if (p_all_to_left) { + int caret_current_column = get_caret_column(caret_idx); + set_caret_column(0, caret_idx == 0, caret_idx); + _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column); + adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx)); + + // Check for any overlapping carets since we removed the entire line. + for (int j = i + 1; j < caret_edit_order.size(); j++) { + // Selection only end on this line, only the one as carets cannot overlap. + if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) { + carets.write[caret_edit_order[j]].selection.to_column = 0; + break; + } + + // Check for caret. + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { break; } + + deselect(caret_edit_order[j]); + carets_to_remove.push_back(caret_edit_order[j]); + set_caret_column(0, caret_idx == 0, caret_idx); + i = j; } + continue; } - _remove_text(caret.line, column, caret.line, caret.column); + if (p_word) { + // Save here as the caret may change when resolving overlaps. + int from_column = get_caret_column(caret_idx); + int column = get_caret_column(caret_idx); + // Check for the case "<word><space><caret>" and ignore the space. + // No need to check for column being 0 since it is checked above. + if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) { + column -= 1; + } + // Get a list with the indices of the word bounds of the given text line. + const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_idx))->get_rid()); + if (words.is_empty() || column <= words[0]) { + // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. + column = 0; + } else { + // Otherwise search for the first word break that is smaller than the index from we're currently deleting. + for (int c = words.size() - 2; c >= 0; c = c - 2) { + if (words[c] < column) { + column = words[c]; + break; + } + } + } - set_caret_line(caret.line, false); - set_caret_column(column); - return; + // Check for any other carets in this range. + int overlapping_caret_index = -1; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + // Check caret and selection in on the right line. + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { + break; + } + + // If it has a selection, check it ends with in the range. + if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) { + break; + } + + // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap. + if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) { + carets.write[caret_edit_order[j]].selection.to_column = column; + overlapping_caret_index = caret_edit_order[j]; + break; + } + + // Otherwise we can remove it. + if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) { + deselect(caret_edit_order[j]); + carets_to_remove.push_back(caret_edit_order[j]); + set_caret_column(0, caret_idx == 0, caret_idx); + i = j; + } + } + + _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column); + + set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(column, caret_idx == 0, caret_idx); + + // Now we can clean up the overlaping caret. + if (overlapping_caret_index != -1) { + backspace(overlapping_caret_index); + i++; + carets_to_remove.push_back(overlapping_caret_index); + set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx); + } + continue; + } + } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); } + end_action(); } void TextEdit::_delete(bool p_word, bool p_all_to_right) { @@ -2482,82 +2634,147 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { return; } - if (has_selection()) { - delete_selection(); - return; - } - int curline_len = text[caret.line].length(); - - if (caret.line == text.size() - 1 && caret.column == curline_len) { - return; // Last line, last column: Nothing to do. - } + start_action(EditAction::ACTION_DELETE); + Vector<int> carets_to_remove; - int next_line = caret.column < curline_len ? caret.line : caret.line + 1; - int next_column; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int caret_idx = caret_edit_order[i]; + if (has_selection(caret_idx)) { + delete_selection(caret_idx); + continue; + } + int curline_len = text[get_caret_line(caret_idx)].length(); - if (p_all_to_right) { - if (caret.column == curline_len) { - return; + if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) { + continue; // Last line, last column: Nothing to do. } - // Delete everything to right of caret - next_column = curline_len; - next_line = caret.line; - } else if (p_word && caret.column < curline_len - 1) { - // Delete next word to right of caret - int line = caret.line; - int column = caret.column; + int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1; + int next_column; - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > column) { - column = words[i]; - break; + if (p_all_to_right) { + // Get caret furthest to the left + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (!has_selection(caret_edit_order[j])) { + i = j; + caret_idx = caret_edit_order[i]; + } } - } - next_line = line; - next_column = column; - } else { - // Delete one character - if (caret_mid_grapheme_enabled) { - next_column = caret.column < curline_len ? (caret.column + 1) : 0; + if (get_caret_column(caret_idx) == curline_len) { + continue; + } + + // Delete everything to right of caret + next_column = curline_len; + next_line = get_caret_line(caret_idx); + + // Remove overlapping carets. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + carets_to_remove.push_back(caret_edit_order[j]); + } + + } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) { + // Delete next word to right of caret + int line = get_caret_line(caret_idx); + int column = get_caret_column(caret_idx); + + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > column) { + column = words[j]; + break; + } + } + + next_line = line; + next_column = column; + + // Remove overlapping carets. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (get_caret_column(caret_edit_order[j]) > column) { + break; + } + carets_to_remove.push_back(caret_edit_order[j]); + } } else { - next_column = caret.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), (caret.column)) : 0; + // Delete one character + if (caret_mid_grapheme_enabled) { + next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0; + } else { + next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0; + } + + // Remove overlapping carets. + if (i > 0) { + int prev_caret_idx = caret_edit_order[i - 1]; + if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) { + carets_to_remove.push_back(prev_caret_idx); + } + } } + + _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); + adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); + } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); } - _remove_text(caret.line, caret.column, next_line, next_column); + // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret. + merge_overlapping_carets(); + end_action(); queue_redraw(); } void TextEdit::_move_caret_document_start(bool p_select) { + remove_secondary_carets(); if (p_select) { - _pre_shift_selection(); + _pre_shift_selection(0); } else { deselect(); } - set_caret_line(0); + set_caret_line(0, false); set_caret_column(0); if (p_select) { - _post_shift_selection(); + _post_shift_selection(0); } } void TextEdit::_move_caret_document_end(bool p_select) { + remove_secondary_carets(); if (p_select) { - _pre_shift_selection(); + _pre_shift_selection(0); } else { deselect(); } set_caret_line(get_last_unhidden_line(), true, false, 9999); - set_caret_column(text[caret.line].length()); + set_caret_column(text[get_caret_line()].length()); if (p_select) { - _post_shift_selection(); + _post_shift_selection(0); } } @@ -2677,7 +2894,7 @@ bool TextEdit::is_text_field() const { } Variant TextEdit::get_drag_data(const Point2 &p_point) { - if (selection.active && selection.drag_attempt) { + if (has_selection() && selection_drag_attempt) { String t = get_selected_text(); Label *l = memnew(Label); l->set_text(t); @@ -2704,34 +2921,41 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); int caret_row_tmp = pos.y; int caret_column_tmp = pos.x; - if (selection.drag_attempt) { - selection.drag_attempt = false; + if (selection_drag_attempt) { + selection_drag_attempt = false; if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) { + // Set caret back at selection for undo / redo. + set_caret_line(get_selection_to_line(), false, false); + set_caret_column(get_selection_to_column()); + begin_complex_operation(); if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { - if (caret_row_tmp > selection.to_line) { - caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line); - } else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) { - caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column); + if (caret_row_tmp > get_selection_to_line()) { + caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line()); + } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) { + caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column()); } delete_selection(); } else { deselect(); } + remove_secondary_carets(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); end_complex_operation(); } } else if (is_mouse_over_selection()) { - caret_row_tmp = selection.from_line; - caret_column_tmp = selection.from_column; + remove_secondary_carets(); + caret_row_tmp = get_selection_from_line(); + caret_column_tmp = get_selection_from_column(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); grab_focus(); } else { + remove_secondary_carets(); deselect(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); @@ -2739,8 +2963,8 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { grab_focus(); } - if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) { - select(caret_row_tmp, caret_column_tmp, caret.line, caret.column); + if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) { + select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column()); } } } @@ -2976,6 +3200,7 @@ void TextEdit::clear() { void TextEdit::_clear() { if (editable && undo_enabled) { + remove_secondary_carets(); _move_caret_document_start(false); begin_complex_operation(); @@ -2991,13 +3216,14 @@ void TextEdit::_clear() { clear_undo_history(); text.clear(); + remove_secondary_carets(); set_caret_line(0, false); set_caret_column(0); - caret.x_ofs = 0; - caret.line_ofs = 0; - caret.wrap_ofs = 0; - caret.last_fit_x = 0; - selection.active = false; + first_visible_col = 0; + first_visible_line = 0; + first_visible_line_wrap_ofs = 0; + carets.write[0].last_fit_x = 0; + deselect(); emit_signal(SNAME("lines_edited_from"), old_text_size, 0); } @@ -3010,6 +3236,7 @@ void TextEdit::set_text(const String &p_text) { } if (undo_enabled) { + remove_secondary_carets(); set_caret_line(0); set_caret_column(0); @@ -3065,11 +3292,14 @@ void TextEdit::set_line(int p_line, const String &p_new_text) { begin_complex_operation(); _remove_text(p_line, 0, p_line, text[p_line].length()); _insert_text(p_line, 0, p_new_text); - if (caret.line == p_line && caret.column > p_new_text.length()) { - set_caret_column(p_new_text.length(), false); - } - if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { - selection.to_column = text[p_line].length(); + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) { + set_caret_column(p_new_text.length(), false, i); + } + + if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) { + carets.write[i].selection.to_column = text[p_line].length(); + } } end_complex_operation(); } @@ -3136,42 +3366,54 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { ERR_FAIL_INDEX(p_at, text.size()); _insert_text(p_at, 0, p_text + "\n"); - if (caret.line >= p_at) { - // offset caret when located after inserted line - set_caret_line(caret.line + 1, false); - } - if (has_selection()) { - if (selection.from_line >= p_at) { - // offset selection when located after inserted line - ++selection.from_line; - ++selection.to_line; - } else if (selection.to_line >= p_at) { - // extend selection that includes inserted line - ++selection.to_line; + + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) >= p_at) { + // offset caret when located after inserted line + set_caret_line(get_caret_line(i) + 1, false, true, 0, i); + } + if (has_selection(i)) { + if (get_selection_from_line(i) >= p_at) { + // offset selection when located after inserted line + select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); + } else if (get_selection_to_line(i) >= p_at) { + // extend selection that includes inserted line + select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); + } } } + + // Need to apply the above adjustments to the undo / redo carets. + current_op.end_carets = carets; queue_redraw(); } -void TextEdit::insert_text_at_caret(const String &p_text) { - bool had_selection = has_selection(); - if (had_selection) { - begin_complex_operation(); - } +void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - delete_selection(); + delete_selection(i); - int new_column, new_line; - _insert_text(caret.line, caret.column, p_text, &new_line, &new_column); - _update_scrollbars(); + int from_line = get_caret_line(i); + int from_col = get_caret_column(i); - set_caret_line(new_line, false); - set_caret_column(new_column); - queue_redraw(); + int new_column, new_line; + _insert_text(from_line, from_col, p_text, &new_line, &new_column); + _update_scrollbars(); - if (had_selection) { - end_complex_operation(); + set_caret_line(new_line, false, true, 0, i); + set_caret_column(new_column, i == 0, i); + + adjust_carets_after_edit(i, new_line, new_column, from_line, from_col); } + end_complex_operation(); + queue_redraw(); } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -3295,46 +3537,46 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p } // Overridable actions -void TextEdit::handle_unicode_input(const uint32_t p_unicode) { - if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) { +void TextEdit::handle_unicode_input(const uint32_t p_unicode, int p_caret) { + if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode, p_caret)) { return; } - _handle_unicode_input_internal(p_unicode); + _handle_unicode_input_internal(p_unicode, p_caret); } -void TextEdit::backspace() { - if (GDVIRTUAL_CALL(_backspace)) { +void TextEdit::backspace(int p_caret) { + if (GDVIRTUAL_CALL(_backspace, p_caret)) { return; } - _backspace_internal(); + _backspace_internal(p_caret); } -void TextEdit::cut() { - if (GDVIRTUAL_CALL(_cut)) { +void TextEdit::cut(int p_caret) { + if (GDVIRTUAL_CALL(_cut, p_caret)) { return; } - _cut_internal(); + _cut_internal(p_caret); } -void TextEdit::copy() { - if (GDVIRTUAL_CALL(_copy)) { +void TextEdit::copy(int p_caret) { + if (GDVIRTUAL_CALL(_copy, p_caret)) { return; } - _copy_internal(); + _copy_internal(p_caret); } -void TextEdit::paste() { - if (GDVIRTUAL_CALL(_paste)) { +void TextEdit::paste(int p_caret) { + if (GDVIRTUAL_CALL(_paste, p_caret)) { return; } - _paste_internal(); + _paste_internal(p_caret); } -void TextEdit::paste_primary_clipboard() { - if (GDVIRTUAL_CALL(_paste_primary_clipboard)) { +void TextEdit::paste_primary_clipboard(int p_caret) { + if (GDVIRTUAL_CALL(_paste_primary_clipboard, p_caret)) { return; } - _paste_primary_clipboard_internal(); + _paste_primary_clipboard_internal(p_caret); } // Context menu. @@ -3471,22 +3713,53 @@ void TextEdit::menu_option(int p_option) { } /* Versioning */ +void TextEdit::start_action(EditAction p_action) { + if (current_action != p_action) { + if (current_action != EditAction::ACTION_NONE) { + in_action = false; + pending_action_end = false; + end_complex_operation(); + } + + if (p_action != EditAction::ACTION_NONE) { + in_action = true; + begin_complex_operation(); + } + } else if (current_action != EditAction::ACTION_NONE) { + pending_action_end = false; + } + current_action = p_action; +} + +void TextEdit::end_action() { + if (current_action != EditAction::ACTION_NONE) { + pending_action_end = true; + } +} + +TextEdit::EditAction TextEdit::get_current_action() const { + return current_action; +} + void TextEdit::begin_complex_operation() { _push_current_op(); if (complex_operation_count == 0) { next_operation_is_complex = true; + current_op.start_carets = carets; } complex_operation_count++; } void TextEdit::end_complex_operation() { _push_current_op(); - ERR_FAIL_COND(undo_stack.size() == 0); complex_operation_count = MAX(complex_operation_count - 1, 0); if (complex_operation_count > 0) { return; } + ERR_FAIL_COND(undo_stack.size() == 0); + + undo_stack.back()->get().end_carets = carets; if (undo_stack.back()->get().chain_forward) { undo_stack.back()->get().chain_forward = false; return; @@ -3546,17 +3819,24 @@ void TextEdit::undo() { } } - if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) { - select(op.from_line, op.from_column, op.to_line, op.to_column); + _update_scrollbars(); + bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size(); + if (!dirty_carets) { + for (int i = 0; i < carets.size(); i++) { + if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) { + dirty_carets = true; + break; + } + } } - _update_scrollbars(); - if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { - set_caret_line(undo_stack_pos->get().to_line, false); - set_caret_column(undo_stack_pos->get().to_column); - } else { - set_caret_line(undo_stack_pos->get().from_line, false); - set_caret_column(undo_stack_pos->get().from_column); + carets = undo_stack_pos->get().start_carets; + + if (dirty_carets && !caret_pos_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); + } + caret_pos_dirty = true; } queue_redraw(); } @@ -3565,6 +3845,7 @@ void TextEdit::redo() { if (!editable) { return; } + _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3590,9 +3871,25 @@ void TextEdit::redo() { } _update_scrollbars(); - set_caret_line(undo_stack_pos->get().to_line, false); - set_caret_column(undo_stack_pos->get().to_column); + bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size(); + if (!dirty_carets) { + for (int i = 0; i < carets.size(); i++) { + if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) { + dirty_carets = true; + break; + } + } + } + + carets = undo_stack_pos->get().end_carets; undo_stack_pos = undo_stack_pos->next(); + + if (dirty_carets && !caret_pos_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); + } + caret_pos_dirty = true; + } queue_redraw(); } @@ -3604,7 +3901,7 @@ void TextEdit::clear_undo_history() { } bool TextEdit::is_insert_text_operation() const { - return (current_op.type == TextOperation::TYPE_INSERT); + return (current_op.type == TextOperation::TYPE_INSERT || current_action == EditAction::ACTION_TYPING); } void TextEdit::tag_saved_version() { @@ -3793,7 +4090,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int wrap_index = 0; if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows))); + Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))); wrap_index = f_ofs.y; if (rows < 0) { @@ -3821,7 +4118,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int col = 0; int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); - colx += caret.x_ofs; + colx += first_visible_col; col = _get_char_pos_for_line(colx, row, wrap_index); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { // Move back one if we are at the end of the row. @@ -3918,7 +4215,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { int row = minimap_line + Math::floor(rows); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1; + int f_ofs = get_next_visible_line_index_offset_from(minimap_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))).x - 1; if (rows < 0) { row = minimap_line - f_ofs; } else { @@ -3941,19 +4238,31 @@ bool TextEdit::is_dragging_cursor() const { return dragging_selection || dragging_minimap; } -bool TextEdit::is_mouse_over_selection(bool p_edges) const { - if (!has_selection()) { - return false; - } - Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - int row = pos.y; - int col = pos.x; - if (p_edges) { - if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) { +bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const { + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (!has_selection(i)) { + continue; + } + + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int row = pos.y; + int col = pos.x; + if (p_edges) { + if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) { + return true; + } + } + + if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) { return true; } } - return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column)); + + return false; } /* Caret */ @@ -4016,15 +4325,233 @@ bool TextEdit::is_caret_mid_grapheme_enabled() const { return caret_mid_grapheme_enabled; } -bool TextEdit::is_caret_visible() const { - return caret.visible; +void TextEdit::set_multiple_carets_enabled(bool p_enabled) { + multi_carets_enabled = p_enabled; + if (!multi_carets_enabled) { + remove_secondary_carets(); + } +} + +bool TextEdit::is_multiple_carets_enabled() const { + return multi_carets_enabled; +} + +int TextEdit::add_caret(int p_line, int p_col) { + if (!multi_carets_enabled) { + return -1; + } + + p_line = CLAMP(p_line, 0, text.size() - 1); + p_col = CLAMP(p_col, 0, get_line(p_line).length()); + + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) { + return -1; + } + + if (has_selection(i)) { + if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) { + return -1; + } + } + } + + carets.push_back(Caret()); + set_caret_line(p_line, false, false, 0, carets.size() - 1); + set_caret_column(p_col, false, carets.size() - 1); + caret_index_edit_dirty = true; + return carets.size() - 1; +} + +void TextEdit::remove_caret(int p_caret) { + ERR_FAIL_COND(carets.size() <= 0); + ERR_FAIL_INDEX(p_caret, carets.size()); + carets.remove_at(p_caret); + caret_index_edit_dirty = true; +} + +void TextEdit::remove_secondary_carets() { + carets.resize(1); + caret_index_edit_dirty = true; + queue_redraw(); +} + +void TextEdit::merge_overlapping_carets() { + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size() - 1; i++) { + int first_caret = caret_edit_order[i]; + int second_caret = caret_edit_order[i + 1]; + + // Both have selection. + if (has_selection(first_caret) && has_selection(second_caret)) { + bool should_merge = false; + if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) { + should_merge = true; + } + + if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) { + should_merge = true; + } + + if (!should_merge) { + continue; + } + + // Save the newest one for click+drag. + int caret_to_save = first_caret; + int caret_to_remove = second_caret; + if (first_caret < second_caret) { + caret_to_save = second_caret; + caret_to_remove = first_caret; + } + + int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove)); + int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove)); + int from_col = get_selection_from_column(caret_to_save); + int to_col = get_selection_to_column(caret_to_save); + int selection_line = get_selection_line(caret_to_save); + int selection_col = get_selection_column(caret_to_save); + + bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save)); + + if (at_from) { + if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) { + selection_line = get_selection_line(caret_to_remove); + selection_col = get_selection_column(caret_to_remove); + } + } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) { + selection_line = get_selection_line(caret_to_remove); + selection_col = get_selection_column(caret_to_remove); + } + + if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) { + from_col = get_selection_from_column(caret_to_remove); + } else { + to_col = get_selection_to_column(caret_to_remove); + } + + select(from_line, from_col, to_line, to_col, caret_to_save); + set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save); + set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save); + set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save); + remove_caret(caret_to_remove); + i--; + caret_edit_order = get_caret_index_edit_order(); + continue; + } + + // Only first has selection. + if (has_selection(first_caret)) { + if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) { + remove_caret(second_caret); + caret_edit_order = get_caret_index_edit_order(); + i--; + } + continue; + } + + // Only second has Selection. + if (has_selection(second_caret)) { + if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) { + remove_caret(first_caret); + caret_edit_order = get_caret_index_edit_order(); + i--; + } + continue; + } + + // Both have no selection. + if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) { + // Save the newest one for click+drag. + if (first_caret < second_caret) { + remove_caret(first_caret); + } else { + remove_caret(second_caret); + } + i--; + caret_edit_order = get_caret_index_edit_order(); + continue; + } + } } -Point2 TextEdit::get_caret_draw_pos() const { - return caret.draw_pos; +int TextEdit::get_caret_count() const { + return carets.size(); } -void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { +Vector<int> TextEdit::get_caret_index_edit_order() { + if (!caret_index_edit_dirty) { + return caret_index_edit_order; + } + + caret_index_edit_order.clear(); + caret_index_edit_order.push_back(0); + for (int i = 1; i < carets.size(); i++) { + int j = 0; + + int line = carets[i].selection.active ? carets[i].selection.to_line : carets[i].line; + int col = carets[i].selection.active ? carets[i].selection.to_column : carets[i].column; + + for (; j < caret_index_edit_order.size(); j++) { + int idx = caret_index_edit_order[j]; + int other_line = carets[idx].selection.active ? carets[idx].selection.to_line : carets[idx].line; + int other_col = carets[idx].selection.active ? carets[idx].selection.to_column : carets[idx].column; + if (line > other_line || (line == other_line && col > other_col)) { + break; + } + } + caret_index_edit_order.insert(j, i); + } + caret_index_edit_dirty = false; + return caret_index_edit_order; +} + +void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) { + int edit_height = p_from_line - p_to_line; + int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col; + + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int j = 0; j < caret_edit_order.size(); j++) { + if (caret_edit_order[j] == p_caret) { + return; + } + + // Adjust caret. + // set_caret_line could adjust the column, so save here. + int cc = get_caret_column(caret_edit_order[j]); + if (edit_height != 0) { + set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]); + } + if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) { + set_caret_column(cc + edit_size, false, caret_edit_order[j]); + } + + // Adjust selection. + if (!has_selection(caret_edit_order[j])) { + continue; + } + if (edit_height != 0) { + carets.write[caret_edit_order[j]].selection.from_line += edit_height; + carets.write[caret_edit_order[j]].selection.to_line += edit_height; + } + if (get_caret_line(p_caret) == carets[caret_edit_order[j]].selection.from_line) { + carets.write[caret_edit_order[j]].selection.from_column += edit_size; + } + } +} + +bool TextEdit::is_caret_visible(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].visible; +} + +Point2 TextEdit::get_caret_draw_pos(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), Point2(0, 0)); + return carets[p_caret].draw_pos; +} + +void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (setting_caret_line) { return; } @@ -4053,10 +4580,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } } } - bool caret_moved = caret.line != p_line; - caret.line = p_line; + bool caret_moved = get_caret_line(p_caret) != p_line; + carets.write[p_caret].line = p_line; - int n_col = _get_char_pos_for_line(caret.last_fit_x, p_line, p_wrap_index); + int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index); if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { Vector<String> rows = get_line_wrapped_text(p_line); int row_end_col = 0; @@ -4067,11 +4594,11 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ n_col -= 1; } } - caret_moved = (caret_moved || caret.column != n_col); - caret.column = n_col; + caret_moved = (caret_moved || get_caret_column(p_caret) != n_col); + carets.write[p_caret].column = n_col; if (is_inside_tree() && p_adjust_viewport) { - adjust_viewport_to_caret(); + adjust_viewport_to_caret(p_caret); } setting_caret_line = false; @@ -4084,25 +4611,27 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } } -int TextEdit::get_caret_line() const { - return caret.line; +int TextEdit::get_caret_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].line; } -void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { +void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (p_col < 0) { p_col = 0; } - if (p_col > get_line(caret.line).length()) { - p_col = get_line(caret.line).length(); + if (p_col > get_line(get_caret_line(p_caret)).length()) { + p_col = get_line(get_caret_line(p_caret)).length(); } - bool caret_moved = caret.column != p_col; - caret.column = p_col; + bool caret_moved = get_caret_column(p_caret) != p_col; + carets.write[p_caret].column = p_col; - caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line); + carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); if (is_inside_tree() && p_adjust_viewport) { - adjust_viewport_to_caret(); + adjust_viewport_to_caret(p_caret); } if (caret_moved && !caret_pos_dirty) { @@ -4113,24 +4642,36 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { } } -int TextEdit::get_caret_column() const { - return caret.column; +int TextEdit::get_caret_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].column; } -int TextEdit::get_caret_wrap_index() const { - return get_line_wrap_index_at_column(caret.line, caret.column); +int TextEdit::get_caret_wrap_index(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return get_line_wrap_index_at_column(get_caret_line(p_caret), get_caret_column(p_caret)); } -String TextEdit::get_word_under_caret() const { - ERR_FAIL_INDEX_V(caret.line, text.size(), ""); - ERR_FAIL_INDEX_V(caret.column, text[caret.line].length() + 1, ""); - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if (words[i] <= caret.column && words[i + 1] > caret.column) { - return text[caret.line].substr(words[i], words[i + 1] - words[i]); +String TextEdit::get_word_under_caret(int p_caret) const { + ERR_FAIL_COND_V(p_caret > carets.size(), ""); + + StringBuilder selected_text; + for (int c = 0; c < carets.size(); c++) { + if (p_caret != -1 && p_caret != c) { + continue; + } + + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (words[i] <= get_caret_column(c) && words[i + 1] > get_caret_column(c)) { + selected_text += text[get_caret_line(c)].substr(words[i], words[i + 1] - words[i]); + if (p_caret == -1 && c != carets.size() - 1) { + selected_text += "\n"; + } + } } } - return ""; + return selected_text.as_string(); } /* Selection. */ @@ -4156,7 +4697,7 @@ void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { } deselect_on_focus_loss_enabled = p_enabled; - if (p_enabled && selection.active && !has_focus()) { + if (p_enabled && has_selection() && !has_focus()) { deselect(); } } @@ -4181,22 +4722,24 @@ bool TextEdit::is_overriding_selected_font_color() const { return override_selected_font_color; } -void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column) { - selection.selecting_mode = p_mode; +void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + + selecting_mode = p_mode; if (p_line >= 0) { ERR_FAIL_INDEX(p_line, text.size()); - selection.selecting_line = p_line; - selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length()); + carets.write[p_caret].selection.selecting_line = p_line; + carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length()); } if (p_column >= 0) { - ERR_FAIL_INDEX(selection.selecting_line, text.size()); - ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length()); - selection.selecting_column = p_column; + ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size()); + ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1); + carets.write[p_caret].selection.selecting_column = p_column; } } TextEdit::SelectionMode TextEdit::get_selection_mode() const { - return selection.selecting_mode; + return selecting_mode; } void TextEdit::select_all() { @@ -4207,21 +4750,19 @@ void TextEdit::select_all() { if (text.size() == 1 && text[0].length() == 0) { return; } - selection.active = true; - selection.from_line = 0; - selection.from_column = 0; - selection.selecting_line = 0; - selection.selecting_column = 0; - selection.to_line = text.size() - 1; - selection.to_column = text[selection.to_line].length(); - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; - selection.shiftclick_left = true; - set_caret_line(selection.to_line, false); - set_caret_column(selection.to_column, false); + + remove_secondary_carets(); + select(0, 0, text.size() - 1, text[text.size() - 1].length()); + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0); + carets.write[0].selection.shiftclick_left = true; + set_caret_line(get_selection_to_line(), false); + set_caret_column(get_selection_to_column(), false); queue_redraw(); } -void TextEdit::select_word_under_caret() { +void TextEdit::select_word_under_caret(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + if (!selecting_enabled) { return; } @@ -4230,36 +4771,43 @@ void TextEdit::select_word_under_caret() { return; } - if (selection.active) { - /* Allow toggling selection by pressing the shortcut a second time. */ - /* This is also usable as a general-purpose "deselect" shortcut after */ - /* selecting anything. */ - deselect(); - return; - } + for (int c = 0; c < carets.size(); c++) { + if (p_caret != -1 && p_caret != c) { + continue; + } - int begin = 0; - int end = 0; - const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { - begin = words[i]; - end = words[i + 1]; - break; + if (has_selection(c)) { + // Allow toggling selection by pressing the shortcut a second time. + // This is also usable as a general-purpose "deselect" shortcut after + // selecting anything. + deselect(c); + continue; } - } - // No word found. - if (begin == 0 && end == 0) { - return; - } + int begin = 0; + int end = 0; + const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if ((words[i] <= get_caret_column(c) && words[i + 1] >= get_caret_column(c)) || (i == words.size() - 2 && get_caret_column(c) == words[i + 1])) { + begin = words[i]; + end = words[i + 1]; + break; + } + } - select(caret.line, begin, caret.line, end); - /* Move the caret to the end of the word for easier editing. */ - set_caret_column(end, false); + // No word found. + if (begin == 0 && end == 0) { + continue; + } + + select(get_caret_line(c), begin, get_caret_column(c), end, c); + // Move the caret to the end of the word for easier editing. + set_caret_column(end, false, c); + } } -void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { +void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (!selecting_enabled) { return; } @@ -4288,91 +4836,143 @@ void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_t p_to_column = 0; } - selection.from_line = p_from_line; - selection.from_column = p_from_column; - selection.to_line = p_to_line; - selection.to_column = p_to_column; + carets.write[p_caret].selection.from_line = p_from_line; + carets.write[p_caret].selection.from_column = p_from_column; + carets.write[p_caret].selection.to_line = p_to_line; + carets.write[p_caret].selection.to_column = p_to_column; - selection.active = true; + carets.write[p_caret].selection.active = true; - if (selection.from_line == selection.to_line) { - if (selection.from_column == selection.to_column) { - selection.active = false; + if (carets[p_caret].selection.from_line == carets[p_caret].selection.to_line) { + if (carets[p_caret].selection.from_column == carets[p_caret].selection.to_column) { + carets.write[p_caret].selection.active = false; - } else if (selection.from_column > selection.to_column) { - selection.shiftclick_left = false; - SWAP(selection.from_column, selection.to_column); + } else if (carets[p_caret].selection.from_column > carets[p_caret].selection.to_column) { + carets.write[p_caret].selection.shiftclick_left = false; + SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); } else { - selection.shiftclick_left = true; + carets.write[p_caret].selection.shiftclick_left = true; } - } else if (selection.from_line > selection.to_line) { - selection.shiftclick_left = false; - SWAP(selection.from_line, selection.to_line); - SWAP(selection.from_column, selection.to_column); + } else if (carets[p_caret].selection.from_line > carets[p_caret].selection.to_line) { + carets.write[p_caret].selection.shiftclick_left = false; + SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line); + SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); } else { - selection.shiftclick_left = true; + carets.write[p_caret].selection.shiftclick_left = true; } + caret_index_edit_dirty = true; queue_redraw(); } -bool TextEdit::has_selection() const { - return selection.active; +bool TextEdit::has_selection(int p_caret) const { + ERR_FAIL_COND_V(p_caret > carets.size(), false); + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (carets[i].selection.active) { + return true; + } + } + return false; } -String TextEdit::get_selected_text() const { - if (!selection.active) { - return ""; +String TextEdit::get_selected_text(int p_caret) { + ERR_FAIL_COND_V(p_caret > carets.size(), ""); + + StringBuilder selected_text; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } + + if (!has_selection(caret_idx)) { + continue; + } + selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx)); + if (p_caret == -1 && i != 0) { + selected_text += "\n"; + } } - return _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); + return selected_text.as_string(); } -int TextEdit::get_selection_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.selecting_line; +int TextEdit::get_selection_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.selecting_line; } -int TextEdit::get_selection_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.selecting_column; +int TextEdit::get_selection_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.selecting_column; } -int TextEdit::get_selection_from_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_line; +int TextEdit::get_selection_from_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.from_line; } -int TextEdit::get_selection_from_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_column; +int TextEdit::get_selection_from_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.from_column; } -int TextEdit::get_selection_to_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_line; +int TextEdit::get_selection_to_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.to_line; } -int TextEdit::get_selection_to_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_column; +int TextEdit::get_selection_to_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.to_column; } -void TextEdit::deselect() { - selection.active = false; +void TextEdit::deselect(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + carets.write[i].selection.active = false; + } + caret_index_edit_dirty = true; queue_redraw(); } -void TextEdit::delete_selection() { - if (!has_selection()) { - return; - } +void TextEdit::delete_selection(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (!has_selection(i)) { + continue; + } - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - set_caret_line(selection.from_line, false, false); - set_caret_column(selection.from_column); + selecting_mode = SelectionMode::SELECTION_MODE_NONE; + _remove_text(carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column); + set_caret_line(carets[i].selection.from_line, false, false, 0, i); + set_caret_column(carets[i].selection.from_column, i == 0, i); + carets.write[i].selection.active = false; + + adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column); + } + end_complex_operation(); queue_redraw(); } @@ -4543,7 +5143,7 @@ void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { } int TextEdit::get_first_visible_line() const { - return CLAMP(caret.line_ofs, 0, text.size() - 1); + return CLAMP(first_visible_line, 0, text.size() - 1); } void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { @@ -4582,14 +5182,14 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { int TextEdit::get_last_full_visible_line() const { int first_vis_line = get_first_visible_line(); int last_vis_line = 0; - last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).x - 1; + last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).x - 1; last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); return last_vis_line; } int TextEdit::get_last_full_visible_line_wrap_index() const { int first_vis_line = get_first_visible_line(); - return get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).y; + return get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).y; } int TextEdit::get_visible_line_count() const { @@ -4625,16 +5225,18 @@ int TextEdit::get_total_visible_line_count() const { } // Auto adjust -void TextEdit::adjust_viewport_to_caret() { +void TextEdit::adjust_viewport_to_caret(int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + // Make sure Caret is visible on the screen. scrolling = false; minimap_clicked = false; - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); + int cur_line = get_caret_line(p_caret); + int cur_wrap = get_caret_wrap_index(p_caret); int first_vis_line = get_first_visible_line(); - int first_vis_wrap = caret.wrap_ofs; + int first_vis_wrap = first_visible_line_wrap_ofs; int last_vis_line = get_last_full_visible_line(); int last_vis_wrap = get_last_full_visible_line_wrap_index(); @@ -4661,43 +5263,45 @@ void TextEdit::adjust_viewport_to_caret() { // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); } } else { caret_pos.y = caret_pos.x; } - if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { - caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { + first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; } - if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { - caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { + first_visible_col = MIN(caret_pos.x, caret_pos.y); } } else { - caret.x_ofs = 0; + first_visible_col = 0; } - h_scroll->set_value(caret.x_ofs); + h_scroll->set_value(first_visible_col); queue_redraw(); } -void TextEdit::center_viewport_to_caret() { +void TextEdit::center_viewport_to_caret(int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + // Move viewport so the caret is in the center of the screen. scrolling = false; minimap_clicked = false; - set_line_as_center_visible(caret.line, get_caret_wrap_index()); + set_line_as_center_visible(get_caret_line(p_caret), get_caret_wrap_index(p_caret)); int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding; if (draw_minimap) { visible_width -= minimap_width; @@ -4714,33 +5318,33 @@ void TextEdit::center_viewport_to_caret() { // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); } } else { caret_pos.y = caret_pos.x; } - if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { - caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { + first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; } - if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { - caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { + first_visible_col = MIN(caret_pos.x, caret_pos.y); } } else { - caret.x_ofs = 0; + first_visible_col = 0; } - h_scroll->set_value(caret.x_ofs); + h_scroll->set_value(first_visible_col); queue_redraw(); } @@ -5187,7 +5791,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines); ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at); - ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &TextEdit::insert_text_at_caret); + ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text); @@ -5196,18 +5800,19 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_next_visible_line_index_offset_from", "line", "wrap_index", "visible_amount"), &TextEdit::get_next_visible_line_index_offset_from); // Overridable actions - ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); + ClassDB::bind_method(D_METHOD("backspace", "caret_index"), &TextEdit::backspace, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); - ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); - ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); + ClassDB::bind_method(D_METHOD("cut", "caret_index"), &TextEdit::cut, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("copy", "caret_index"), &TextEdit::copy, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("paste", "caret_index"), &TextEdit::paste, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("paste_primary_clipboard", "caret_index"), &TextEdit::paste_primary_clipboard, DEFVAL(-1)); - GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char") - GDVIRTUAL_BIND(_backspace) - GDVIRTUAL_BIND(_cut) - GDVIRTUAL_BIND(_copy) - GDVIRTUAL_BIND(_paste) - GDVIRTUAL_BIND(_paste_primary_clipboard) + GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char", "caret_index") + GDVIRTUAL_BIND(_backspace, "caret_index") + GDVIRTUAL_BIND(_cut, "caret_index") + GDVIRTUAL_BIND(_copy, "caret_index") + GDVIRTUAL_BIND(_paste, "caret_index") + GDVIRTUAL_BIND(_paste_primary_clipboard, "caret_index") // Context Menu BIND_ENUM_CONSTANT(MENU_CUT); @@ -5241,6 +5846,14 @@ void TextEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); /* Versioning */ + BIND_ENUM_CONSTANT(ACTION_NONE); + BIND_ENUM_CONSTANT(ACTION_TYPING); + BIND_ENUM_CONSTANT(ACTION_BACKSPACE); + BIND_ENUM_CONSTANT(ACTION_DELETE); + + ClassDB::bind_method(D_METHOD("start_action", "action"), &TextEdit::start_action); + ClassDB::bind_method(D_METHOD("end_action"), &TextEdit::end_complex_operation); + ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation); ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); @@ -5280,7 +5893,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); - ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection); + ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges", "caret_index"), &TextEdit::is_mouse_over_selection, DEFVAL(-1)); /* Caret. */ BIND_ENUM_CONSTANT(CARET_TYPE_LINE); @@ -5304,18 +5917,30 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &TextEdit::set_caret_mid_grapheme_enabled); ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &TextEdit::is_caret_mid_grapheme_enabled); - ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible); - ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos); + ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled); + ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled); + + ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret); + ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret); + ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets); + ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); + ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count); - ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_caret_line"), &TextEdit::get_caret_line); + ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); + ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); - ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport"), &TextEdit::set_caret_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_caret_column"), &TextEdit::get_caret_column); + ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_caret_wrap_index"), &TextEdit::get_caret_wrap_index); + ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_line", "caret_index"), &TextEdit::get_caret_line, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_word_under_caret"), &TextEdit::get_word_under_caret); + ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport", "caret_index"), &TextEdit::set_caret_column, DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_column", "caret_index"), &TextEdit::get_caret_column, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("get_caret_wrap_index", "caret_index"), &TextEdit::get_caret_wrap_index, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1)); /* Selection. */ BIND_ENUM_CONSTANT(SELECTION_MODE_NONE); @@ -5336,27 +5961,27 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); - ClassDB::bind_method(D_METHOD("select_word_under_caret"), &TextEdit::select_word_under_caret); - ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select); + ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_selection"), &TextEdit::has_selection); + ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_selected_text"), &TextEdit::get_selected_text); + ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_selection_line"), &TextEdit::get_selection_line); - ClassDB::bind_method(D_METHOD("get_selection_column"), &TextEdit::get_selection_column); + ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_selection_from_line"), &TextEdit::get_selection_from_line); - ClassDB::bind_method(D_METHOD("get_selection_from_column"), &TextEdit::get_selection_from_column); - ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line); - ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column); + ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); - ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); + ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1)); /* Line wrapping. */ BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE); @@ -5411,8 +6036,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count); // Auto adjust - ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret"), &TextEdit::adjust_viewport_to_caret); - ClassDB::bind_method(D_METHOD("center_viewport_to_caret"), &TextEdit::center_viewport_to_caret); + ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret", "caret_index"), &TextEdit::adjust_viewport_to_caret, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("center_viewport_to_caret", "caret_index"), &TextEdit::center_viewport_to_caret, DEFVAL(0)); // Minimap ClassDB::bind_method(D_METHOD("set_draw_minimap", "enabled"), &TextEdit::set_draw_minimap); @@ -5528,6 +6153,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); @@ -5613,157 +6239,238 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { /* Text manipulation */ // Overridable actions -void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { +void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - bool had_selection = has_selection(); - if (had_selection) { - begin_complex_operation(); - delete_selection(); - } - - /* Remove the old character if in insert mode and no selection. */ - if (overtype_mode && !had_selection) { - begin_complex_operation(); - - /* Make sure we don't try and remove empty space. */ - int cl = get_caret_line(); - int cc = get_caret_column(); - if (cc < get_line(cl).length()) { - _remove_text(cl, cc, cl, cc + 1); + start_action(EditAction::ACTION_TYPING); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; } - } - const char32_t chr[2] = { (char32_t)p_unicode, 0 }; - insert_text_at_caret(chr); + /* Remove the old character if in insert mode and no selection. */ + if (overtype_mode && !has_selection(i)) { + /* Make sure we don't try and remove empty space. */ + int cl = get_caret_line(i); + int cc = get_caret_column(i); + if (cc < get_line(cl).length()) { + _remove_text(cl, cc, cl, cc + 1); + } + } - if ((overtype_mode && !had_selection) || (had_selection)) { - end_complex_operation(); + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + insert_text_at_caret(chr, i); } + end_action(); } -void TextEdit::_backspace_internal() { +void TextEdit::_backspace_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - if (has_selection()) { - delete_selection(); + if (has_selection(p_caret)) { + delete_selection(p_caret); return; } - int cc = get_caret_column(); - int cl = get_caret_line(); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (cc == 0 && cl == 0) { - return; - } + int cc = get_caret_column(i); + int cl = get_caret_line(i); - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); + if (cc == 0 && cl == 0) { + continue; + } - merge_gutters(prev_line, cl); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); - if (_is_line_hidden(cl)) { - _set_line_as_hidden(prev_line, true); - } - _remove_text(prev_line, prev_column, cl, cc); + merge_gutters(prev_line, cl); + + if (_is_line_hidden(cl)) { + _set_line_as_hidden(prev_line, true); + } + _remove_text(prev_line, prev_column, cl, cc); + + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + } + merge_overlapping_carets(); + end_complex_operation(); } -void TextEdit::_cut_internal() { +void TextEdit::_cut_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - if (has_selection()) { - DisplayServer::get_singleton()->clipboard_set(get_selected_text()); - delete_selection(); + if (has_selection(p_caret)) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); + delete_selection(p_caret); cut_copy_line = ""; return; } - int cl = get_caret_line(); - int cc = get_caret_column(); - int indent_level = get_indent_level(cl); - double hscroll = get_h_scroll(); + begin_complex_operation(); + Vector<int> carets_to_remove; + + StringBuilder clipboard; + // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards. + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } + + int cl = get_caret_line(caret_idx); + int cc = get_caret_column(caret_idx); + int indent_level = get_indent_level(cl); + double hscroll = get_h_scroll(); - String clipboard = text[cl]; - DisplayServer::get_singleton()->clipboard_set(clipboard); - set_caret_column(0); + // Check for overlaping carets. + // We don't need to worry about selections as that is caught before this entire section. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + } + } - if (cl == 0 && get_line_count() > 1) { - _remove_text(cl, 0, cl + 1, 0); - } else { - _remove_text(cl, 0, cl, text[cl].length()); - backspace(); - set_caret_line(get_caret_line() + 1); - } + clipboard += text[cl]; + if (p_caret == -1 && caret_idx != 0) { + clipboard += "\n"; + } + + if (cl == 0 && get_line_count() > 1) { + _remove_text(cl, 0, cl + 1, 0); + adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length()); + } else { + _remove_text(cl, 0, cl, text[cl].length()); + set_caret_column(0, false, caret_idx); + backspace(caret_idx); + set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx); + } - // Correct the visually perceived caret column taking care of indentation level of the lines. - int diff_indent = indent_level - get_indent_level(get_caret_line()); - cc += diff_indent; - if (diff_indent != 0) { - cc += diff_indent > 0 ? -1 : 1; + // Correct the visually perceived caret column taking care of indentation level of the lines. + int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx)); + cc += diff_indent; + if (diff_indent != 0) { + cc += diff_indent > 0 ? -1 : 1; + } + + // Restore horizontal scroll and caret column modified by the backspace() call. + set_h_scroll(hscroll); + set_caret_column(cc, caret_idx == 0, caret_idx); } - // Restore horizontal scroll and caret column modified by the backspace() call. - set_h_scroll(hscroll); - set_caret_column(cc); + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); + } + end_complex_operation(); - cut_copy_line = clipboard; + String clipboard_string = clipboard.as_string(); + DisplayServer::get_singleton()->clipboard_set(clipboard_string); + cut_copy_line = clipboard_string; } -void TextEdit::_copy_internal() { - if (has_selection()) { - DisplayServer::get_singleton()->clipboard_set(get_selected_text()); +void TextEdit::_copy_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + if (has_selection(p_caret)) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); cut_copy_line = ""; return; } - int cl = get_caret_line(); - if (text[cl].length() != 0) { - String clipboard = _base_get_text(cl, 0, cl, text[cl].length()); - DisplayServer::get_singleton()->clipboard_set(clipboard); - cut_copy_line = clipboard; + StringBuilder clipboard; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } + + int cl = get_caret_line(caret_idx); + if (text[cl].length() != 0) { + clipboard += _base_get_text(cl, 0, cl, text[cl].length()); + if (p_caret == -1 && i != 0) { + clipboard += "\n"; + } + } } + + String clipboard_string = clipboard.as_string(); + DisplayServer::get_singleton()->clipboard_set(clipboard_string); + cut_copy_line = clipboard_string; } -void TextEdit::_paste_internal() { +void TextEdit::_paste_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } String clipboard = DisplayServer::get_singleton()->clipboard_get(); + Vector<String> clipboad_lines = clipboard.split("\n"); + bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size(); begin_complex_operation(); - if (has_selection()) { - delete_selection(); - } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { - set_caret_column(0); - String ins = "\n"; - clipboard += ins; - } + Vector<int> caret_edit_order = get_caret_index_edit_order(); + int clipboad_line = clipboad_lines.size() - 1; + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (has_selection(i)) { + delete_selection(i); + } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { + set_caret_column(0, i == 0, i); + String ins = "\n"; + clipboard += ins; + } + + if (insert_line_per_caret) { + clipboard = clipboad_lines[clipboad_line]; + } - insert_text_at_caret(clipboard); + insert_text_at_caret(clipboard, i); + clipboad_line--; + } end_complex_operation(); } -void TextEdit::_paste_primary_clipboard_internal() { +void TextEdit::_paste_primary_clipboard_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { return; } String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary(); - Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - deselect(); - set_caret_line(pos.y, true, false); - set_caret_column(pos.x); + if (carets.size() == 1) { + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + deselect(); + set_caret_line(pos.y, true, false); + set_caret_column(pos.x); + } + if (!paste_buffer.is_empty()) { insert_text_at_caret(paste_buffer); } @@ -5880,6 +6587,10 @@ Key TextEdit::_get_menu_action_accelerator(const String &p_action) { /* Versioning */ void TextEdit::_push_current_op() { + if (pending_action_end) { + start_action(EditAction::ACTION_NONE); + return; + } if (current_op.type == TextOperation::TYPE_NONE) { return; // Nothing to do. } @@ -5981,6 +6692,7 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con void TextEdit::_emit_caret_changed() { emit_signal(SNAME("caret_changed")); caret_pos_dirty = false; + caret_index_edit_dirty = true; } void TextEdit::_reset_caret_blink_timer() { @@ -6003,7 +6715,7 @@ void TextEdit::_toggle_draw_caret() { } } -int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { +int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); int row = 0; @@ -6016,7 +6728,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { } RID text_rid = text.get_line_data(p_line)->get_line_rid(row); - CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, caret.column); + CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column); if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { return ts_caret.l_caret.position.x; } else { @@ -6029,8 +6741,8 @@ void TextEdit::_click_selection_held() { // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { - switch (selection.selecting_mode) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) { + switch (get_selection_mode()) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); } break; @@ -6056,14 +6768,16 @@ void TextEdit::_update_selection_mode_pointer() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; - select(selection.selecting_line, selection.selecting_column, line, col); + select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); - set_caret_line(line, false); - set_caret_column(col); + set_caret_line(line, false, true, 0, caret_idx); + set_caret_column(col, true, caret_idx); queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } void TextEdit::_update_selection_mode_word() { @@ -6073,6 +6787,7 @@ void TextEdit::_update_selection_mode_word() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; int caret_pos = CLAMP(col, 0, text[line].length()); int beg = caret_pos; @@ -6087,25 +6802,25 @@ void TextEdit::_update_selection_mode_word() { } /* Initial selection. */ - if (!selection.active) { - select(line, beg, line, end); - selection.selecting_column = beg; - selection.selected_word_beg = beg; - selection.selected_word_end = end; - selection.selected_word_origin = beg; - set_caret_line(line, false); - set_caret_column(end); + if (!has_selection(caret_idx)) { + select(line, beg, line, end, caret_idx); + carets.write[caret_idx].selection.selecting_column = beg; + carets.write[caret_idx].selection.selected_word_beg = beg; + carets.write[caret_idx].selection.selected_word_end = end; + carets.write[caret_idx].selection.selected_word_origin = beg; + set_caret_line(line, false, true, 0, caret_idx); + set_caret_column(end, true, caret_idx); } else { - if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) { - selection.selecting_column = selection.selected_word_end; - select(line, beg, selection.selecting_line, selection.selected_word_end); - set_caret_line(selection.from_line, false); - set_caret_column(selection.from_column); + if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) { + carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end; + select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx); + set_caret_line(get_selection_from_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(get_selection_from_column(caret_idx), true, caret_idx); } else { - selection.selecting_column = selection.selected_word_beg; - select(selection.selecting_line, selection.selected_word_beg, line, end); - set_caret_line(selection.to_line, false); - set_caret_column(selection.to_column); + carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg; + select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx); + set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(get_selection_to_column(caret_idx), true, caret_idx); } } @@ -6116,6 +6831,7 @@ void TextEdit::_update_selection_mode_word() { queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } void TextEdit::_update_selection_mode_line() { @@ -6125,21 +6841,22 @@ void TextEdit::_update_selection_mode_line() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; col = 0; - if (line < selection.selecting_line) { + if (line < carets[caret_idx].selection.selecting_line) { /* Caret is above us. */ - set_caret_line(line - 1, false); - selection.selecting_column = text[selection.selecting_line].length(); + set_caret_line(line - 1, false, true, 0, caret_idx); + carets.write[caret_idx].selection.selecting_column = text[get_selection_line(caret_idx)].length(); } else { /* Caret is below us. */ - set_caret_line(line + 1, false); - selection.selecting_column = 0; + set_caret_line(line + 1, false, true, 0, caret_idx); + carets.write[caret_idx].selection.selecting_column = 0; col = text[line].length(); } - set_caret_column(0); + set_caret_column(0, false, caret_idx); - select(selection.selecting_line, selection.selecting_column, line, col); + select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } @@ -6147,33 +6864,31 @@ void TextEdit::_update_selection_mode_line() { queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } -void TextEdit::_pre_shift_selection() { +void TextEdit::_pre_shift_selection(int p_caret) { if (!selecting_enabled) { return; } - if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) { - selection.selecting_line = caret.line; - selection.selecting_column = caret.column; - selection.active = true; + if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) { + carets.write[p_caret].selection.active = true; + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret); + return; } - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret); } -void TextEdit::_post_shift_selection() { +void TextEdit::_post_shift_selection(int p_caret) { if (!selecting_enabled) { return; } - if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { - select(selection.selecting_line, selection.selecting_column, caret.line, caret.column); - queue_redraw(); + if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) { + select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret); } - - selection.selecting_text = true; } /* Line Wrapping */ @@ -6199,17 +6914,14 @@ void TextEdit::_update_wrap_at_column(bool p_force) { _update_placeholder(); } - _update_caret_wrap_offset(); -} - -void TextEdit::_update_caret_wrap_offset() { + // Update viewport. int first_vis_line = get_first_visible_line(); if (is_line_wrapped(first_vis_line)) { - caret.wrap_ofs = MIN(caret.wrap_ofs, get_line_wrap_count(first_vis_line)); + first_visible_line_wrap_ofs = MIN(first_visible_line_wrap_ofs, get_line_wrap_count(first_vis_line)); } else { - caret.wrap_ofs = 0; + first_visible_line_wrap_ofs = 0; } - set_line_as_first_visible(caret.line_ofs, caret.wrap_ofs); + set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs); } /* Viewport. */ @@ -6258,8 +6970,8 @@ void TextEdit::_update_scrollbars() { set_v_scroll(get_v_scroll()); } else { - caret.line_ofs = 0; - caret.wrap_ofs = 0; + first_visible_line = 0; + first_visible_line_wrap_ofs = 0; v_scroll->set_value(0); v_scroll->set_max(0); v_scroll->hide(); @@ -6269,15 +6981,15 @@ void TextEdit::_update_scrollbars() { h_scroll->show(); h_scroll->set_max(total_width); h_scroll->set_page(visible_width); - if (caret.x_ofs > (total_width - visible_width)) { - caret.x_ofs = (total_width - visible_width); + if (first_visible_col > (total_width - visible_width)) { + first_visible_col = (total_width - visible_width); } - if (fabs(h_scroll->get_value() - (double)caret.x_ofs) >= 1) { - h_scroll->set_value(caret.x_ofs); + if (fabs(h_scroll->get_value() - (double)first_visible_col) >= 1) { + h_scroll->set_value(first_visible_col); } } else { - caret.x_ofs = 0; + first_visible_col = 0; h_scroll->set_value(0); h_scroll->set_max(0); h_scroll->hide(); @@ -6306,7 +7018,7 @@ void TextEdit::_scroll_moved(double p_to_val) { } if (h_scroll->is_visible_in_tree()) { - caret.x_ofs = h_scroll->get_value(); + first_visible_col = h_scroll->get_value(); } if (v_scroll->is_visible_in_tree()) { // Set line ofs and wrap ofs. @@ -6329,8 +7041,8 @@ void TextEdit::_scroll_moved(double p_to_val) { int wi = line_wrap_amount - (sc - v_scroll_i - 1); wi = CLAMP(wi, 0, line_wrap_amount); - caret.line_ofs = n_line; - caret.wrap_ofs = wi; + first_visible_line = n_line; + first_visible_line_wrap_ofs = wi; } queue_redraw(); } @@ -6411,16 +7123,18 @@ void TextEdit::_scroll_lines_up() { set_v_scroll(get_v_scroll() - 1); // Adjust the caret to viewport. - if (!selection.active) { - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); + for (int i = 0; i < carets.size(); i++) { + if (has_selection(i)) { + continue; + } + int last_vis_line = get_last_full_visible_line(); int last_vis_wrap = get_last_full_visible_line_wrap_index(); - - if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { - set_caret_line(last_vis_line, false, false, last_vis_wrap); + if (get_caret_line(i) > last_vis_line || (get_caret_line(i) == last_vis_line && get_caret_wrap_index(i) > last_vis_wrap)) { + set_caret_line(last_vis_line, false, false, last_vis_wrap, i); } } + merge_overlapping_carets(); } void TextEdit::_scroll_lines_down() { @@ -6431,16 +7145,17 @@ void TextEdit::_scroll_lines_down() { set_v_scroll(get_v_scroll() + 1); // Adjust the caret to viewport. - if (!selection.active) { - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); - int first_vis_line = get_first_visible_line(); - int first_vis_wrap = caret.wrap_ofs; + for (int i = 0; i < carets.size(); i++) { + if (has_selection(i)) { + continue; + } - if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - set_caret_line(first_vis_line, false, false, first_vis_wrap); + int first_vis_line = get_first_visible_line(); + if (get_caret_line(i) < first_vis_line || (get_caret_line(i) == first_vis_line && get_caret_wrap_index(i) < first_visible_line_wrap_ofs)) { + set_caret_line(first_vis_line, false, false, first_visible_line_wrap_ofs, i); } } + merge_overlapping_carets(); } // Minimap @@ -6577,6 +7292,8 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r op.version = ++version; op.chain_forward = false; op.chain_backward = false; + op.start_carets = carets; + op.end_carets = carets; // See if it should just be set as current op. if (current_op.type != op.type) { @@ -6599,6 +7316,7 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r current_op.to_column = retchar; current_op.to_line = retline; current_op.version = op.version; + current_op.end_carets = carets; } void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -6629,6 +7347,8 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i op.version = ++version; op.chain_forward = false; op.chain_backward = false; + op.start_carets = carets; + op.end_carets = carets; // See if it should just be set as current op. if (current_op.type != op.type) { @@ -6643,6 +7363,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i current_op.text = text + current_op.text; current_op.from_line = p_from_line; current_op.from_column = p_from_column; + current_op.end_carets = carets; return; // Update current op. } @@ -6692,7 +7413,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); - TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? caret.column : 0, r_end_column); + TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? carets[0].column : 0, r_end_column); if (dir != TextServer::DIRECTION_AUTO) { input_direction = (TextDirection)dir; } @@ -6754,6 +7475,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li TextEdit::TextEdit(const String &p_placeholder) { placeholder_data_buf.instantiate(); + carets.push_back(Caret()); clear(); set_focus_mode(FOCUS_ALL); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index a8da878ede..a8e087909e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -42,6 +42,14 @@ class TextEdit : public Control { GDCLASS(TextEdit, Control); public: + /* Edit Actions. */ + enum EditAction { + ACTION_NONE, + ACTION_TYPING, + ACTION_BACKSPACE, + ACTION_DELETE, + }; + /* Caret. */ enum CaretType { CARET_TYPE_LINE, @@ -299,12 +307,15 @@ private: Key _get_menu_action_accelerator(const String &p_action); /* Versioning */ + struct Caret; struct TextOperation { enum Type { TYPE_NONE, TYPE_INSERT, TYPE_REMOVE }; + Vector<Caret> start_carets; + Vector<Caret> end_carets; Type type = TYPE_NONE; int from_line = 0; @@ -321,6 +332,10 @@ private: bool undo_enabled = true; int undo_stack_max_size = 50; + EditAction current_action = EditAction::ACTION_NONE; + bool pending_action_end = false; + bool in_action = false; + int complex_operation_count = 0; bool next_operation_is_complex = false; @@ -361,19 +376,39 @@ private: int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; /* Caret. */ + struct Selection { + bool active = false; + bool shiftclick_left = false; + + int selecting_line = 0; + int selecting_column = 0; + int selected_word_beg = 0; + int selected_word_end = 0; + int selected_word_origin = 0; + + int from_line = 0; + int from_column = 0; + int to_line = 0; + int to_column = 0; + }; + struct Caret { + Selection selection; + Point2 draw_pos; bool visible = false; int last_fit_x = 0; int line = 0; int column = 0; - int x_ofs = 0; - int line_ofs = 0; - int wrap_ofs = 0; - } caret; + }; + + // Vector containing all the carets, index '0' is the "main caret" and should never be removed. + Vector<Caret> carets; + Vector<int> caret_index_edit_order; bool setting_caret_line = false; bool caret_pos_dirty = false; + bool caret_index_edit_dirty = true; Color caret_color = Color(1, 1, 1); Color caret_background_color = Color(0, 0, 0); @@ -389,6 +424,8 @@ private: bool caret_mid_grapheme_enabled = true; + bool multi_carets_enabled = true; + bool drag_action = false; bool drag_caret_force_displayed = false; @@ -397,28 +434,10 @@ private: void _reset_caret_blink_timer(); void _toggle_draw_caret(); - int _get_column_x_offset_for_line(int p_char, int p_line) const; + int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const; /* Selection. */ - struct Selection { - SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; - int selecting_line = 0; - int selecting_column = 0; - int selected_word_beg = 0; - int selected_word_end = 0; - int selected_word_origin = 0; - bool selecting_text = false; - - bool active = false; - - int from_line = 0; - int from_column = 0; - int to_line = 0; - int to_column = 0; - - bool shiftclick_left = false; - bool drag_attempt = false; - } selection; + SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; bool selecting_enabled = true; bool deselect_on_focus_loss_enabled = true; @@ -428,6 +447,7 @@ private: Color selection_color = Color(1, 1, 1); bool override_selected_font_color = false; + bool selection_drag_attempt = false; bool dragging_selection = false; Timer *click_select_held = nullptr; @@ -439,8 +459,8 @@ private: void _update_selection_mode_word(); void _update_selection_mode_line(); - void _pre_shift_selection(); - void _post_shift_selection(); + void _pre_shift_selection(int p_caret); + void _post_shift_selection(int p_caret); /* Line wrapping. */ LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; @@ -450,8 +470,6 @@ private: void _update_wrap_at_column(bool p_force = false); - void _update_caret_wrap_offset(); - /* Viewport. */ HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; @@ -466,6 +484,10 @@ private: float v_scroll_speed = 80.0; // Scrolling. + int first_visible_line = 0; + int first_visible_line_wrap_ofs = 0; + int first_visible_col = 0; + bool scrolling = false; bool updating_scrolls = false; @@ -583,6 +605,17 @@ protected: /* Internal API for CodeEdit, pending public API. */ // brace matching + struct BraceMatchingData { + int open_match_line = -1; + int open_match_column = -1; + bool open_matching = false; + bool open_mismatch = false; + int close_match_line = -1; + int close_match_column = -1; + bool close_matching = false; + bool close_mismatch = false; + }; + bool highlight_matching_braces_enabled = false; Color brace_mismatch_color; @@ -607,20 +640,20 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input_internal(const uint32_t p_unicode); - virtual void _backspace_internal(); + virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret); + virtual void _backspace_internal(int p_caret); - virtual void _cut_internal(); - virtual void _copy_internal(); - virtual void _paste_internal(); - virtual void _paste_primary_clipboard_internal(); + virtual void _cut_internal(int p_caret); + virtual void _copy_internal(int p_caret); + virtual void _paste_internal(int p_caret); + virtual void _paste_primary_clipboard_internal(int p_caret); - GDVIRTUAL1(_handle_unicode_input, int) - GDVIRTUAL0(_backspace) - GDVIRTUAL0(_cut) - GDVIRTUAL0(_copy) - GDVIRTUAL0(_paste) - GDVIRTUAL0(_paste_primary_clipboard) + GDVIRTUAL2(_handle_unicode_input, int, int) + GDVIRTUAL1(_backspace, int) + GDVIRTUAL1(_cut, int) + GDVIRTUAL1(_copy, int) + GDVIRTUAL1(_paste, int) + GDVIRTUAL1(_paste_primary_clipboard, int) public: /* General overrides. */ @@ -696,7 +729,7 @@ public: void swap_lines(int p_from_line, int p_to_line); void insert_line_at(int p_at, const String &p_text); - void insert_text_at_caret(const String &p_text); + void insert_text_at_caret(const String &p_text, int p_caret = -1); void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); @@ -705,13 +738,13 @@ public: Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const; // Overridable actions - void handle_unicode_input(const uint32_t p_unicode); - void backspace(); + void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1); + void backspace(int p_caret = -1); - void cut(); - void copy(); - void paste(); - void paste_primary_clipboard(); + void cut(int p_caret = -1); + void copy(int p_caret = -1); + void paste(int p_caret = -1); + void paste_primary_clipboard(int p_caret = -1); // Context menu. PopupMenu *get_menu() const; @@ -719,6 +752,10 @@ public: void menu_option(int p_option); /* Versioning */ + void start_action(EditAction p_action); + void end_action(); + EditAction get_current_action() const; + void begin_complex_operation(); void end_complex_operation(); @@ -753,7 +790,7 @@ public: int get_minimap_line_at_pos(const Point2i &p_pos) const; bool is_dragging_cursor() const; - bool is_mouse_over_selection(bool p_edges = true) const; + bool is_mouse_over_selection(bool p_edges = true, int p_caret = -1) const; /* Caret */ void set_caret_type(CaretType p_type); @@ -771,18 +808,30 @@ public: void set_caret_mid_grapheme_enabled(const bool p_enabled); bool is_caret_mid_grapheme_enabled() const; - bool is_caret_visible() const; - Point2 get_caret_draw_pos() const; + void set_multiple_carets_enabled(bool p_enabled); + bool is_multiple_carets_enabled() const; + + int add_caret(int p_line, int p_col); + void remove_caret(int p_caret); + void remove_secondary_carets(); + void merge_overlapping_carets(); + int get_caret_count() const; + + Vector<int> get_caret_index_edit_order(); + void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); + + bool is_caret_visible(int p_caret = 0) const; + Point2 get_caret_draw_pos(int p_caret = 0) const; - void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); - int get_caret_line() const; + void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0); + int get_caret_line(int p_caret = 0) const; - void set_caret_column(int p_col, bool p_adjust_viewport = true); - int get_caret_column() const; + void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0); + int get_caret_column(int p_caret = 0) const; - int get_caret_wrap_index() const; + int get_caret_wrap_index(int p_caret = 0) const; - String get_word_under_caret() const; + String get_word_under_caret(int p_caret = -1) const; /* Selection. */ void set_selecting_enabled(const bool p_enabled); @@ -797,27 +846,27 @@ public: void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; - void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1); + void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0); SelectionMode get_selection_mode() const; void select_all(); - void select_word_under_caret(); - void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); + void select_word_under_caret(int p_caret = -1); + void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0); - bool has_selection() const; + bool has_selection(int p_caret = -1) const; - String get_selected_text() const; + String get_selected_text(int p_caret = -1); - int get_selection_line() const; - int get_selection_column() const; + int get_selection_line(int p_caret = 0) const; + int get_selection_column(int p_caret = 0) const; - int get_selection_from_line() const; - int get_selection_from_column() const; - int get_selection_to_line() const; - int get_selection_to_column() const; + int get_selection_from_line(int p_caret = 0) const; + int get_selection_from_column(int p_caret = 0) const; + int get_selection_to_line(int p_caret = 0) const; + int get_selection_to_column(int p_caret = 0) const; - void deselect(); - void delete_selection(); + void deselect(int p_caret = -1); + void delete_selection(int p_caret = -1); /* Line wrapping. */ void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); @@ -866,8 +915,8 @@ public: int get_total_visible_line_count() const; // Auto Adjust - void adjust_viewport_to_caret(); - void center_viewport_to_caret(); + void adjust_viewport_to_caret(int p_caret = 0); + void center_viewport_to_caret(int p_caret = 0); // Minimap void set_draw_minimap(bool p_enabled); @@ -949,6 +998,7 @@ public: TextEdit(const String &p_placeholder = String()); }; +VARIANT_ENUM_CAST(TextEdit::EditAction); VARIANT_ENUM_CAST(TextEdit::CaretType); VARIANT_ENUM_CAST(TextEdit::LineWrappingMode); VARIANT_ENUM_CAST(TextEdit::SelectionMode); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f82a853e56..db7d390b16 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1163,7 +1163,7 @@ bool TreeItem::is_editable(int p_column) { void TreeItem::set_custom_color(int p_column, const Color &p_color) { ERR_FAIL_INDEX(p_column, cells.size()); - if (cells[p_column].custom_color == true) { + if (cells[p_column].custom_color && cells[p_column].color == p_color) { return; } diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 0ea5264935..49b78a091d 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -1667,6 +1667,10 @@ Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) cons idx = (end + start) / 2; } + if (idx == count - 1) { + return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx]; + } + real_t offset_begin = baked_dist_cache[idx]; real_t offset_end = baked_dist_cache[idx + 1]; @@ -1675,10 +1679,6 @@ Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) cons real_t frac = (p_offset - offset_begin) / idx_interval; - if (idx == count - 1) { - return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx]; - } - Vector3 forward = (rp[idx + 1] - rp[idx]).normalized(); Vector3 up = r[idx]; Vector3 up1 = r[idx + 1]; diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 9430e5797d..8fe9223a47 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -265,9 +265,9 @@ private: float spread = 0.0f; float flatness = 0.0f; - float params_min[PARAM_MAX]; - float params_max[PARAM_MAX]; - float params[PARAM_MAX]; + float params_min[PARAM_MAX] = {}; + float params_max[PARAM_MAX] = {}; + float params[PARAM_MAX] = {}; Ref<Texture2D> tex_parameters[PARAM_MAX]; Color color; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 9829c7e86b..94967352c8 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -986,7 +986,7 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const format = 0; } - uint32_t nformat; + uint32_t nformat = 0; LocalVector<Vertex> nvertices; LocalVector<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 15678c9281..298d7b1ffa 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1209,6 +1209,8 @@ Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, if (texture.is_valid()) { RenderingServer::get_singleton()->texture_replace(texture, tex); + } else { + texture = tex; } return OK; @@ -1489,7 +1491,15 @@ void AtlasTexture::set_atlas(const Ref<Texture2D> &p_atlas) { if (atlas == p_atlas) { return; } + // Support recursive AtlasTextures. + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->disconnect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } atlas = p_atlas; + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->connect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } + emit_changed(); } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 65f3767449..813be773b7 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -5017,7 +5017,7 @@ void TileData::add_custom_data_layer(int p_to_pos) { void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { ERR_FAIL_INDEX(p_from_index, custom_data.size()); ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); - custom_data.insert(p_to_pos, navigation[p_from_index]); + custom_data.insert(p_to_pos, custom_data[p_from_index]); custom_data.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); } diff --git a/servers/extensions/physics_server_3d_extension.cpp b/servers/extensions/physics_server_3d_extension.cpp index 27f9ed2cb3..811ff231fc 100644 --- a/servers/extensions/physics_server_3d_extension.cpp +++ b/servers/extensions/physics_server_3d_extension.cpp @@ -60,6 +60,7 @@ void PhysicsDirectBodyState3DExtension::_bind_methods() { GDVIRTUAL_BIND(_get_inverse_mass); GDVIRTUAL_BIND(_get_inverse_inertia); + GDVIRTUAL_BIND(_get_inverse_inertia_tensor); GDVIRTUAL_BIND(_set_linear_velocity, "velocity"); GDVIRTUAL_BIND(_get_linear_velocity); diff --git a/servers/physics_2d/godot_body_pair_2d.cpp b/servers/physics_2d/godot_body_pair_2d.cpp index 2f2af15da7..7b7c67bbc2 100644 --- a/servers/physics_2d/godot_body_pair_2d.cpp +++ b/servers/physics_2d/godot_body_pair_2d.cpp @@ -170,7 +170,7 @@ bool GodotBodyPair2D::_test_ccd(real_t p_step, GodotBody2D *p_A, int p_shape_A, Vector2 mnormal = motion / mlen; - real_t min, max; + real_t min = 0.0, max = 0.0; p_A->get_shape(p_shape_A)->project_rangev(mnormal, p_xform_A, min, max); // Did it move enough in this direction to even attempt raycast? diff --git a/servers/physics_2d/godot_collision_solver_2d.cpp b/servers/physics_2d/godot_collision_solver_2d.cpp index 52237539c0..9c7b8fbe53 100644 --- a/servers/physics_2d/godot_collision_solver_2d.cpp +++ b/servers/physics_2d/godot_collision_solver_2d.cpp @@ -197,7 +197,7 @@ bool GodotCollisionSolver2D::solve_concave(const GodotShape2D *p_shape_A, const real_t axis_scale = 1.0 / axis.length(); axis *= axis_scale; - real_t smin, smax; + real_t smin = 0.0, smax = 0.0; p_shape_A->project_rangev(axis, rel_transform, smin, smax); smin *= axis_scale; smax *= axis_scale; diff --git a/servers/physics_2d/godot_collision_solver_2d_sat.cpp b/servers/physics_2d/godot_collision_solver_2d_sat.cpp index 8aa30ad6a4..f8924239ea 100644 --- a/servers/physics_2d/godot_collision_solver_2d_sat.cpp +++ b/servers/physics_2d/godot_collision_solver_2d_sat.cpp @@ -234,7 +234,7 @@ public: axis = Vector2(0.0, 1.0); } - real_t min_A, max_A, min_B, max_B; + real_t min_A = 0.0, max_A = 0.0, min_B = 0.0, max_B = 0.0; if (castA) { shape_A->project_range_cast(motion_A, axis, *transform_A, min_A, max_A); diff --git a/servers/physics_3d/godot_body_pair_3d.cpp b/servers/physics_3d/godot_body_pair_3d.cpp index eebbe0196d..7e6cc6f834 100644 --- a/servers/physics_3d/godot_body_pair_3d.cpp +++ b/servers/physics_3d/godot_body_pair_3d.cpp @@ -170,7 +170,7 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, Vector3 mnormal = motion / mlen; - real_t min, max; + real_t min = 0.0, max = 0.0; p_A->get_shape(p_shape_A)->project_range(mnormal, p_xform_A, min, max); // Did it move enough in this direction to even attempt raycast? diff --git a/servers/physics_3d/godot_collision_solver_3d.cpp b/servers/physics_3d/godot_collision_solver_3d.cpp index 9fe0e3eb84..ca76a819ec 100644 --- a/servers/physics_3d/godot_collision_solver_3d.cpp +++ b/servers/physics_3d/godot_collision_solver_3d.cpp @@ -48,7 +48,7 @@ bool GodotCollisionSolver3D::solve_static_world_boundary(const GodotShape3D *p_s static const int max_supports = 16; Vector3 supports[max_supports]; int support_count; - GodotShape3D::FeatureType support_type; + GodotShape3D::FeatureType support_type = GodotShape3D::FeatureType::FEATURE_POINT; p_shape_B->get_supports(p_transform_B.basis.xform_inv(-p.normal).normalized(), max_supports, supports, support_count, support_type); if (support_type == GodotShape3D::FEATURE_CIRCLE) { @@ -338,7 +338,7 @@ bool GodotCollisionSolver3D::solve_concave(const GodotShape3D *p_shape_A, const real_t axis_scale = 1.0 / axis.length(); axis *= axis_scale; - real_t smin, smax; + real_t smin = 0.0, smax = 0.0; p_shape_A->project_range(axis, rel_transform, smin, smax); smin -= p_margin_A; smax += p_margin_A; diff --git a/servers/physics_3d/godot_collision_solver_3d_sat.cpp b/servers/physics_3d/godot_collision_solver_3d_sat.cpp index 56e644b57b..933a5e28df 100644 --- a/servers/physics_3d/godot_collision_solver_3d_sat.cpp +++ b/servers/physics_3d/godot_collision_solver_3d_sat.cpp @@ -634,7 +634,7 @@ public: axis = Vector3(0.0, 1.0, 0.0); } - real_t min_A, max_A, min_B, max_B; + real_t min_A = 0.0, max_A = 0.0, min_B = 0.0, max_B = 0.0; shape_A->project_range(axis, *transform_A, min_A, max_A); shape_B->project_range(axis, *transform_B, min_B, max_B); diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h index a3feb97d5d..95425e72e8 100644 --- a/servers/physics_server_2d.h +++ b/servers/physics_server_2d.h @@ -490,7 +490,7 @@ public: bool collide_separation_ray = false; HashSet<RID> exclude_bodies; HashSet<ObjectID> exclude_objects; - bool recovery_as_collision = false; // Don't report margin recovery as collision by default, only used for floor snapping. + bool recovery_as_collision = false; MotionParameters() {} diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index 24cc7e8459..db21e1db06 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -99,6 +99,7 @@ void PhysicsDirectBodyState3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_inverse_mass"), &PhysicsDirectBodyState3D::get_inverse_mass); ClassDB::bind_method(D_METHOD("get_inverse_inertia"), &PhysicsDirectBodyState3D::get_inverse_inertia); + ClassDB::bind_method(D_METHOD("get_inverse_inertia_tensor"), &PhysicsDirectBodyState3D::get_inverse_inertia_tensor); ClassDB::bind_method(D_METHOD("set_linear_velocity", "velocity"), &PhysicsDirectBodyState3D::set_linear_velocity); ClassDB::bind_method(D_METHOD("get_linear_velocity"), &PhysicsDirectBodyState3D::get_linear_velocity); @@ -153,6 +154,7 @@ void PhysicsDirectBodyState3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_angular_damp"), "", "get_total_angular_damp"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_linear_damp"), "", "get_total_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inverse_inertia"), "", "get_inverse_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inverse_inertia_tensor"), "", "get_inverse_inertia_tensor"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "total_gravity"), "", "get_total_gravity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass"), "", "get_center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass_local"), "", "get_center_of_mass_local"); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index b21c4e9249..87877ad52d 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -527,7 +527,7 @@ public: bool collide_separation_ray = false; HashSet<RID> exclude_bodies; HashSet<ObjectID> exclude_objects; - bool recovery_as_collision = false; // Don't report margin recovery as collision by default, only used for floor snapping. + bool recovery_as_collision = false; MotionParameters() {} diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h index f5bdccca68..98acd5ad74 100644 --- a/servers/rendering/dummy/storage/texture_storage.h +++ b/servers/rendering/dummy/storage/texture_storage.h @@ -157,14 +157,17 @@ public: virtual RID render_target_create() override { return RID(); } virtual void render_target_free(RID p_rid) override {} virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) override {} + virtual Point2i render_target_get_position(RID p_render_target) const override { return Point2i(); } virtual void render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) override {} - virtual RID render_target_get_texture(RID p_render_target) override { return RID(); } - virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) override {} + virtual Size2i render_target_get_size(RID p_render_target) const override { return Size2i(); } virtual void render_target_set_transparent(RID p_render_target, bool p_is_transparent) override {} + virtual bool render_target_get_transparent(RID p_render_target) const override { return false; } virtual void render_target_set_direct_to_screen(RID p_render_target, bool p_direct_to_screen) override {} - virtual bool render_target_was_used(RID p_render_target) override { return false; } + virtual bool render_target_get_direct_to_screen(RID p_render_target) const override { return false; } + virtual bool render_target_was_used(RID p_render_target) const override { return false; } virtual void render_target_set_as_unused(RID p_render_target) override {} virtual void render_target_set_msaa(RID p_render_target, RS::ViewportMSAA p_msaa) override {} + virtual RS::ViewportMSAA render_target_get_msaa(RID p_render_target) const override { return RS::VIEWPORT_MSAA_DISABLED; } virtual void render_target_request_clear(RID p_render_target, const Color &p_clear_color) override {} virtual bool render_target_is_clear_requested(RID p_render_target) override { return false; } @@ -176,8 +179,19 @@ public: virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const override { return Rect2i(); } virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) override {} - virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override{}; - virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override{}; + virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override {} + virtual RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const override { return RS::VIEWPORT_VRS_DISABLED; } + virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_vrs_texture(RID p_render_target) const override { return RID(); } + + virtual void render_target_set_override_color(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_color(RID p_render_target) const override { return RID(); } + virtual void render_target_set_override_depth(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_depth(RID p_render_target) const override { return RID(); } + virtual void render_target_set_override_velocity(RID p_render_target, RID p_texture) override {} + virtual RID render_target_get_override_velocity(RID p_render_target) const override { return RID(); } + + virtual RID render_target_get_texture(RID p_render_target) override { return RID(); } }; } // namespace RendererDummy diff --git a/servers/rendering/renderer_rd/effects/ss_effects.cpp b/servers/rendering/renderer_rd/effects/ss_effects.cpp index b48e539823..9653382e96 100644 --- a/servers/rendering/renderer_rd/effects/ss_effects.cpp +++ b/servers/rendering/renderer_rd/effects/ss_effects.cpp @@ -43,7 +43,7 @@ SSEffects *SSEffects::singleton = nullptr; static _FORCE_INLINE_ void store_camera(const Projection &p_mtx, float *p_array) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - p_array[i * 4 + j] = p_mtx.matrix[i][j]; + p_array[i * 4 + j] = p_mtx.columns[i][j]; } } } @@ -488,8 +488,8 @@ void SSEffects::downsample_depth(RID p_depth_buffer, const Vector<RID> &p_depth_ ss_effects.downsample_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, ss_effects.downsample_shader.version_get_shader(ss_effects.downsample_shader_version, use_full_mips ? 6 : 2), 2); } - float depth_linearize_mul = -p_projection.matrix[3][2]; - float depth_linearize_add = p_projection.matrix[2][2]; + float depth_linearize_mul = -p_projection.columns[3][2]; + float depth_linearize_add = p_projection.columns[2][2]; if (depth_linearize_mul * depth_linearize_add < 0) { depth_linearize_add = -depth_linearize_add; } @@ -713,8 +713,8 @@ void SSEffects::screen_space_indirect_lighting(SSILRenderBuffers &p_ssil_buffers ssil.gather_push_constant.half_screen_pixel_size[0] = 1.0 / p_ssil_buffers.buffer_width; ssil.gather_push_constant.half_screen_pixel_size[1] = 1.0 / p_ssil_buffers.buffer_height; - float tan_half_fov_x = 1.0 / p_projection.matrix[0][0]; - float tan_half_fov_y = 1.0 / p_projection.matrix[1][1]; + float tan_half_fov_x = 1.0 / p_projection.columns[0][0]; + float tan_half_fov_y = 1.0 / p_projection.columns[1][1]; ssil.gather_push_constant.NDC_to_view_mul[0] = tan_half_fov_x * 2.0; ssil.gather_push_constant.NDC_to_view_mul[1] = tan_half_fov_y * -2.0; ssil.gather_push_constant.NDC_to_view_add[0] = tan_half_fov_x * -1.0; @@ -1164,8 +1164,8 @@ void SSEffects::generate_ssao(SSAORenderBuffers &p_ssao_buffers, RID p_normal_bu ssao.gather_push_constant.half_screen_pixel_size[0] = 1.0 / p_ssao_buffers.buffer_width; ssao.gather_push_constant.half_screen_pixel_size[1] = 1.0 / p_ssao_buffers.buffer_height; - float tan_half_fov_x = 1.0 / p_projection.matrix[0][0]; - float tan_half_fov_y = 1.0 / p_projection.matrix[1][1]; + float tan_half_fov_x = 1.0 / p_projection.columns[0][0]; + float tan_half_fov_y = 1.0 / p_projection.columns[1][1]; ssao.gather_push_constant.NDC_to_view_mul[0] = tan_half_fov_x * 2.0; ssao.gather_push_constant.NDC_to_view_mul[1] = tan_half_fov_y * -2.0; ssao.gather_push_constant.NDC_to_view_add[0] = tan_half_fov_x * -1.0; @@ -1627,10 +1627,10 @@ void SSEffects::screen_space_reflection(SSRRenderBuffers &p_ssr_buffers, const R push_constant.num_steps = p_max_steps; push_constant.depth_tolerance = p_tolerance; push_constant.use_half_res = true; - push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_projections[v].matrix[0][0]); - push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_projections[v].matrix[1][1]); - push_constant.proj_info[2] = (1.0f - p_projections[v].matrix[0][2]) / p_projections[v].matrix[0][0]; - push_constant.proj_info[3] = (1.0f + p_projections[v].matrix[1][2]) / p_projections[v].matrix[1][1]; + push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_projections[v].columns[0][0]); + push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_projections[v].columns[1][1]); + push_constant.proj_info[2] = (1.0f - p_projections[v].columns[0][2]) / p_projections[v].columns[0][0]; + push_constant.proj_info[3] = (1.0f + p_projections[v].columns[1][2]) / p_projections[v].columns[1][1]; ScreenSpaceReflectionMode mode = (ssr_roughness_quality != RS::ENV_SSR_ROUGHNESS_QUALITY_DISABLED) ? SCREEN_SPACE_REFLECTION_ROUGH : SCREEN_SPACE_REFLECTION_NORMAL; RID shader = ssr.shader.version_get_shader(ssr.shader_version, mode); @@ -1683,10 +1683,10 @@ void SSEffects::screen_space_reflection(SSRRenderBuffers &p_ssr_buffers, const R push_constant.view_index = v; push_constant.orthogonal = p_projections[v].is_orthogonal(); push_constant.edge_tolerance = Math::sin(Math::deg_to_rad(15.0)); - push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_projections[v].matrix[0][0]); - push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_projections[v].matrix[1][1]); - push_constant.proj_info[2] = (1.0f - p_projections[v].matrix[0][2]) / p_projections[v].matrix[0][0]; - push_constant.proj_info[3] = (1.0f + p_projections[v].matrix[1][2]) / p_projections[v].matrix[1][1]; + push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_projections[v].columns[0][0]); + push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_projections[v].columns[1][1]); + push_constant.proj_info[2] = (1.0f - p_projections[v].columns[0][2]) / p_projections[v].columns[0][0]; + push_constant.proj_info[3] = (1.0f + p_projections[v].columns[1][2]) / p_projections[v].columns[1][1]; push_constant.vertical = 0; if (ssr_roughness_quality == RS::ENV_SSR_ROUGHNESS_QUALITY_LOW) { push_constant.steps = p_max_steps / 3; diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp index 4c72f0a56e..74082906c4 100644 --- a/servers/rendering/renderer_rd/environment/fog.cpp +++ b/servers/rendering/renderer_rd/environment/fog.cpp @@ -143,7 +143,7 @@ Vector3 Fog::fog_volume_get_extents(RID p_fog_volume) const { bool Fog::FogMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { uniform_set_updated = true; - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, Fog::get_singleton()->volumetric_fog.shader.version_get_shader(shader_data->version, 0), VolumetricFogShader::FogSet::FOG_SET_MATERIAL); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, Fog::get_singleton()->volumetric_fog.shader.version_get_shader(shader_data->version, 0), VolumetricFogShader::FogSet::FOG_SET_MATERIAL, true); } Fog::FogMaterialData::~FogMaterialData() { diff --git a/servers/rendering/renderer_rd/environment/gi.cpp b/servers/rendering/renderer_rd/environment/gi.cpp index 0853460861..9b6be0b422 100644 --- a/servers/rendering/renderer_rd/environment/gi.cpp +++ b/servers/rendering/renderer_rd/environment/gi.cpp @@ -1634,7 +1634,7 @@ void GI::SDFGI::debug_draw(uint32_t p_view_count, const Projection *p_projection Projection inv_projection = p_projections[v].inverse(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 3; j++) { - push_constant.inv_projection[j][i] = inv_projection.matrix[i][j]; + push_constant.inv_projection[j][i] = inv_projection.columns[i][j]; } } @@ -1644,8 +1644,8 @@ void GI::SDFGI::debug_draw(uint32_t p_view_count, const Projection *p_projection RD::get_singleton()->compute_list_end(); } - Size2 rtsize = texture_storage->render_target_get_size(p_render_target); - copy_effects->copy_to_fb_rect(p_texture, texture_storage->render_target_get_rd_framebuffer(p_render_target), Rect2(Vector2(), rtsize), true, false, false, false, RID(), p_view_count > 1); + Size2i rtsize = texture_storage->render_target_get_size(p_render_target); + copy_effects->copy_to_fb_rect(p_texture, texture_storage->render_target_get_rd_framebuffer(p_render_target), Rect2i(Point2i(), rtsize), true, false, false, false, RID(), p_view_count > 1); } void GI::SDFGI::debug_probes(RID p_framebuffer, const uint32_t p_view_count, const Projection *p_camera_with_transforms, bool p_will_continue_color, bool p_will_continue_depth) { @@ -3285,7 +3285,7 @@ void GI::VoxelGIInstance::debug(RD::DrawListID p_draw_list, RID p_framebuffer, c for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - push_constant.projection[i * 4 + j] = cam_transform.matrix[i][j]; + push_constant.projection[i * 4 + j] = cam_transform.columns[i][j]; } } @@ -3820,10 +3820,10 @@ void GI::process_gi(Ref<RenderSceneBuffersRD> p_render_buffers, const RID *p_nor push_constant.z_far = p_projections[0].get_z_far(); // these are only used if we have 1 view, else we use the projections in our scene data - push_constant.proj_info[0] = -2.0f / (internal_size.x * p_projections[0].matrix[0][0]); - push_constant.proj_info[1] = -2.0f / (internal_size.y * p_projections[0].matrix[1][1]); - push_constant.proj_info[2] = (1.0f - p_projections[0].matrix[0][2]) / p_projections[0].matrix[0][0]; - push_constant.proj_info[3] = (1.0f + p_projections[0].matrix[1][2]) / p_projections[0].matrix[1][1]; + push_constant.proj_info[0] = -2.0f / (internal_size.x * p_projections[0].columns[0][0]); + push_constant.proj_info[1] = -2.0f / (internal_size.y * p_projections[0].columns[1][1]); + push_constant.proj_info[2] = (1.0f - p_projections[0].columns[0][2]) / p_projections[0].columns[0][0]; + push_constant.proj_info[3] = (1.0f + p_projections[0].columns[1][2]) / p_projections[0].columns[1][1]; bool use_sdfgi = p_render_buffers->has_custom_data(RB_SCOPE_SDFGI); bool use_voxel_gi_instances = push_constant.max_voxel_gi_instances > 0; diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 21a15ed475..4ff2a66975 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -268,7 +268,7 @@ bool SkyRD::SkyMaterialData::update_parameters(const HashMap<StringName, Variant uniform_set_updated = true; - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, scene_singleton->sky.sky_shader.shader.version_get_shader(shader_data->version, 0), SKY_SET_MATERIAL); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, scene_singleton->sky.sky_shader.shader.version_get_shader(shader_data->version, 0), SKY_SET_MATERIAL, true); } SkyRD::SkyMaterialData::~SkyMaterialData() { @@ -300,10 +300,10 @@ void SkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_fb, PipelineC for (uint32_t v = 0; v < p_view_count; v++) { // We only need key components of our projection matrix - sky_push_constant.projections[v][0] = p_projections[v].matrix[2][0]; - sky_push_constant.projections[v][1] = p_projections[v].matrix[0][0]; - sky_push_constant.projections[v][2] = p_projections[v].matrix[2][1]; - sky_push_constant.projections[v][3] = p_projections[v].matrix[1][1]; + sky_push_constant.projections[v][0] = p_projections[v].columns[2][0]; + sky_push_constant.projections[v][1] = p_projections[v].columns[0][0]; + sky_push_constant.projections[v][2] = p_projections[v].columns[2][1]; + sky_push_constant.projections[v][3] = p_projections[v].columns[1][1]; } sky_push_constant.position[0] = p_position.x; sky_push_constant.position[1] = p_position.y; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index e4bc51bcdb..84d2ad328c 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -2094,17 +2094,17 @@ void RenderForwardClustered::_render_buffers_debug_draw(Ref<RenderSceneBuffersRD RID render_target = p_render_buffers->get_render_target(); if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SSAO && rb_data->ss_effects_data.ssao.ao_final.is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(rb_data->ss_effects_data.ssao.ao_final, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2(Vector2(), rtsize), false, true); } if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SSIL && rb_data->ss_effects_data.ssil.ssil_final.is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(rb_data->ss_effects_data.ssil.ssil_final, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2(Vector2(), rtsize), false, false); } if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_BUFFER && p_render_buffers->has_texture(RB_SCOPE_GI, RB_TEX_AMBIENT)) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); RID ambient_texture = p_render_buffers->get_texture(RB_SCOPE_GI, RB_TEX_AMBIENT); RID reflection_texture = p_render_buffers->get_texture(RB_SCOPE_GI, RB_TEX_REFLECTION); copy_effects->copy_to_fb_rect(ambient_texture, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2(Vector2(), rtsize), false, false, false, true, reflection_texture, p_render_buffers->get_view_count() > 1); @@ -2173,11 +2173,11 @@ void RenderForwardClustered::_render_shadow_pass(RID p_light, RID p_shadow_atlas } } - int directional_shadow_size = light_storage->directional_shadow_get_size(); - atlas_rect.position /= directional_shadow_size; - atlas_rect.size /= directional_shadow_size; - - light_storage->light_instance_set_directional_shadow_atlas_rect(p_light, p_pass, atlas_rect); + float directional_shadow_size = light_storage->directional_shadow_get_size(); + Rect2 atlas_rect_norm = atlas_rect; + atlas_rect_norm.position /= directional_shadow_size; + atlas_rect_norm.size /= directional_shadow_size; + light_storage->light_instance_set_directional_shadow_atlas_rect(p_light, p_pass, atlas_rect_norm); zfar = RSG::light_storage->light_get_param(base, RS::LIGHT_PARAM_RANGE); diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 1987577464..c01919a606 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -509,7 +509,7 @@ void SceneShaderForwardClustered::MaterialData::set_next_pass(RID p_pass) { bool SceneShaderForwardClustered::MaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton; - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardClustered::MATERIAL_UNIFORM_SET, RD::BARRIER_MASK_RASTER); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardClustered::MATERIAL_UNIFORM_SET, true, RD::BARRIER_MASK_RASTER); } SceneShaderForwardClustered::MaterialData::~MaterialData() { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index c3ce1b05f1..2b2090f8ed 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -1128,11 +1128,11 @@ void RenderForwardMobile::_render_shadow_pass(RID p_light, RID p_shadow_atlas, i } } - int directional_shadow_size = light_storage->directional_shadow_get_size(); - atlas_rect.position /= directional_shadow_size; - atlas_rect.size /= directional_shadow_size; - - light_storage->light_instance_set_directional_shadow_atlas_rect(p_light, p_pass, atlas_rect); + float directional_shadow_size = light_storage->directional_shadow_get_size(); + Rect2 atlas_rect_norm = atlas_rect; + atlas_rect_norm.position /= directional_shadow_size; + atlas_rect_norm.size /= directional_shadow_size; + light_storage->light_instance_set_directional_shadow_atlas_rect(p_light, p_pass, atlas_rect_norm); zfar = RSG::light_storage->light_get_param(base, RS::LIGHT_PARAM_RANGE); diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 691d431b82..71fb3ee4f5 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -464,7 +464,7 @@ void SceneShaderForwardMobile::MaterialData::set_next_pass(RID p_pass) { bool SceneShaderForwardMobile::MaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton; - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardMobile::MATERIAL_UNIFORM_SET, RD::BARRIER_MASK_RASTER); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardMobile::MATERIAL_UNIFORM_SET, true, RD::BARRIER_MASK_RASTER); } SceneShaderForwardMobile::MaterialData::~MaterialData() { diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index bbb53f7b97..4c14ef7b9e 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1627,7 +1627,7 @@ void RendererCanvasRenderRD::light_update_shadow(RID p_rid, int p_shadow_index, ShadowRenderPushConstant push_constant; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { - push_constant.projection[y * 4 + x] = projection.matrix[y][x]; + push_constant.projection[y * 4 + x] = projection.columns[y][x]; } } static const Vector2 directions[4] = { Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1) }; @@ -1702,7 +1702,7 @@ void RendererCanvasRenderRD::light_update_directional_shadow(RID p_rid, int p_sh ShadowRenderPushConstant push_constant; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { - push_constant.projection[y * 4 + x] = projection.matrix[y][x]; + push_constant.projection[y * 4 + x] = projection.columns[y][x]; } } @@ -1770,7 +1770,7 @@ void RendererCanvasRenderRD::render_sdf(RID p_render_target, LightOccluderInstan ShadowRenderPushConstant push_constant; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { - push_constant.projection[y * 4 + x] = projection.matrix[y][x]; + push_constant.projection[y * 4 + x] = projection.columns[y][x]; } } @@ -2324,7 +2324,7 @@ RendererRD::MaterialStorage::ShaderData *RendererCanvasRenderRD::_create_shader_ bool RendererCanvasRenderRD::CanvasMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton); - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, canvas_singleton->shader.canvas_shader.version_get_shader(shader_data->version, 0), MATERIAL_UNIFORM_SET); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, canvas_singleton->shader.canvas_shader.version_get_shader(shader_data->version, 0), MATERIAL_UNIFORM_SET, false); } RendererCanvasRenderRD::CanvasMaterialData::~CanvasMaterialData() { diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp index a50742f91a..dd3f62e509 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -44,13 +44,9 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID } for (int i = 0; i < p_amount; i++) { - RID texture = texture_storage->render_target_get_texture(p_render_targets[i].render_target); - ERR_CONTINUE(texture.is_null()); - RID rd_texture = texture_storage->texture_get_rd_texture(texture); + RID rd_texture = texture_storage->render_target_get_rd_texture(p_render_targets[i].render_target); ERR_CONTINUE(rd_texture.is_null()); - // TODO if keep_3d_linear was set when rendering to this render target we need to add a linear->sRGB conversion in. - if (!render_target_descriptors.has(rd_texture) || !RD::get_singleton()->uniform_set_is_valid(render_target_descriptors[rd_texture])) { Vector<RD::Uniform> uniforms; RD::Uniform u; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index acc964b3f3..6157d7d840 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -828,7 +828,7 @@ void RendererSceneRenderRD::_render_buffers_debug_draw(Ref<RenderSceneBuffersRD> if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS) { if (RendererRD::LightStorage::get_singleton()->directional_shadow_get_texture().is_valid()) { RID shadow_atlas_texture = RendererRD::LightStorage::get_singleton()->directional_shadow_get_texture(); - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(shadow_atlas_texture, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2i(Vector2(), rtsize / 2), false, true); } @@ -838,7 +838,7 @@ void RendererSceneRenderRD::_render_buffers_debug_draw(Ref<RenderSceneBuffersRD> RID decal_atlas = RendererRD::TextureStorage::get_singleton()->decal_atlas_get_texture(); if (decal_atlas.is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(decal_atlas, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2i(Vector2(), rtsize / 2), false, false, true); } @@ -846,7 +846,7 @@ void RendererSceneRenderRD::_render_buffers_debug_draw(Ref<RenderSceneBuffersRD> if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_SCENE_LUMINANCE) { if (p_render_buffers->luminance.current.is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(p_render_buffers->luminance.current, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2(Vector2(), rtsize / 8), false, true); } @@ -859,13 +859,13 @@ void RendererSceneRenderRD::_render_buffers_debug_draw(Ref<RenderSceneBuffersRD> if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_OCCLUDERS) { if (p_occlusion_buffer.is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(texture_storage->texture_get_rd_texture(p_occlusion_buffer), texture_storage->render_target_get_rd_framebuffer(render_target), Rect2i(Vector2(), rtsize), true, false); } } if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_MOTION_VECTORS && _render_buffers_get_velocity_texture(p_render_buffers).is_valid()) { - Size2 rtsize = texture_storage->render_target_get_size(render_target); + Size2i rtsize = texture_storage->render_target_get_size(render_target); copy_effects->copy_to_fb_rect(_render_buffers_get_velocity_texture(p_render_buffers), texture_storage->render_target_get_rd_framebuffer(render_target), Rect2(Vector2(), rtsize), false, false); } } diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index 36f1bc0316..1fecac9045 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp @@ -720,7 +720,7 @@ _FORCE_INLINE_ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataTy Projection v = value; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - gui[i * 4 + j] = v.matrix[i][j]; + gui[i * 4 + j] = v.columns[i][j]; } } } @@ -1270,7 +1270,7 @@ void MaterialStorage::MaterialData::free_parameters_uniform_set(RID p_uniform_se } } -bool MaterialStorage::MaterialData::update_parameters_uniform_set(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, const HashMap<StringName, HashMap<int, RID>> &p_default_texture_params, uint32_t p_ubo_size, RID &uniform_set, RID p_shader, uint32_t p_shader_uniform_set, uint32_t p_barrier) { +bool MaterialStorage::MaterialData::update_parameters_uniform_set(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, const HashMap<StringName, HashMap<int, RID>> &p_default_texture_params, uint32_t p_ubo_size, RID &uniform_set, RID p_shader, uint32_t p_shader_uniform_set, bool p_use_linear_color, uint32_t p_barrier) { if ((uint32_t)ubo_data.size() != p_ubo_size) { p_uniform_dirty = true; if (uniform_buffer.is_valid()) { @@ -1294,7 +1294,7 @@ bool MaterialStorage::MaterialData::update_parameters_uniform_set(const HashMap< //check whether buffer changed if (p_uniform_dirty && ubo_data.size()) { - update_uniform_buffer(p_uniforms, p_uniform_offsets, p_parameters, ubo_data.ptrw(), ubo_data.size(), true); + update_uniform_buffer(p_uniforms, p_uniform_offsets, p_parameters, ubo_data.ptrw(), ubo_data.size(), p_use_linear_color); RD::get_singleton()->buffer_update(uniform_buffer, 0, ubo_data.size(), ubo_data.ptrw(), p_barrier); } diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.h b/servers/rendering/renderer_rd/storage_rd/material_storage.h index d496eed17a..19c2f39553 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.h @@ -79,7 +79,7 @@ public: virtual ~MaterialData(); //to be used internally by update_parameters, in the most common configuration of material parameters - bool update_parameters_uniform_set(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, const HashMap<StringName, HashMap<int, RID>> &p_default_texture_params, uint32_t p_ubo_size, RID &uniform_set, RID p_shader, uint32_t p_shader_uniform_set, uint32_t p_barrier = RD::BARRIER_MASK_ALL); + bool update_parameters_uniform_set(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, const HashMap<StringName, HashMap<int, RID>> &p_default_texture_params, uint32_t p_ubo_size, RID &uniform_set, RID p_shader, uint32_t p_shader_uniform_set, bool p_use_linear_color, uint32_t p_barrier = RD::BARRIER_MASK_ALL); void free_parameters_uniform_set(RID p_uniform_set); private: @@ -302,7 +302,7 @@ public: static _FORCE_INLINE_ void store_camera(const Projection &p_mtx, float *p_array) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - p_array[i * 4 + j] = p_mtx.matrix[i][j]; + p_array[i * 4 + j] = p_mtx.columns[i][j]; } } } diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index 18303ce870..78f4c410c3 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp @@ -1712,7 +1712,7 @@ MaterialStorage::ShaderData *ParticlesStorage::_create_particles_shader_func() { } bool ParticlesStorage::ParticleProcessMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) { - return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, ParticlesStorage::get_singleton()->particles_shader.shader.version_get_shader(shader_data->version, 0), 3); + return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, ParticlesStorage::get_singleton()->particles_shader.shader.version_get_shader(shader_data->version, 0), 3, true); } ParticlesStorage::ParticleProcessMaterialData::~ParticleProcessMaterialData() { diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index 1b2237b505..0c2092f03e 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -178,7 +178,7 @@ void RenderSceneBuffersRD::configure(RID p_render_target, const Size2i p_interna // Create our depth buffer { - // TODO If we have depth buffer supplied externally, pick this up + // TODO Lazy create this in case we've got an external depth buffer RD::DataFormat format; uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT; @@ -490,6 +490,28 @@ Ref<RenderBufferCustomDataRD> RenderSceneBuffersRD::get_custom_data(const String return ret; } +// Depth texture + +RID RenderSceneBuffersRD::get_depth_texture() { + RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); + RID depth = texture_storage->render_target_get_override_depth(render_target); + if (depth.is_valid()) { + return depth; + } else { + return get_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH); + } +} + +RID RenderSceneBuffersRD::get_depth_texture(const uint32_t p_layer) { + RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); + RID depth_slice = texture_storage->render_target_get_override_depth_slice(render_target, p_layer); + if (depth_slice.is_valid()) { + return depth_slice; + } else { + return get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_DEPTH, p_layer, 0); + } +} + // Velocity texture. void RenderSceneBuffersRD::ensure_velocity() { @@ -516,6 +538,20 @@ void RenderSceneBuffersRD::ensure_velocity() { } } +bool RenderSceneBuffersRD::has_velocity_buffer(bool p_has_msaa) { + if (p_has_msaa) { + return has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA); + } else { + RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); + RID velocity = texture_storage->render_target_get_override_velocity(render_target); + if (velocity.is_valid()) { + return true; + } else { + return has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY); + } + } +} + RID RenderSceneBuffersRD::get_velocity_buffer(bool p_get_msaa) { if (p_get_msaa) { if (!has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA)) { @@ -524,10 +560,28 @@ RID RenderSceneBuffersRD::get_velocity_buffer(bool p_get_msaa) { return get_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA); } } else { - if (!has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY)) { + RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); + RID velocity = texture_storage->render_target_get_override_velocity(render_target); + if (velocity.is_valid()) { + return velocity; + } else if (!has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY)) { return RID(); } else { return get_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY); } } } + +RID RenderSceneBuffersRD::get_velocity_buffer(bool p_get_msaa, uint32_t p_layer) { + if (p_get_msaa) { + return get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA, p_layer, 0); + } else { + RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); + RID velocity_slice = texture_storage->render_target_get_override_velocity_slice(render_target, p_layer); + if (velocity_slice.is_valid()) { + return velocity_slice; + } else { + return get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY, p_layer, 0); + } + } +} diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h index 169ee2e2b1..6907f69b93 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h @@ -189,12 +189,8 @@ public: return get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_COLOR, p_layer, 0); } - _FORCE_INLINE_ RID get_depth_texture() const { - return get_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH); - } - _FORCE_INLINE_ RID get_depth_texture(const uint32_t p_layer) { - return get_texture_slice(RB_SCOPE_BUFFERS, RB_TEX_DEPTH, p_layer, 0); - } + RID get_depth_texture(); + RID get_depth_texture(const uint32_t p_layer); // back buffer (color) RID get_back_buffer_texture() const { return has_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0) ? get_texture(RB_SCOPE_BUFFERS, RB_TEX_BLUR_0) : RID(); } // We (re)use our blur texture here. @@ -202,9 +198,9 @@ public: // Velocity, currently only used by TAA (Clustered) but we'll be using this in other places soon too. void ensure_velocity(); - bool has_velocity_buffer(bool p_has_msaa) { return has_texture(RB_SCOPE_BUFFERS, p_has_msaa ? RB_TEX_VELOCITY_MSAA : RB_TEX_VELOCITY); } + bool has_velocity_buffer(bool p_has_msaa); RID get_velocity_buffer(bool p_get_msaa); - RID get_velocity_buffer(bool p_get_msaa, uint32_t p_layer) { return get_texture_slice(RB_SCOPE_BUFFERS, p_get_msaa ? RB_TEX_VELOCITY_MSAA : RB_TEX_VELOCITY, p_layer, 0); } + RID get_velocity_buffer(bool p_get_msaa, uint32_t p_layer); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Everything after this needs to be re-evaluated, this is all old implementation diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index 14e41a0d6b..b3a7e05ff5 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -30,6 +30,7 @@ #include "texture_storage.h" #include "../effects/copy_effects.h" +#include "../framebuffer_cache_rd.h" #include "material_storage.h" #include "servers/rendering/renderer_rd/renderer_scene_render_rd.h" @@ -2359,10 +2360,26 @@ void TextureStorage::update_decal_buffer(const PagedArray<RID> &p_decals, const /* RENDER TARGET API */ +RID TextureStorage::RenderTarget::get_framebuffer() { + // Note that if we're using an overridden color buffer, we're likely cycling through a texture chain. + // this is where our framebuffer cache comes in clutch.. + + if (msaa != RS::VIEWPORT_MSAA_DISABLED) { + return FramebufferCacheRD::get_singleton()->get_cache_multiview(view_count, color_multisample, overridden.color.is_valid() ? overridden.color : color); + } else { + return FramebufferCacheRD::get_singleton()->get_cache_multiview(view_count, overridden.color.is_valid() ? overridden.color : color); + } +} + void TextureStorage::_clear_render_target(RenderTarget *rt) { - //free in reverse dependency order - if (rt->framebuffer.is_valid()) { - RD::get_singleton()->free(rt->framebuffer); + // clear overrides, we assume these are freed by the object that created them + rt->overridden.color = RID(); + rt->overridden.depth = RID(); + rt->overridden.velocity = RID(); + rt->overridden.cached_slices.clear(); // these are automatically freed when their parent textures are freed so just clear + + // free in reverse dependency order + if (rt->framebuffer_uniform_set.is_valid()) { rt->framebuffer_uniform_set = RID(); //chain deleted } @@ -2384,7 +2401,6 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { _render_target_clear_sdf(rt); - rt->framebuffer = RID(); rt->color = RID(); rt->color_multisample = RID(); } @@ -2432,11 +2448,10 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { } } + // TODO see if we can lazy create this once we actually use it as we may not need to create this if we have an overridden color buffer... rt->color = RD::get_singleton()->texture_create(rd_color_attachment_format, rd_view); ERR_FAIL_COND(rt->color.is_null()); - Vector<RID> fb_textures; - if (rt->msaa != RS::VIEWPORT_MSAA_DISABLED) { // Use the texture format of the color attachment for the multisample color attachment. RD::TextureFormat rd_color_multisample_format = rd_color_attachment_format; @@ -2450,15 +2465,8 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { RD::TextureView rd_view_multisample; rd_color_multisample_format.is_resolve_buffer = false; rt->color_multisample = RD::get_singleton()->texture_create(rd_color_multisample_format, rd_view_multisample); - fb_textures.push_back(rt->color_multisample); ERR_FAIL_COND(rt->color_multisample.is_null()); } - fb_textures.push_back(rt->color); - rt->framebuffer = RD::get_singleton()->framebuffer_create(fb_textures, RenderingDevice::INVALID_ID, rt->view_count); - if (rt->framebuffer.is_null()) { - _clear_render_target(rt); - ERR_FAIL_COND(rt->framebuffer.is_null()); - } { //update texture @@ -2568,6 +2576,11 @@ void TextureStorage::render_target_set_position(RID p_render_target, int p_x, in //unused for this render target } +Point2i TextureStorage::render_target_get_position(RID p_render_target) const { + //unused for this render target + return Point2i(); +} + void TextureStorage::render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND(!rt); @@ -2579,6 +2592,13 @@ void TextureStorage::render_target_set_size(RID p_render_target, int p_width, in } } +Size2i TextureStorage::render_target_get_size(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, Size2i()); + + return rt->size; +} + RID TextureStorage::render_target_get_texture(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, RID()); @@ -2586,7 +2606,84 @@ RID TextureStorage::render_target_get_texture(RID p_render_target) { return rt->texture; } -void TextureStorage::render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) { +void TextureStorage::render_target_set_override_color(RID p_render_target, RID p_texture) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + + rt->overridden.color = p_texture; +} + +RID TextureStorage::render_target_get_override_color(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RID()); + + return rt->overridden.color; +} + +void TextureStorage::render_target_set_override_depth(RID p_render_target, RID p_texture) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + + rt->overridden.depth = p_texture; +} + +RID TextureStorage::render_target_get_override_depth(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RID()); + + return rt->overridden.depth; +} + +RID TextureStorage::render_target_get_override_depth_slice(RID p_render_target, const uint32_t p_layer) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RID()); + + if (rt->overridden.depth.is_null()) { + return RID(); + } else if (rt->view_count == 1) { + return rt->overridden.depth; + } else { + RenderTarget::RTOverridden::SliceKey key(rt->overridden.depth, p_layer); + + if (!rt->overridden.cached_slices.has(key)) { + rt->overridden.cached_slices[key] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rt->overridden.depth, p_layer, 0); + } + + return rt->overridden.cached_slices[key]; + } +} + +void TextureStorage::render_target_set_override_velocity(RID p_render_target, RID p_texture) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND(!rt); + + rt->overridden.velocity = p_texture; +} + +RID TextureStorage::render_target_get_override_velocity(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RID()); + + return rt->overridden.velocity; +} + +RID TextureStorage::render_target_get_override_velocity_slice(RID p_render_target, const uint32_t p_layer) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, RID()); + + if (rt->overridden.velocity.is_null()) { + return RID(); + } else if (rt->view_count == 1) { + return rt->overridden.velocity; + } else { + RenderTarget::RTOverridden::SliceKey key(rt->overridden.velocity, p_layer); + + if (!rt->overridden.cached_slices.has(key)) { + rt->overridden.cached_slices[key] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rt->overridden.velocity, p_layer, 0); + } + + return rt->overridden.cached_slices[key]; + } } void TextureStorage::render_target_set_transparent(RID p_render_target, bool p_is_transparent) { @@ -2596,10 +2693,21 @@ void TextureStorage::render_target_set_transparent(RID p_render_target, bool p_i _update_render_target(rt); } +bool TextureStorage::render_target_get_transparent(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_COND_V(!rt, false); + + return rt->is_transparent; +} + void TextureStorage::render_target_set_direct_to_screen(RID p_render_target, bool p_value) { } -bool TextureStorage::render_target_was_used(RID p_render_target) { +bool TextureStorage::render_target_get_direct_to_screen(RID p_render_target) const { + return false; +} + +bool TextureStorage::render_target_was_used(RID p_render_target) const { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, false); return rt->was_used; @@ -2622,25 +2730,29 @@ void TextureStorage::render_target_set_msaa(RID p_render_target, RS::ViewportMSA _update_render_target(rt); } -Size2 TextureStorage::render_target_get_size(RID p_render_target) { +RS::ViewportMSAA TextureStorage::render_target_get_msaa(RID p_render_target) const { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); - ERR_FAIL_COND_V(!rt, Size2()); + ERR_FAIL_COND_V(!rt, RS::VIEWPORT_MSAA_DISABLED); - return rt->size; + return rt->msaa; } RID TextureStorage::render_target_get_rd_framebuffer(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, RID()); - return rt->framebuffer; + return rt->get_framebuffer(); } RID TextureStorage::render_target_get_rd_texture(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_COND_V(!rt, RID()); - return rt->color; + if (rt->overridden.color.is_valid()) { + return rt->overridden.color; + } else { + return rt->color; + } } RID TextureStorage::render_target_get_rd_texture_slice(RID p_render_target, uint32_t p_layer) { @@ -2711,7 +2823,7 @@ void TextureStorage::render_target_do_clear_request(RID p_render_target) { } Vector<Color> clear_colors; clear_colors.push_back(rt->clear_color); - RD::get_singleton()->draw_list_begin(rt->framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::get_singleton()->draw_list_begin(rt->get_framebuffer(), RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, clear_colors); RD::get_singleton()->draw_list_end(); rt->clear_requested = false; } @@ -3140,18 +3252,18 @@ void TextureStorage::render_target_set_vrs_mode(RID p_render_target, RS::Viewpor rt->vrs_mode = p_mode; } -void TextureStorage::render_target_set_vrs_texture(RID p_render_target, RID p_texture) { +RS::ViewportVRSMode TextureStorage::render_target_get_vrs_mode(RID p_render_target) const { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); - ERR_FAIL_COND(!rt); + ERR_FAIL_COND_V(!rt, RS::VIEWPORT_VRS_DISABLED); - rt->vrs_texture = p_texture; + return rt->vrs_mode; } -RS::ViewportVRSMode TextureStorage::render_target_get_vrs_mode(RID p_render_target) const { +void TextureStorage::render_target_set_vrs_texture(RID p_render_target, RID p_texture) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); - ERR_FAIL_COND_V(!rt, RS::VIEWPORT_VRS_DISABLED); + ERR_FAIL_COND(!rt); - return rt->vrs_mode; + rt->vrs_texture = p_texture; } RID TextureStorage::render_target_get_vrs_texture(RID p_render_target) const { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index 327a21a1fc..00b4e50737 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -301,7 +301,6 @@ private: struct RenderTarget { Size2i size; uint32_t view_count; - RID framebuffer; RID color; Vector<RID> color_slices; RID color_multisample; // Needed when MSAA is enabled. @@ -339,6 +338,43 @@ private: RS::ViewportVRSMode vrs_mode = RS::VIEWPORT_VRS_DISABLED; RID vrs_texture; + // overridden textures + struct RTOverridden { + RID color; + RID depth; + RID velocity; + + // In a multiview scenario, which is the most likely where we + // override our destination textures, we need to obtain slices + // for each layer of these textures. + // These are likely changing every frame as we loop through + // texture chains hence we add a cache to manage these slices. + // For this we define a key using the RID of the texture and + // the layer for which we create a slice. + struct SliceKey { + RID rid; + uint32_t layer = 0; + + bool operator==(const SliceKey &p_val) const { + return (rid == p_val.rid) && (layer == p_val.layer); + } + + static uint32_t hash(const SliceKey &p_val) { + uint32_t h = hash_one_uint64(p_val.rid.get_id()); + h = hash_murmur3_one_32(p_val.layer, h); + return hash_fmix32(h); + } + + SliceKey() {} + SliceKey(RID p_rid, uint32_t p_layer) { + rid = p_rid; + layer = p_layer; + } + }; + + mutable HashMap<SliceKey, RID, SliceKey> cached_slices; + } overridden; + //texture generated for this owner (nor RD). RID texture; bool was_used; @@ -346,6 +382,8 @@ private: //clear request bool clear_requested; Color clear_color; + + RID get_framebuffer(); }; mutable RID_Owner<RenderTarget> render_target_owner; @@ -644,14 +682,17 @@ public: virtual void render_target_free(RID p_rid) override; virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) override; + virtual Point2i render_target_get_position(RID p_render_target) const override; virtual void render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) override; - virtual RID render_target_get_texture(RID p_render_target) override; - virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) override; + virtual Size2i render_target_get_size(RID p_render_target) const override; virtual void render_target_set_transparent(RID p_render_target, bool p_is_transparent) override; + virtual bool render_target_get_transparent(RID p_render_target) const override; virtual void render_target_set_direct_to_screen(RID p_render_target, bool p_direct_to_screen) override; - virtual bool render_target_was_used(RID p_render_target) override; + virtual bool render_target_get_direct_to_screen(RID p_render_target) const override; + virtual bool render_target_was_used(RID p_render_target) const override; virtual void render_target_set_as_unused(RID p_render_target) override; virtual void render_target_set_msaa(RID p_render_target, RS::ViewportMSAA p_msaa) override; + virtual RS::ViewportMSAA render_target_get_msaa(RID p_render_target) const override; void render_target_copy_to_back_buffer(RID p_render_target, const Rect2i &p_region, bool p_gen_mipmaps); void render_target_clear_back_buffer(RID p_render_target, const Rect2i &p_region, const Color &p_color); @@ -673,12 +714,21 @@ public: bool render_target_is_sdf_enabled(RID p_render_target) const; virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) override; + virtual RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const override; virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) override; + virtual RID render_target_get_vrs_texture(RID p_render_target) const override; - RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const; - RID render_target_get_vrs_texture(RID p_render_target) const; + virtual void render_target_set_override_color(RID p_render_target, RID p_texture) override; + virtual RID render_target_get_override_color(RID p_render_target) const override; + virtual void render_target_set_override_depth(RID p_render_target, RID p_texture) override; + virtual RID render_target_get_override_depth(RID p_render_target) const override; + RID render_target_get_override_depth_slice(RID p_render_target, const uint32_t p_layer) const; + virtual void render_target_set_override_velocity(RID p_render_target, RID p_texture) override; + virtual RID render_target_get_override_velocity(RID p_render_target) const override; + RID render_target_get_override_velocity_slice(RID p_render_target, const uint32_t p_layer) const; + + virtual RID render_target_get_texture(RID p_render_target) override; - Size2 render_target_get_size(RID p_render_target); RID render_target_get_rd_framebuffer(RID p_render_target); RID render_target_get_rd_texture(RID p_render_target); RID render_target_get_rd_texture_slice(RID p_render_target, uint32_t p_layer); diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 1db018b647..4f7683493b 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -664,9 +664,9 @@ void RendererViewport::draw_viewports() { RSG::texture_storage->render_target_set_as_unused(vp->render_target); if (vp->use_xr && xr_interface.is_valid()) { - // check for an external texture destination (disabled for now, not yet supported) - // RSG::texture_storage->render_target_set_external_texture(vp->render_target, xr_interface->get_external_texture_for_eye(leftOrMono)); - RSG::texture_storage->render_target_set_external_texture(vp->render_target, 0); + RSG::texture_storage->render_target_set_override_color(vp->render_target, xr_interface->get_color_texture()); + RSG::texture_storage->render_target_set_override_depth(vp->render_target, xr_interface->get_depth_texture()); + RSG::texture_storage->render_target_set_override_velocity(vp->render_target, xr_interface->get_velocity_texture()); // render... RSG::scene->set_debug_draw_mode(vp->debug_draw); @@ -695,7 +695,9 @@ void RendererViewport::draw_viewports() { } } } else { - RSG::texture_storage->render_target_set_external_texture(vp->render_target, 0); + RSG::texture_storage->render_target_set_override_color(vp->render_target, RID()); // TODO if fullscreen output, we can set this to our texture chain + RSG::texture_storage->render_target_set_override_depth(vp->render_target, RID()); + RSG::texture_storage->render_target_set_override_velocity(vp->render_target, RID()); RSG::scene->set_debug_draw_mode(vp->debug_draw); diff --git a/servers/rendering/storage/texture_storage.h b/servers/rendering/storage/texture_storage.h index 5024a76c09..635f44786c 100644 --- a/servers/rendering/storage/texture_storage.h +++ b/servers/rendering/storage/texture_storage.h @@ -131,15 +131,18 @@ public: virtual RID render_target_create() = 0; virtual void render_target_free(RID p_rid) = 0; - virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) = 0; - virtual void render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) = 0; - virtual RID render_target_get_texture(RID p_render_target) = 0; - virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) = 0; + virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) = 0; // Q change input to const Point2i &p_position ? + virtual Point2i render_target_get_position(RID p_render_target) const = 0; + virtual void render_target_set_size(RID p_render_target, int p_width, int p_height, uint32_t p_view_count) = 0; // Q change input to const Size2i &p_size ? + virtual Size2i render_target_get_size(RID p_render_target) const = 0; virtual void render_target_set_transparent(RID p_render_target, bool p_is_transparent) = 0; + virtual bool render_target_get_transparent(RID p_render_target) const = 0; virtual void render_target_set_direct_to_screen(RID p_render_target, bool p_direct_to_screen) = 0; - virtual bool render_target_was_used(RID p_render_target) = 0; + virtual bool render_target_get_direct_to_screen(RID p_render_target) const = 0; + virtual bool render_target_was_used(RID p_render_target) const = 0; virtual void render_target_set_as_unused(RID p_render_target) = 0; virtual void render_target_set_msaa(RID p_render_target, RS::ViewportMSAA p_msaa) = 0; + virtual RS::ViewportMSAA render_target_get_msaa(RID p_render_target) const = 0; virtual void render_target_request_clear(RID p_render_target, const Color &p_clear_color) = 0; virtual bool render_target_is_clear_requested(RID p_render_target) = 0; @@ -152,7 +155,20 @@ public: virtual void render_target_mark_sdf_enabled(RID p_render_target, bool p_enabled) = 0; virtual void render_target_set_vrs_mode(RID p_render_target, RS::ViewportVRSMode p_mode) = 0; + virtual RS::ViewportVRSMode render_target_get_vrs_mode(RID p_render_target) const = 0; virtual void render_target_set_vrs_texture(RID p_render_target, RID p_texture) = 0; + virtual RID render_target_get_vrs_texture(RID p_render_target) const = 0; + + // override color, depth and velocity buffers (depth and velocity only for 3D) + virtual void render_target_set_override_color(RID p_render_target, RID p_texture) = 0; + virtual RID render_target_get_override_color(RID p_render_target) const = 0; + virtual void render_target_set_override_depth(RID p_render_target, RID p_texture) = 0; + virtual RID render_target_get_override_depth(RID p_render_target) const = 0; + virtual void render_target_set_override_velocity(RID p_render_target, RID p_texture) = 0; + virtual RID render_target_get_override_velocity(RID p_render_target) const = 0; + + // get textures + virtual RID render_target_get_texture(RID p_render_target) = 0; }; #endif // TEXTURE_STORAGE_H diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 57378708ba..750121719f 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -701,6 +701,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint } uint32_t RenderingServer::mesh_surface_get_format_offset(uint32_t p_format, int p_vertex_len, int p_array_index) const { + ERR_FAIL_INDEX_V(p_array_index, ARRAY_MAX, 0); p_format &= ~ARRAY_FORMAT_INDEX; uint32_t offsets[ARRAY_MAX]; uint32_t vstr; diff --git a/servers/xr/xr_interface.cpp b/servers/xr/xr_interface.cpp index 430a5bfd16..430e2042d9 100644 --- a/servers/xr/xr_interface.cpp +++ b/servers/xr/xr_interface.cpp @@ -241,6 +241,19 @@ RID XRInterface::get_vrs_texture() { } /** these are optional, so we want dummies **/ + +RID XRInterface::get_color_texture() { + return RID(); +} + +RID XRInterface::get_depth_texture() { + return RID(); +} + +RID XRInterface::get_velocity_texture() { + return RID(); +} + PackedStringArray XRInterface::get_suggested_tracker_names() const { PackedStringArray arr; diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h index 17ff5f8add..86d328d41c 100644 --- a/servers/xr/xr_interface.h +++ b/servers/xr/xr_interface.h @@ -121,8 +121,9 @@ public: virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) = 0; /* get each views transform */ virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) = 0; /* get each view projection matrix */ virtual RID get_vrs_texture(); /* obtain VRS texture */ - - // note, external color/depth/vrs texture support will be added here soon. + virtual RID get_color_texture(); /* obtain color output texture (if applicable) */ + virtual RID get_depth_texture(); /* obtain depth output texture (if applicable, used for reprojection) */ + virtual RID get_velocity_texture(); /* obtain velocity output texture (if applicable, used for spacewarp) */ virtual void process() = 0; virtual void pre_render(){}; diff --git a/servers/xr/xr_interface_extension.cpp b/servers/xr/xr_interface_extension.cpp index 7024a25528..0d4a53d287 100644 --- a/servers/xr/xr_interface_extension.cpp +++ b/servers/xr/xr_interface_extension.cpp @@ -74,6 +74,15 @@ void XRInterfaceExtension::_bind_methods() { GDVIRTUAL_BIND(_set_anchor_detection_is_enabled, "enabled"); GDVIRTUAL_BIND(_get_camera_feed_id); + // override output methods + GDVIRTUAL_BIND(_get_color_texture); + GDVIRTUAL_BIND(_get_depth_texture); + GDVIRTUAL_BIND(_get_velocity_texture); + + ClassDB::bind_method(D_METHOD("get_color_texture"), &XRInterfaceExtension::get_color_texture); + ClassDB::bind_method(D_METHOD("get_depth_texture"), &XRInterfaceExtension::get_depth_texture); + ClassDB::bind_method(D_METHOD("get_velocity_texture"), &XRInterfaceExtension::get_velocity_texture); + // helper methods ClassDB::bind_method(D_METHOD("add_blit", "render_target", "src_rect", "dst_rect", "use_layer", "layer", "apply_lens_distortion", "eye_center", "k1", "k2", "upscale", "aspect_ratio"), &XRInterfaceExtension::add_blit); ClassDB::bind_method(D_METHOD("get_render_target_texture", "render_target"), &XRInterfaceExtension::get_render_target_texture); @@ -264,7 +273,7 @@ Projection XRInterfaceExtension::get_projection_for_view(uint32_t p_view, double if (GDVIRTUAL_CALL(_get_projection_for_view, p_view, p_aspect, p_z_near, p_z_far, arr)) { ERR_FAIL_COND_V_MSG(arr.size() != 16, Projection(), "Projection matrix must contain 16 floats"); - real_t *m = (real_t *)cm.matrix; + real_t *m = (real_t *)cm.columns; for (int i = 0; i < 16; i++) { m[i] = arr[i]; } @@ -283,6 +292,33 @@ RID XRInterfaceExtension::get_vrs_texture() { } } +RID XRInterfaceExtension::get_color_texture() { + RID texture; + if (GDVIRTUAL_CALL(_get_color_texture, texture)) { + return texture; + } else { + return RID(); + } +} + +RID XRInterfaceExtension::get_depth_texture() { + RID texture; + if (GDVIRTUAL_CALL(_get_depth_texture, texture)) { + return texture; + } else { + return RID(); + } +} + +RID XRInterfaceExtension::get_velocity_texture() { + RID texture; + if (GDVIRTUAL_CALL(_get_velocity_texture, texture)) { + return texture; + } else { + return RID(); + } +} + void XRInterfaceExtension::add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer, uint32_t p_layer, bool p_apply_lens_distortion, Vector2 p_eye_center, double p_k1, double p_k2, double p_upscale, double p_aspect_ratio) { BlitToScreen blit; diff --git a/servers/xr/xr_interface_extension.h b/servers/xr/xr_interface_extension.h index 65b474425e..2235b57cb3 100644 --- a/servers/xr/xr_interface_extension.h +++ b/servers/xr/xr_interface_extension.h @@ -102,6 +102,9 @@ public: virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; virtual RID get_vrs_texture() override; + virtual RID get_color_texture() override; + virtual RID get_depth_texture() override; + virtual RID get_velocity_texture() override; GDVIRTUAL0R(Size2, _get_render_target_size); GDVIRTUAL0R(uint32_t, _get_view_count); @@ -109,6 +112,9 @@ public: GDVIRTUAL2R(Transform3D, _get_transform_for_view, uint32_t, const Transform3D &); GDVIRTUAL4R(PackedFloat64Array, _get_projection_for_view, uint32_t, double, double, double); GDVIRTUAL0R(RID, _get_vrs_texture); + GDVIRTUAL0R(RID, _get_color_texture); + GDVIRTUAL0R(RID, _get_depth_texture); + GDVIRTUAL0R(RID, _get_velocity_texture); void add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer = false, uint32_t p_layer = 0, bool p_apply_lens_distortion = false, Vector2 p_eye_center = Vector2(), double p_k1 = 0.0, double p_k2 = 0.0, double p_upscale = 1.0, double p_aspect_ratio = 1.0); diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 225316b293..6b831bc9c7 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -93,10 +93,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "test text"); - CHECK(text_edit->get_caret_column() == 9); + CHECK(text_edit->get_caret_column() == 0); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_set"); text_edit->redo(); @@ -104,18 +104,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_text() == ""); CHECK(text_edit->get_caret_column() == 0); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_set"); // Cannot undo when not-editable but should still clear. text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "test text"); - CHECK(text_edit->get_caret_column() == 9); + CHECK(text_edit->get_caret_column() == 0); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_set"); // Clear. @@ -131,8 +131,8 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_text() == ""); CHECK(text_edit->get_caret_column() == 0); SIGNAL_CHECK("text_set", empty_signal_args); - SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_clear_args); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); text_edit->set_editable(true); @@ -252,6 +252,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_line(0) == "te"); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "te"); + CHECK(text_edit->get_caret_column() == 2); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -263,6 +264,8 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_line(0) == "test text"); CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "test"); + CHECK(text_edit->get_caret_column() == 4); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -271,7 +274,8 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->redo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_line(0) == "te"); - CHECK_FALSE(text_edit->has_selection()); // Currently not handled. + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_column() == 2); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -425,7 +429,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "testing\nswap"); - CHECK_FALSE(text_edit->has_selection()); // Not currently handled. + CHECK(text_edit->has_selection()); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -436,7 +440,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->redo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "new\ntesting\nswap"); - CHECK_FALSE(text_edit->has_selection()); // Not currently handled. + CHECK(text_edit->has_selection()); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -448,7 +452,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->has_selection()); CHECK(text_edit->get_selection_from_line() == 0); CHECK(text_edit->get_selection_to_line() == 2); - SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("caret_changed"); ((Array)lines_edited_args[0])[0] = 2; ((Array)lines_edited_args[0])[1] = 3; @@ -533,7 +537,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "temidsting\nswap"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 10); + CHECK(text_edit->get_caret_column() == 5); CHECK(text_edit->has_selection()); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -950,11 +954,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->delete_selection(); CHECK(text_edit->get_text() == "this is some text\nfor selection"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 8); text_edit->select(0, 8, 0, 4); CHECK(text_edit->has_selection()); SEND_GUI_ACTION(text_edit, "ui_text_backspace"); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); text_edit->undo(); CHECK(text_edit->has_selection()); @@ -966,7 +974,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->get_caret_column() == 4); text_edit->undo(); CHECK(text_edit->has_selection()); @@ -991,7 +999,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->get_caret_column() == 4); text_edit->undo(); CHECK(text_edit->has_selection()); @@ -1176,8 +1184,8 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "this is\nsome\n"); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1187,9 +1195,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->redo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "some\n"); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1259,9 +1267,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { lines_edited_args.push_back(args1); SUBCASE("[TextEdit] ui_text_newline_above") { - text_edit->set_text("this is some test text."); + text_edit->set_text("this is some test text.\nthis is some test text."); text_edit->select(0, 0, 0, 4); text_edit->set_caret_column(4); + + text_edit->add_caret(1, 4); + text_edit->select(1, 0, 1, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1269,50 +1282,78 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - ((Array)lines_edited_args[0])[1] = 1; + // For the second caret. + Array args2; + args2.push_back(0); + args2.push_back(1); + lines_edited_args.push_front(args2); + + ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION(text_edit, "ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); + CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->set_caret_line(1); text_edit->set_caret_column(4); - text_edit->select(0, 0, 0, 4); + + text_edit->set_caret_line(3, false, true, 0, 1); + text_edit->set_caret_column(4, false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); + CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 4); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + ((Array)lines_edited_args[0])[0] = 2; + ((Array)lines_edited_args[0])[1] = 3; + SEND_GUI_ACTION(text_edit, "ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n\nthis is some test text."); + CHECK(text_edit->get_text() == "\n\nthis is some test text.\n\n\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 4); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); } SUBCASE("[TextEdit] ui_text_newline_blank") { - text_edit->set_text("this is some test text."); + text_edit->set_text("this is some test text.\nthis is some test text."); text_edit->select(0, 0, 0, 4); text_edit->set_caret_column(4); + + text_edit->add_caret(1, 4); + text_edit->select(1, 0, 1, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1320,13 +1361,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - ((Array)lines_edited_args[0])[1] = 1; + // For the second caret. + Array args2; + args2.push_back(1); + args2.push_back(2); + lines_edited_args.push_front(args2); + + ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION(text_edit, "ui_text_newline_blank"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text.\n"); + CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1334,10 +1385,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_newline_blank"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text.\n"); + CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -1345,9 +1400,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] ui_text_newline") { - text_edit->set_text("this is some test text."); + text_edit->set_text("this is some test text.\nthis is some test text."); text_edit->select(0, 0, 0, 4); text_edit->set_caret_column(4); + + text_edit->add_caret(1, 4); + text_edit->select(1, 0, 1, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1355,14 +1415,27 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - lines_edited_args.push_back(lines_edited_args[0].duplicate()); - ((Array)lines_edited_args[1])[1] = 1; + // For the second caret. + Array args2; + args2.push_back(1); + args2.push_back(1); + lines_edited_args.push_front(args2); + lines_edited_args.push_front(args2.duplicate()); + ((Array)lines_edited_args[1])[1] = 2; + + lines_edited_args.push_back(lines_edited_args[2].duplicate()); + ((Array)lines_edited_args[3])[1] = 1; + SEND_GUI_ACTION(text_edit, "ui_text_newline"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text."); + CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1370,10 +1443,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_newline"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text."); + CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -1381,10 +1458,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] ui_text_backspace_all_to_left") { - text_edit->set_text("\nthis is some test text."); + text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); text_edit->select(1, 0, 1, 4); text_edit->set_caret_line(1); text_edit->set_caret_column(4); + + text_edit->add_caret(3, 4); + text_edit->select(3, 0, 3, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); Ref<InputEvent> tmpevent = InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL); @@ -1395,34 +1477,50 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(3); + args2.push_back(3); + lines_edited_args.push_front(args2); + // With selection should be a normal backspace. - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 1; + ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION(text_edit, "ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text."); + CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[1] = 0; + ((Array)lines_edited_args[0])[1] = 2; + ((Array)lines_edited_args[1])[1] = 0; // Start of line should also be a normal backspace. SEND_GUI_ACTION(text_edit, "ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1433,23 +1531,33 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - ((Array)lines_edited_args[0])[0] = 0; + ((Array)lines_edited_args[0])[0] = 1; + ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 0; SEND_GUI_ACTION(text_edit, "ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_text() == "\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1458,10 +1566,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] ui_text_backspace_word") { - text_edit->set_text("\nthis is some test text."); + text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); text_edit->select(1, 0, 1, 4); text_edit->set_caret_line(1); text_edit->set_caret_column(4); + + text_edit->add_caret(3, 4); + text_edit->select(3, 0, 3, 4, 1); + CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1469,30 +1581,45 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(3); + args2.push_back(3); + lines_edited_args.push_front(args2); + // With selection should be a normal backspace. - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 1; + ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION(text_edit, "ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text."); + CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->end_complex_operation(); - ((Array)lines_edited_args[0])[1] = 0; + ((Array)lines_edited_args[0])[1] = 2; + ((Array)lines_edited_args[1])[1] = 0; // Start of line should also be a normal backspace. SEND_GUI_ACTION(text_edit, "ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1500,16 +1627,21 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1517,24 +1649,35 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - ((Array)lines_edited_args[0])[0] = 0; + ((Array)lines_edited_args[0])[0] = 1; + ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 0; SEND_GUI_ACTION(text_edit, "ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test "); + CHECK(text_edit->get_text() == " is some test \n is some test "); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 14); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 14); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); } SUBCASE("[TextEdit] ui_text_backspace") { - text_edit->set_text("\nthis is some test text."); + text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); text_edit->select(1, 0, 1, 4); text_edit->set_caret_line(1); text_edit->set_caret_column(4); + + text_edit->add_caret(3, 4); + text_edit->select(3, 0, 3, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1542,34 +1685,50 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(3); + args2.push_back(3); + lines_edited_args.push_front(args2); + // With selection should be a normal backspace. - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 1; + ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION(text_edit, "ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text."); + CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[1] = 0; + ((Array)lines_edited_args[0])[1] = 2; + ((Array)lines_edited_args[1])[1] = 0; // Start of line should also be a normal backspace. SEND_GUI_ACTION(text_edit, "ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1580,23 +1739,33 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text."); + CHECK(text_edit->get_text() == " is some test text.\n is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - ((Array)lines_edited_args[0])[0] = 0; + ((Array)lines_edited_args[0])[0] = 1; + ((Array)lines_edited_args[0])[1] = 1; + ((Array)lines_edited_args[1])[0] = 0; SEND_GUI_ACTION(text_edit, "ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text"); + CHECK(text_edit->get_text() == " is some test text\n is some test text"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 18); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 18); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1605,6 +1774,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->select(0, 18, 0, 0); text_edit->set_caret_line(0); text_edit->set_caret_column(0); + + text_edit->select(1, 18, 1, 0, 1); + text_edit->set_caret_line(1, false, true, 0, 1); + text_edit->set_caret_column(0, false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1612,12 +1785,12 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - ((Array)lines_edited_args[0])[0] = 0; - SEND_GUI_ACTION(text_edit, "ui_text_backspace"); - CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_text() == "\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1627,10 +1800,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { Ref<InputEvent> tmpevent = InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL); InputMap::get_singleton()->action_add_event("ui_text_delete_all_to_right", tmpevent); - text_edit->set_text("this is some test text.\n"); + text_edit->set_text("this is some test text.\nthis is some test text.\n"); text_edit->select(0, 0, 0, 4); text_edit->set_caret_line(0); text_edit->set_caret_column(4); + + text_edit->add_caret(1, 4); + text_edit->select(1, 0, 1, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1638,19 +1816,30 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(1); + args2.push_back(1); + lines_edited_args.push_front(args2); + // With selection should be a normal delete. SEND_GUI_ACTION(text_edit, "ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n"); + CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); // End of line should not do anything. text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1660,15 +1849,20 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n"); + CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1679,10 +1873,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n"); + CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -1690,10 +1888,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n"); + CHECK(text_edit->get_text() == "\n\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1705,10 +1907,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_caret_mid_grapheme_enabled(true); CHECK(text_edit->is_caret_mid_grapheme_enabled()); - text_edit->set_text("this ffi some test text.\n"); + text_edit->set_text("this ffi some test text.\n\nthis ffi some test text.\n"); text_edit->select(0, 0, 0, 4); text_edit->set_caret_line(0); text_edit->set_caret_column(4); + + text_edit->add_caret(2, 4); + text_edit->select(2, 0, 2, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1716,20 +1923,32 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(2); + args2.push_back(2); + lines_edited_args.push_front(args2); + // With selection should be a normal delete. SEND_GUI_ACTION(text_edit, "ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n"); + CHECK(text_edit->get_text() == " ffi some test text.\n\n ffi some test text.\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); // With selection should be a normal delete. - ((Array)lines_edited_args[0])[0] = 1; + ((Array)lines_edited_args[0])[0] = 3; + ((Array)lines_edited_args[1])[0] = 1; text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(2).length(), false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1739,16 +1958,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text."); + CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); CHECK_FALSE(text_edit->has_selection()); - SIGNAL_CHECK_FALSE("caret_changed"); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK_FALSE(text_edit->has_selection(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[0] = 0; + ((Array)lines_edited_args[1])[0] = 0; + ((Array)lines_edited_args[0])[0] = 1; + ((Array)lines_edited_args[0])[1] = 1; text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1759,10 +1985,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text."); + CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -1770,10 +2000,14 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " some test text."); + CHECK(text_edit->get_text() == " some test text.\n some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1783,10 +2017,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_caret_mid_grapheme_enabled(true); CHECK(text_edit->is_caret_mid_grapheme_enabled()); - text_edit->set_text("this ffi some test text.\n"); + text_edit->set_text("this ffi some test text.\nthis ffi some test text."); text_edit->select(0, 0, 0, 4); text_edit->set_caret_line(0); text_edit->set_caret_column(4); + + text_edit->add_caret(1, 4); + text_edit->select(1, 0, 1, 4, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1794,19 +2033,31 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(1); + args2.push_back(1); + lines_edited_args.push_front(args2); + // With selection should be a normal delete. SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n"); + CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); // With selection should be a normal delete. + lines_edited_args.remove_at(0); ((Array)lines_edited_args[0])[0] = 1; + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); text_edit->set_caret_column(text_edit->get_line(0).length()); MessageQueue::get_singleton()->flush(); @@ -1817,16 +2068,25 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text."); + CHECK(text_edit->get_text() == " ffi some test text. ffi some test text."); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK(text_edit->get_caret_column() == 20); + CHECK_FALSE(text_edit->has_selection(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[0] = 0; + // Caret should be removed due to column preservation. + CHECK(text_edit->get_caret_count() == 1); + + // Lets add it back. text_edit->set_caret_column(0); + text_edit->add_caret(0, 20); + + ((Array)lines_edited_args[0])[0] = 0; + lines_edited_args.push_back(args2); + ((Array)lines_edited_args[1])[0] = 0; + ((Array)lines_edited_args[1])[1] = 0; MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1837,43 +2097,59 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text."); + CHECK(text_edit->get_text() == " ffi some test text. ffi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 20); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + text_edit->start_action(TextEdit::EditAction::ACTION_NONE); + SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "ffi some test text."); + CHECK(text_edit->get_text() == "ffi some test text.ffi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 19); + CHECK_FALSE(text_edit->has_selection(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->start_action(TextEdit::EditAction::ACTION_NONE); + SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "fi some test text."); + CHECK(text_edit->get_text() == "fi some test text.fi some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 18); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->set_caret_mid_grapheme_enabled(false); CHECK_FALSE(text_edit->is_caret_mid_grapheme_enabled()); + text_edit->start_action(TextEdit::EditAction::ACTION_NONE); + text_edit->undo(); - text_edit->set_caret_line(0); - text_edit->set_caret_column(0); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_text() == "ffi some test text."); + CHECK(text_edit->get_text() == "ffi some test text.ffi some test text."); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -1882,19 +2158,26 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION(text_edit, "ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " some test text."); + CHECK(text_edit->get_text() == " some test text. some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 16); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); } SUBCASE("[TextEdit] ui_text_caret_word_left") { - text_edit->set_text("\nthis is some test text."); + text_edit->set_text("\nthis is some test text.\nthis is some test text."); text_edit->set_caret_line(1); text_edit->set_caret_column(7); + + text_edit->add_caret(2, 7); + CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1902,47 +2185,67 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Shift should select. #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); #endif CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); - CHECK(text_edit->get_selected_text() == "is"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "is"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 5); + CHECK(text_edit->get_selected_text(1) == "is"); + CHECK(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Should still move caret with selection. SEND_GUI_ACTION(text_edit, "ui_text_caret_word_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal word left. SEND_GUI_ACTION(text_edit, "ui_text_caret_word_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 23); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_caret_left") { - text_edit->set_text("\nthis is some test text."); + text_edit->set_text("\nthis is some test text.\nthis is some test text."); text_edit->set_caret_line(1); text_edit->set_caret_column(7); text_edit->select(1, 2, 1, 7); + + text_edit->add_caret(2, 7); + text_edit->select(2, 2, 2, 7, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -1950,62 +2253,91 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Normal left shoud deselect and place at selection start. SEND_GUI_ACTION(text_edit, "ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); + CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 2); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // With shift should select. SEND_GUI_KEY_EVENT(text_edit, Key::LEFT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 1); - CHECK(text_edit->get_selected_text() == "h"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "h"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 1); + CHECK(text_edit->get_selected_text(1) == "h"); + CHECK(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // All ready at select left, should only deselect. SEND_GUI_ACTION(text_edit, "ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 1); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 1); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal left. SEND_GUI_ACTION(text_edit, "ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); CHECK_FALSE(text_edit->has_selection()); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Left at col 0 should go up a line. SEND_GUI_ACTION(text_edit, "ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\nthis is some test text."); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 23); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_caret_word_right") { - text_edit->set_text("this is some test text\n"); + text_edit->set_text("this is some test text\n\nthis is some test text\n"); text_edit->set_caret_line(0); text_edit->set_caret_column(13); + + text_edit->add_caret(2, 13); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -2013,47 +2345,67 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Shift should select. #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); #endif CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 17); - CHECK(text_edit->get_selected_text() == "test"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 17); + CHECK(text_edit->get_selected_text(1) == "test"); + CHECK(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Should still move caret with selection. SEND_GUI_ACTION(text_edit, "ui_text_caret_word_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 22); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 22); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal word right. SEND_GUI_ACTION(text_edit, "ui_text_caret_word_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_caret_right") { - text_edit->set_text("this is some test text\n"); + text_edit->set_text("this is some test text\n\nthis is some test text\n"); text_edit->set_caret_line(0); text_edit->set_caret_column(16); text_edit->select(0, 16, 0, 20); + + text_edit->add_caret(2, 16); + text_edit->select(2, 16, 2, 20, 1); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); @@ -2061,53 +2413,76 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Normal right shoud deselect and place at selection start. SEND_GUI_ACTION(text_edit, "ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 20); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 20); + CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // With shift should select. SEND_GUI_KEY_EVENT(text_edit, Key::RIGHT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 21); - CHECK(text_edit->get_selected_text() == "x"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "x"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 21); + CHECK(text_edit->get_selected_text(1) == "x"); + CHECK(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // All ready at select right, should only deselect. SEND_GUI_ACTION(text_edit, "ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 21); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 21); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal right. SEND_GUI_ACTION(text_edit, "ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 22); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 22); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Right at end col should go down a line. SEND_GUI_ACTION(text_edit, "ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some test text\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2117,9 +2492,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); text_edit->set_size(Size2(110, 100)); - text_edit->set_text("this is some\nother test\nlines\ngo here"); - text_edit->set_caret_line(4); + text_edit->set_text("this is some\nother test\nlines\ngo here\nthis is some\nother test\nlines\ngo here"); + text_edit->set_caret_line(3); text_edit->set_caret_column(7); + + text_edit->add_caret(7, 7); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(0)); @@ -2128,44 +2507,61 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Select + up should select everything to the left on that line. SEND_GUI_KEY_EVENT(text_edit, Key::UP | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some\nother test\nlines\ngo here"); CHECK(text_edit->get_caret_line() == 2); CHECK(text_edit->get_caret_column() == 5); - CHECK(text_edit->get_selected_text() == "\ngo here"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "\ngo here"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 6); + CHECK(text_edit->get_caret_column(1) == 5); + CHECK(text_edit->get_selected_text(1) == "\ngo here"); + CHECK(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Should deselect and move up. SEND_GUI_ACTION(text_edit, "ui_text_caret_up"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some\nother test\nlines\ngo here"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 8); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 5); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal up over wrapped line. SEND_GUI_ACTION(text_edit, "ui_text_caret_up"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some\nother test\nlines\ngo here"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 12); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 4); + CHECK(text_edit->get_caret_column(1) == 12); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_caret_column(12, false); + // Normal up over wrapped line to line 0. SEND_GUI_ACTION(text_edit, "ui_text_caret_up"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "this is some\nother test\nlines\ngo here"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 7); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 4); + CHECK(text_edit->get_caret_column(1) == 7); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2175,9 +2571,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); text_edit->set_size(Size2(110, 100)); - text_edit->set_text("go here\nlines\nother test\nthis is some"); + text_edit->set_text("go here\nlines\nother test\nthis is some\ngo here\nlines\nother test\nthis is some"); text_edit->set_caret_line(0); text_edit->set_caret_column(7); + + text_edit->add_caret(4, 7); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(3)); @@ -2186,44 +2586,61 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Select + down should select everything to the right on that line. SEND_GUI_KEY_EVENT(text_edit, Key::DOWN | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "go here\nlines\nother test\nthis is some"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); - CHECK(text_edit->get_selected_text() == "\nlines"); - CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text(0) == "\nlines"); + CHECK(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 5); + CHECK(text_edit->get_caret_column(1) == 5); + CHECK(text_edit->get_selected_text(1) == "\nlines"); + CHECK(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Should deselect and move down. SEND_GUI_ACTION(text_edit, "ui_text_caret_down"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "go here\nlines\nother test\nthis is some"); CHECK(text_edit->get_caret_line() == 2); CHECK(text_edit->get_caret_column() == 8); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 6); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Normal down over wrapped line. SEND_GUI_ACTION(text_edit, "ui_text_caret_down"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "go here\nlines\nother test\nthis is some"); CHECK(text_edit->get_caret_line() == 3); CHECK(text_edit->get_caret_column() == 7); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 7); + CHECK(text_edit->get_caret_column(1) == 7); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_caret_column(7, false); + // Normal down over wrapped line to last wrapped line. SEND_GUI_ACTION(text_edit, "ui_text_caret_down"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "go here\nlines\nother test\nthis is some"); CHECK(text_edit->get_caret_line() == 3); CHECK(text_edit->get_caret_column() == 12); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 7); + CHECK(text_edit->get_caret_column(1) == 12); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2236,6 +2653,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_text("this is some\nother test\nlines\ngo here"); text_edit->set_caret_line(4); text_edit->set_caret_column(7); + + text_edit->add_caret(3, 2); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(0)); @@ -2258,6 +2679,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + CHECK(text_edit->get_caret_count() == 1); SEND_GUI_ACTION(text_edit, "ui_text_caret_document_start"); CHECK(text_edit->get_viewport()->is_input_handled()); @@ -2277,6 +2699,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_text("go here\nlines\nother test\nthis is some"); text_edit->set_caret_line(0); text_edit->set_caret_column(0); + + text_edit->add_caret(1, 0); + CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(3)); @@ -2299,6 +2724,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + CHECK(text_edit->get_caret_count() == 1); SEND_GUI_ACTION(text_edit, "ui_text_caret_document_end"); CHECK(text_edit->get_viewport()->is_input_handled()); @@ -2315,9 +2741,12 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); text_edit->set_size(Size2(110, 100)); - text_edit->set_text(" this is some"); + text_edit->set_text(" this is some\n this is some"); text_edit->set_caret_line(0); text_edit->set_caret_column(text_edit->get_line(0).length()); + + text_edit->add_caret(1, text_edit->get_line(1).length()); + CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(0)); @@ -2334,8 +2763,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 10); - CHECK(text_edit->has_selection()); - CHECK(text_edit->get_selected_text() == "some"); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "some"); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 10); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "some"); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2344,7 +2778,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 2); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2353,7 +2791,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2362,7 +2804,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 2); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2372,9 +2818,12 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); text_edit->set_size(Size2(110, 100)); - text_edit->set_text(" this is some"); + text_edit->set_text(" this is some\n this is some"); text_edit->set_caret_line(0); text_edit->set_caret_column(0); + + text_edit->add_caret(1, 0); + CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); CHECK(text_edit->is_line_wrapped(0)); @@ -2391,8 +2840,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 9); - CHECK(text_edit->has_selection()); - CHECK(text_edit->get_selected_text() == " this is"); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == " this is"); + + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 9); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == " this is"); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2401,13 +2855,24 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); + CHECK_FALSE(text_edit->has_selection(0)); + + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] unicode") { + text_edit->set_text("\n"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + + text_edit->add_caret(1, 0); + CHECK(text_edit->get_caret_count() == 2); text_edit->insert_text_at_caret("a"); MessageQueue::get_singleton()->flush(); @@ -2416,10 +2881,17 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // For the second caret. + Array args2; + args2.push_back(1); + args2.push_back(1); + lines_edited_args.push_front(args2); + SEND_GUI_KEY_EVENT(text_edit, Key::A); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "aA"); + CHECK(text_edit->get_text() == "aA\naA"); CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_caret_column(1) == 2); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -2427,20 +2899,24 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(false); SEND_GUI_KEY_EVENT(text_edit, Key::A); CHECK_FALSE(text_edit->get_viewport()->is_input_handled()); // Should this be handled? - CHECK(text_edit->get_text() == "aA"); + CHECK(text_edit->get_text() == "aA\naA"); CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_caret_column(1) == 2); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - lines_edited_args.push_back(lines_edited_args[0].duplicate()); + lines_edited_args.push_back(lines_edited_args[1].duplicate()); + lines_edited_args.push_front(args2.duplicate()); text_edit->select(0, 0, 0, 1); + text_edit->select(1, 0, 1, 1, 1); SEND_GUI_KEY_EVENT(text_edit, Key::B); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "BA"); + CHECK(text_edit->get_text() == "BA\nBA"); CHECK(text_edit->get_caret_column() == 1); + CHECK(text_edit->get_caret_column(1) == 1); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -2450,17 +2926,19 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_KEY_EVENT(text_edit, Key::B); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "BB"); + CHECK(text_edit->get_text() == "BB\nBB"); CHECK(text_edit->get_caret_column() == 2); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->select(0, 0, 0, 1); + text_edit->select(1, 0, 1, 1, 1); SEND_GUI_KEY_EVENT(text_edit, Key::A); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "AB"); + CHECK(text_edit->get_text() == "AB\nAB"); CHECK(text_edit->get_caret_column() == 1); + CHECK(text_edit->get_caret_column(1) == 1); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -2558,7 +3036,7 @@ TEST_CASE("[SceneTree][TextEdit] versioning") { CHECK(text_edit->has_redo()); text_edit->redo(); - CHECK(text_edit->get_line(0) == "test nested ops"); + CHECK(text_edit->get_line(0) == "test ops nested"); CHECK(text_edit->get_version() == 3); CHECK(text_edit->get_saved_version() == 3); CHECK(text_edit->has_undo()); @@ -2779,6 +3257,94 @@ TEST_CASE("[SceneTree][TextEdit] caret") { memdelete(text_edit); } +TEST_CASE("[SceneTree][TextEdit] muiticaret") { + TextEdit *text_edit = memnew(TextEdit); + SceneTree::get_singleton()->get_root()->add_child(text_edit); + text_edit->set_multiple_carets_enabled(true); + + Array empty_signal_args; + empty_signal_args.push_back(Array()); + + SIGNAL_WATCH(text_edit, "caret_changed"); + + text_edit->set_text("this is\nsome test\ntext"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SUBCASE("[TextEdit] add remove caret") { + // Overlapping + CHECK(text_edit->add_caret(0, 0) == -1); + MessageQueue::get_singleton()->flush(); + SIGNAL_CHECK_FALSE("caret_changed"); + + // Selection + text_edit->select(0, 0, 2, 4); + CHECK(text_edit->add_caret(0, 0) == -1); + CHECK(text_edit->add_caret(2, 4) == -1); + CHECK(text_edit->add_caret(1, 2) == -1); + + // Out of bounds + CHECK(text_edit->add_caret(-1, 0) == -1); + CHECK(text_edit->add_caret(5, 0) == -1); + CHECK(text_edit->add_caret(0, 100) == -1); + + MessageQueue::get_singleton()->flush(); + SIGNAL_CHECK_FALSE("caret_changed"); + + CHECK(text_edit->get_caret_count() == 1); + + text_edit->deselect(); + SIGNAL_CHECK_FALSE("caret_changed"); + + CHECK(text_edit->add_caret(0, 1) == 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_CHECK("caret_changed", empty_signal_args); + CHECK(text_edit->get_caret_count() == 2); + + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 1); + + ERR_PRINT_OFF; + text_edit->remove_caret(-1); + text_edit->remove_caret(5); + ERR_PRINT_ON; + CHECK(text_edit->get_caret_count() == 2); + SIGNAL_CHECK_FALSE("caret_changed"); + + text_edit->remove_caret(0); + SIGNAL_CHECK_FALSE("caret_changed"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 1); + } + + SUBCASE("[TextEdit] caret index edit order") { + Vector<int> caret_index_get_order; + caret_index_get_order.push_back(1); + caret_index_get_order.push_back(0); + + CHECK(text_edit->add_caret(1, 0)); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order); + + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(1); + CHECK(text_edit->add_caret(0, 0)); + CHECK(text_edit->get_caret_count() == 2); + + caret_index_get_order.write[0] = 0; + caret_index_get_order.write[1] = 1; + CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order); + } + + memdelete(text_edit); +} + TEST_CASE("[SceneTree][TextEdit] line wrapping") { TextEdit *text_edit = memnew(TextEdit); SceneTree::get_singleton()->get_root()->add_child(text_edit); |