diff options
56 files changed, 1172 insertions, 559 deletions
diff --git a/SConstruct b/SConstruct index 0297cd6e61..5c1ccd2449 100644 --- a/SConstruct +++ b/SConstruct @@ -791,6 +791,8 @@ else: # Note that this is still not complete conformance, as certain Windows-related headers # don't compile under complete conformance. env.Prepend(CCFLAGS=["/permissive-"]) + # Allow use of `__cplusplus` macro to determine C++ standard universally. + env.Prepend(CXXFLAGS=["/Zc:__cplusplus"]) # Disable exception handling. Godot doesn't use exceptions anywhere, and this # saves around 20% of binary size and very significant build time (GH-80513). diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index b1d2f82f9d..c40ee5b4d7 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -127,13 +127,18 @@ void AStarGrid2D::update() { } points.clear(); + solid_mask.clear(); const int32_t end_x = region.get_end().x; const int32_t end_y = region.get_end().y; const Vector2 half_cell_size = cell_size / 2; + for (int32_t x = region.position.x; x < end_x + 2; x++) { + solid_mask.push_back(true); + } for (int32_t y = region.position.y; y < end_y; y++) { LocalVector<Point> line; + solid_mask.push_back(true); for (int32_t x = region.position.x; x < end_x; x++) { Vector2 v = offset; switch (cell_shape) { @@ -150,10 +155,16 @@ void AStarGrid2D::update() { break; } line.push_back(Point(Vector2i(x, y), v)); + solid_mask.push_back(false); } + solid_mask.push_back(true); points.push_back(line); } + for (int32_t x = region.position.x; x < end_x + 2; x++) { + solid_mask.push_back(true); + } + dirty = false; } @@ -207,13 +218,13 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const { void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) { ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method."); ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region)); - _get_point_unchecked(p_id)->solid = p_solid; + _set_solid_unchecked(p_id, p_solid); } bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const { ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method."); ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region)); - return _get_point_unchecked(p_id)->solid; + return _get_solid_unchecked(p_id); } void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) { @@ -238,7 +249,7 @@ void AStarGrid2D::fill_solid_region(const Rect2i &p_region, bool p_solid) { for (int32_t y = safe_region.position.y; y < end_y; y++) { for (int32_t x = safe_region.position.x; x < end_x; x++) { - _get_point_unchecked(x, y)->solid = p_solid; + _set_solid_unchecked(x, y, p_solid); } } } @@ -259,13 +270,6 @@ void AStarGrid2D::fill_weight_scale_region(const Rect2i &p_region, real_t p_weig } AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { - if (!p_to || p_to->solid) { - return nullptr; - } - if (p_to == end) { - return p_to; - } - int32_t from_x = p_from->id.x; int32_t from_y = p_from->id.y; @@ -276,72 +280,109 @@ AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { int32_t dy = to_y - from_y; if (diagonal_mode == DIAGONAL_MODE_ALWAYS || diagonal_mode == DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE) { - if (dx != 0 && dy != 0) { - if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) { - return p_to; - } - if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { - return p_to; + if (dx == 0 || dy == 0) { + return _forced_successor(to_x, to_y, dx, dy); + } + + while (_is_walkable(to_x, to_y) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || _is_walkable(to_x, to_y - dy) || _is_walkable(to_x - dx, to_y))) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { - return p_to; + + if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) { + return _get_point_unchecked(to_x, to_y); } - } else { - if (dx != 0) { - if ((_is_walkable(to_x + dx, to_y + 1) && !_is_walkable(to_x, to_y + 1)) || (_is_walkable(to_x + dx, to_y - 1) && !_is_walkable(to_x, to_y - 1))) { - return p_to; - } - } else { - if ((_is_walkable(to_x + 1, to_y + dy) && !_is_walkable(to_x + 1, to_y)) || (_is_walkable(to_x - 1, to_y + dy) && !_is_walkable(to_x - 1, to_y))) { - return p_to; - } + + if (_forced_successor(to_x + dx, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y + dy, 0, dy) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_x += dx; + to_y += dy; } - if (_is_walkable(to_x + dx, to_y + dy) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || (_is_walkable(to_x + dx, to_y) || _is_walkable(to_x, to_y + dy)))) { - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); - } + } else if (diagonal_mode == DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES) { - if (dx != 0 && dy != 0) { - if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) { - return p_to; - } - if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { - return p_to; + if (dx == 0 || dy == 0) { + return _forced_successor(from_x, from_y, dx, dy, true); + } + + while (_is_walkable(to_x, to_y) && _is_walkable(to_x, to_y - dy) && _is_walkable(to_x - dx, to_y)) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { - return p_to; + + if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) { + return _get_point_unchecked(to_x, to_y); } - } else { - if (dx != 0) { - if ((_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1)) || (_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1))) { - return p_to; - } - } else { - if ((_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy)) || (_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy))) { - return p_to; - } + + if (_forced_successor(to_x, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y, 0, dy) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_x += dx; + to_y += dy; } - if (_is_walkable(to_x + dx, to_y + dy) && _is_walkable(to_x + dx, to_y) && _is_walkable(to_x, to_y + dy)) { - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); - } + } else { // DIAGONAL_MODE_NEVER - if (dx != 0) { - if ((_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1)) || (_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1))) { - return p_to; + if (dy == 0) { + return _forced_successor(from_x, from_y, dx, 0, true); + } + + while (_is_walkable(to_x, to_y)) { + if (end->id.x == to_x && end->id.y == to_y) { + return end; } - } else if (dy != 0) { + if ((_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy)) || (_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy))) { - return p_to; - } - if (_jump(p_to, _get_point(to_x + 1, to_y)) != nullptr) { - return p_to; + return _get_point_unchecked(to_x, to_y); } - if (_jump(p_to, _get_point(to_x - 1, to_y)) != nullptr) { - return p_to; + + if (_forced_successor(to_x, to_y, 1, 0, true) != nullptr || _forced_successor(to_x, to_y, -1, 0, true) != nullptr) { + return _get_point_unchecked(to_x, to_y); } + + to_y += dy; } - return _jump(p_to, _get_point(to_x + dx, to_y + dy)); + } + + return nullptr; +} + +AStarGrid2D::Point *AStarGrid2D::_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive) { + // Remembering previous results can improve performance. + bool l_prev = false, r_prev = false, l = false, r = false; + + int32_t o_x = p_x, o_y = p_y; + if (p_inclusive) { + o_x += p_dx; + o_y += p_dy; + } + + int32_t l_x = p_x - p_dy, l_y = p_y - p_dx; + int32_t r_x = p_x + p_dy, r_y = p_y + p_dx; + + while (_is_walkable(o_x, o_y)) { + if (end->id.x == o_x && end->id.y == o_y) { + return end; + } + + l_prev = l || _is_walkable(l_x, l_y); + r_prev = r || _is_walkable(r_x, r_y); + + l_x += p_dx; + l_y += p_dy; + r_x += p_dx; + r_y += p_dy; + + l = _is_walkable(l_x, l_y); + r = _is_walkable(r_x, r_y); + + if ((l && !l_prev) || (r && !r_prev)) { + return _get_point_unchecked(o_x, o_y); + } + + o_x += p_dx; + o_y += p_dy; } return nullptr; } @@ -394,19 +435,19 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { } } - if (top && !top->solid) { + if (top && !_get_solid_unchecked(top->id)) { r_nbors.push_back(top); ts0 = true; } - if (right && !right->solid) { + if (right && !_get_solid_unchecked(right->id)) { r_nbors.push_back(right); ts1 = true; } - if (bottom && !bottom->solid) { + if (bottom && !_get_solid_unchecked(bottom->id)) { r_nbors.push_back(bottom); ts2 = true; } - if (left && !left->solid) { + if (left && !_get_solid_unchecked(left->id)) { r_nbors.push_back(left); ts3 = true; } @@ -436,16 +477,16 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { break; } - if (td0 && (top_left && !top_left->solid)) { + if (td0 && (top_left && !_get_solid_unchecked(top_left->id))) { r_nbors.push_back(top_left); } - if (td1 && (top_right && !top_right->solid)) { + if (td1 && (top_right && !_get_solid_unchecked(top_right->id))) { r_nbors.push_back(top_right); } - if (td2 && (bottom_right && !bottom_right->solid)) { + if (td2 && (bottom_right && !_get_solid_unchecked(bottom_right->id))) { r_nbors.push_back(bottom_right); } - if (td3 && (bottom_left && !bottom_left->solid)) { + if (td3 && (bottom_left && !_get_solid_unchecked(bottom_left->id))) { r_nbors.push_back(bottom_left); } } @@ -454,7 +495,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { last_closest_point = nullptr; pass++; - if (p_end_point->solid) { + if (_get_solid_unchecked(p_end_point->id)) { return false; } @@ -500,7 +541,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { continue; } } else { - if (e->solid || e->closed_pass == pass) { + if (_get_solid_unchecked(e->id) || e->closed_pass == pass) { continue; } weight_scale = e->weight_scale; @@ -580,7 +621,7 @@ TypedArray<Dictionary> AStarGrid2D::get_point_data_in_region(const Rect2i &p_reg Dictionary dict; dict["id"] = p.id; dict["position"] = p.pos; - dict["solid"] = p.solid; + dict["solid"] = _get_solid_unchecked(p.id); dict["weight_scale"] = p.weight_scale; data.push_back(dict); } diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index c2be0bbf29..f5ac472f09 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -78,7 +78,6 @@ private: struct Point { Vector2i id; - bool solid = false; Vector2 pos; real_t weight_scale = 1.0; @@ -111,6 +110,7 @@ private: } }; + LocalVector<bool> solid_mask; LocalVector<LocalVector<Point>> points; Point *end = nullptr; Point *last_closest_point = nullptr; @@ -118,11 +118,12 @@ private: uint64_t pass = 1; private: // Internal routines. + _FORCE_INLINE_ size_t _to_mask_index(int32_t p_x, int32_t p_y) const { + return ((p_y - region.position.y + 1) * (region.size.x + 2)) + p_x - region.position.x + 1; + } + _FORCE_INLINE_ bool _is_walkable(int32_t p_x, int32_t p_y) const { - if (region.has_point(Vector2i(p_x, p_y))) { - return !points[p_y - region.position.y][p_x - region.position.x].solid; - } - return false; + return !solid_mask[_to_mask_index(p_x, p_y)]; } _FORCE_INLINE_ Point *_get_point(int32_t p_x, int32_t p_y) { @@ -132,6 +133,18 @@ private: // Internal routines. return nullptr; } + _FORCE_INLINE_ void _set_solid_unchecked(int32_t p_x, int32_t p_y, bool p_solid) { + solid_mask[_to_mask_index(p_x, p_y)] = p_solid; + } + + _FORCE_INLINE_ void _set_solid_unchecked(const Vector2i &p_id, bool p_solid) { + solid_mask[_to_mask_index(p_id.x, p_id.y)] = p_solid; + } + + _FORCE_INLINE_ bool _get_solid_unchecked(const Vector2i &p_id) const { + return solid_mask[_to_mask_index(p_id.x, p_id.y)]; + } + _FORCE_INLINE_ Point *_get_point_unchecked(int32_t p_x, int32_t p_y) { return &points[p_y - region.position.y][p_x - region.position.x]; } @@ -146,6 +159,7 @@ private: // Internal routines. void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors); Point *_jump(Point *p_from, Point *p_to); + Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false); bool _solve(Point *p_begin_point, Point *p_end_point); protected: diff --git a/core/object/object.cpp b/core/object/object.cpp index 4be1dc4b34..d6b7d7a7fe 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1724,33 +1724,65 @@ void Object::_bind_methods() { #define BIND_OBJ_CORE_METHOD(m_method) \ ::ClassDB::add_virtual_method(get_class_static(), m_method, true, Vector<String>(), true); - MethodInfo notification_mi("_notification", PropertyInfo(Variant::INT, "what")); - notification_mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32); - BIND_OBJ_CORE_METHOD(notification_mi); - BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value"))); + BIND_OBJ_CORE_METHOD(MethodInfo("_init")); + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); + + { + MethodInfo mi("_notification"); + mi.arguments.push_back(PropertyInfo(Variant::INT, "what")); + mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32); + BIND_OBJ_CORE_METHOD(mi); + } + + { + MethodInfo mi("_set"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.arguments.push_back(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); + mi.return_val.type = Variant::BOOL; + BIND_OBJ_CORE_METHOD(mi); + } + #ifdef TOOLS_ENABLED - MethodInfo miget("_get", PropertyInfo(Variant::STRING_NAME, "property")); - miget.return_val.name = "Variant"; - miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_OBJ_CORE_METHOD(miget); + { + MethodInfo mi("_get"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } - MethodInfo plget("_get_property_list"); - plget.return_val.type = Variant::ARRAY; - plget.return_val.hint = PROPERTY_HINT_ARRAY_TYPE; - plget.return_val.hint_string = "Dictionary"; - BIND_OBJ_CORE_METHOD(plget); + { + MethodInfo mi("_get_property_list"); + mi.return_val.type = Variant::ARRAY; + mi.return_val.hint = PROPERTY_HINT_ARRAY_TYPE; + mi.return_val.hint_string = "Dictionary"; + BIND_OBJ_CORE_METHOD(mi); + } BIND_OBJ_CORE_METHOD(MethodInfo(Variant::NIL, "_validate_property", PropertyInfo(Variant::DICTIONARY, "property"))); BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property"))); - MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property")); - mipgr.return_val.name = "Variant"; - mipgr.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_OBJ_CORE_METHOD(mipgr); + { + MethodInfo mi("_property_get_revert"); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property")); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } + + // These are actually `Variant` methods, but that doesn't matter since scripts can't inherit built-in types. + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_init", PropertyInfo(Variant::ARRAY, "iter"))); + + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_next", PropertyInfo(Variant::ARRAY, "iter"))); + + { + MethodInfo mi("_iter_get"); + mi.arguments.push_back(PropertyInfo(Variant::NIL, "iter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)); + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mi); + } #endif - BIND_OBJ_CORE_METHOD(MethodInfo("_init")); - BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); BIND_CONSTANT(NOTIFICATION_PREDELETE); diff --git a/core/typedefs.h b/core/typedefs.h index 0de803293d..35c4668581 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -44,6 +44,9 @@ #include "core/error/error_list.h" #include <cstdint> +// Ensure that C++ standard is at least C++17. If on MSVC, also ensures that the `Zc:__cplusplus` flag is present. +static_assert(__cplusplus >= 201703L); + // Turn argument to string constant: // https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing #ifndef _STR diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 7534a154a1..384fe6c4a6 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -452,12 +452,14 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::QUATERNION: case Variant::BASIS: case Variant::COLOR: + case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: break; default: r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::NIL; - return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Quaternion", "Basis, or "Color".)"; + return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Color", "Quaternion", "Basis", "Transform2D", or "Transform3D".)"; } if (from.get_type() != to.get_type()) { @@ -490,6 +492,12 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::BASIS: { return VariantInternalAccessor<Basis>::get(&from).slerp(VariantInternalAccessor<Basis>::get(&to), weight); } break; + case Variant::TRANSFORM2D: { + return VariantInternalAccessor<Transform2D>::get(&from).interpolate_with(VariantInternalAccessor<Transform2D>::get(&to), weight); + } break; + case Variant::TRANSFORM3D: { + return VariantInternalAccessor<Transform3D>::get(&from).interpolate_with(VariantInternalAccessor<Transform3D>::get(&to), weight); + } break; case Variant::COLOR: { return VariantInternalAccessor<Color>::get(&from).lerp(VariantInternalAccessor<Color>::get(&to), weight); } break; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 49b8e34f8f..ba448534b1 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -621,13 +621,13 @@ <param index="1" name="to" type="Variant" /> <param index="2" name="weight" type="Variant" /> <description> - Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clamp] on the result of this function. - Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis]. + Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clampf] to limit [param weight]. + Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis], [Transform2D], [Transform3D]. [codeblock] lerp(0, 4, 0.75) # Returns 3.0 [/codeblock] See also [method inverse_lerp] which performs the reverse of this operation. To perform eased interpolation with [method lerp], combine it with [method ease] or [method smoothstep]. See also [method remap] to map a continuous series of values to another. - [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp] or [method Basis.slerp]. + [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp], [method Basis.slerp], [method Transform2D.interpolate_with], or [method Transform3D.interpolate_with]. </description> </method> <method name="lerp_angle" keywords="interpolate"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 7f017f39de..1de63b4a39 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -207,8 +207,11 @@ If [code]true[/code], displays folders in the FileSystem dock's bottom pane when split mode is enabled. If [code]false[/code], only files will be displayed in the bottom pane. Split mode can be toggled by pressing the icon next to the [code]res://[/code] folder path. [b]Note:[/b] This setting has no effect when split mode is disabled (which is the default). </member> + <member name="docks/filesystem/other_file_extensions" type="String" setter="" getter=""> + A comma separated list of unsupported file extensions to show in the FileSystem dock, e.g. [code]"ico,icns"[/code]. + </member> <member name="docks/filesystem/textfile_extensions" type="String" setter="" getter=""> - List of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files). + A comma separated list of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files), e.g. [code]"txt,md,cfg,ini,log,json,yml,yaml,toml,xml"[/code]. </member> <member name="docks/filesystem/thumbnail_size" type="int" setter="" getter=""> The thumbnail size to use in the FileSystem dock (in pixels). See also [member filesystem/file_dialog/thumbnail_size]. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 0cfa3a5d4a..a331c05e47 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -192,6 +192,55 @@ [b]Note:[/b] If [method _init] is defined with [i]required[/i] parameters, the Object with script may only be created directly. If any other means (such as [method PackedScene.instantiate] or [method Node.duplicate]) are used, the script's initialization will fail. </description> </method> + <method name="_iter_get" qualifiers="virtual"> + <return type="Variant" /> + <param index="0" name="iter" type="Variant" /> + <description> + Returns the current iterable value. [param iter] stores the iteration state, but unlike [method _iter_init] and [method _iter_next] the state is supposed to be read-only, so there is no [Array] wrapper. + </description> + </method> + <method name="_iter_init" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="iter" type="Array" /> + <description> + Initializes the iterator. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end. + Example: + [codeblock] + class MyRange: + var _from + var _to + + func _init(from, to): + assert(from <= to) + _from = from + _to = to + + func _iter_init(iter): + iter[0] = _from + return iter[0] < _to + + func _iter_next(iter): + iter[0] += 1 + return iter[0] < _to + + func _iter_get(iter): + return iter + + func _ready(): + var my_range = MyRange.new(2, 5) + for x in my_range: + print(x) # Prints 2, 3, 4. + [/codeblock] + [b]Note:[/b] Alternatively, you can ignore [param iter] and use the object's state instead, see [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_advanced.html#custom-iterators]online docs[/url] for an example. Note that in this case you will not be able to reuse the same iterator instance in nested loops. Also, make sure you reset the iterator state in this method if you want to reuse the same instance multiple times. + </description> + </method> + <method name="_iter_next" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="iter" type="Array" /> + <description> + Moves the iterator to the next iteration. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end. + </description> + </method> <method name="_notification" qualifiers="virtual"> <return type="void" /> <param index="0" name="what" type="int" /> diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml index 3fb30a81b8..6da6a301fe 100644 --- a/doc/classes/SpinBox.xml +++ b/doc/classes/SpinBox.xml @@ -98,12 +98,12 @@ Vertical separation between the up and down buttons. </theme_item> <theme_item name="buttons_width" data_type="constant" type="int" default="16"> - Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements, unless [theme_item set_min_buttons_width_from_icons] is different than [code]0[/code]. + Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements. If smaller than [code]0[/code], the width is automatically adjusted from the icon size. </theme_item> <theme_item name="field_and_buttons_separation" data_type="constant" type="int" default="2"> Width of the horizontal separation between the text input field ([LineEdit]) and the buttons. </theme_item> - <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1"> + <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1" deprecated="This property exists only for compatibility with older themes. Setting [theme_item buttons_width] to a negative value has the same effect."> If not [code]0[/code], the minimum button width corresponds to the widest of all icons set on those buttons, even if [theme_item buttons_width] is smaller. </theme_item> <theme_item name="down" data_type="icon" type="Texture2D"> diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm index ac737b3f0a..36edbab99a 100644 --- a/drivers/metal/pixel_formats.mm +++ b/drivers/metal/pixel_formats.mm @@ -1200,7 +1200,7 @@ void PixelFormats::modifyMTLFormatCapabilities(id<MTLDevice> p_device) { // Disable for iOS simulator last. #if TARGET_OS_SIMULATOR - if (![mtlDevice supportsFamily:MTLGPUFamilyApple5]) { + if (![p_device supportsFamily:MTLGPUFamilyApple5]) { disableAllMTLPixFmtCaps(R8Unorm_sRGB); disableAllMTLPixFmtCaps(RG8Unorm_sRGB); disableAllMTLPixFmtCaps(B5G6R5Unorm); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index e13a984213..b1b64b5d60 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1075,7 +1075,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->modified_time = 0; fi->import_modified_time = 0; - fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path); + fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path); ItemAction ia; ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; @@ -1118,6 +1118,9 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, if (fi->type == "" && textfile_extensions.has(ext)) { fi->type = "TextFile"; } + if (fi->type == "" && other_file_extensions.has(ext)) { + fi->type = "OtherFile"; + } fi->uid = ResourceLoader::get_resource_uid(path); fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->deps = _get_dependencies(path); @@ -1263,8 +1266,11 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr if (fi->type == "" && textfile_extensions.has(ext)) { fi->type = "TextFile"; } + if (fi->type == "" && other_file_extensions.has(ext)) { + fi->type = "OtherFile"; + } fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); - fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path); + fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path); fi->import_group_file = ResourceLoader::get_import_group_file(path); { @@ -2118,6 +2124,9 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { if (type.is_empty() && textfile_extensions.has(file.get_extension())) { type = "TextFile"; } + if (type.is_empty() && other_file_extensions.has(file.get_extension())) { + type = "OtherFile"; + } String script_class = ResourceLoader::get_resource_script_class(file); ResourceUID::ID uid = ResourceLoader::get_resource_uid(file); @@ -2137,7 +2146,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); fi->file = file_name; fi->import_modified_time = 0; - fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); + fi->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file); if (idx == fs->files.size()) { fs->files.push_back(fi); @@ -2161,7 +2170,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file); fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); fs->files[cpos]->deps = _get_dependencies(file); - fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); + fs->files[cpos]->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file); if (uid != ResourceUID::INVALID_ID) { if (ResourceUID::get_singleton()->has_id(uid)) { @@ -2419,6 +2428,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) { fs->files[cpos]->type = "TextFile"; } + if (fs->files[cpos]->type == "" && other_file_extensions.has(file.get_extension())) { + fs->files[cpos]->type = "OtherFile"; + } fs->files[cpos]->import_valid = err == OK; if (ResourceUID::get_singleton()->has_id(uid)) { @@ -3119,6 +3131,7 @@ void EditorFileSystem::_update_extensions() { valid_extensions.clear(); import_extensions.clear(); textfile_extensions.clear(); + other_file_extensions.clear(); List<String> extensionsl; ResourceLoader::get_recognized_extensions_for_type("", &extensionsl); @@ -3134,6 +3147,14 @@ void EditorFileSystem::_update_extensions() { valid_extensions.insert(E); textfile_extensions.insert(E); } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + if (valid_extensions.has(E)) { + continue; + } + valid_extensions.insert(E); + other_file_extensions.insert(E); + } extensionsl.clear(); ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 7848ede8a7..9adab1ed24 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -235,6 +235,7 @@ class EditorFileSystem : public Node { int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir); HashSet<String> textfile_extensions; + HashSet<String> other_file_extensions; HashSet<String> valid_extensions; HashSet<String> import_extensions; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f154cbd1e2..6899a35ded 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -825,6 +825,7 @@ void EditorNode::_notification(int p_what) { if (EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) { HashSet<String> updated_textfile_extensions; + HashSet<String> updated_other_file_extensions; bool extensions_match = true; const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false); for (const String &E : textfile_ext) { @@ -833,9 +834,17 @@ void EditorNode::_notification(int p_what) { extensions_match = false; } } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + updated_other_file_extensions.insert(E); + if (extensions_match && !other_file_extensions.has(E)) { + extensions_match = false; + } + } - if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) { + if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size() || updated_other_file_extensions.size() < other_file_extensions.size()) { textfile_extensions = updated_textfile_extensions; + other_file_extensions = updated_other_file_extensions; EditorFileSystem::get_singleton()->scan(); } } @@ -1326,6 +1335,9 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); } else if (textfile_extensions.has(p_resource.get_extension())) { res = ScriptEditor::get_singleton()->open_file(p_resource); + } else if (other_file_extensions.has(p_resource.get_extension())) { + OS::get_singleton()->shell_open(ProjectSettings::get_singleton()->globalize_path(p_resource)); + return OK; } ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN); @@ -5240,8 +5252,8 @@ void EditorNode::_copy_warning(const String &p_str) { } void EditorNode::_save_editor_layout() { - if (waiting_for_first_scan) { - return; // Scanning, do not touch docks. + if (!load_editor_layout_done) { + return; } Ref<ConfigFile> config; config.instantiate(); @@ -5297,22 +5309,22 @@ void EditorNode::_load_editor_layout() { if (overridden_default_layout >= 0) { _layout_menu_option(overridden_default_layout); } - return; - } - - ep.step(TTR("Loading docks..."), 1, true); - editor_dock_manager->load_docks_from_config(config, "docks"); + } else { + ep.step(TTR("Loading docks..."), 1, true); + editor_dock_manager->load_docks_from_config(config, "docks"); - ep.step(TTR("Reopening scenes..."), 2, true); - _load_open_scenes_from_config(config); + ep.step(TTR("Reopening scenes..."), 2, true); + _load_open_scenes_from_config(config); - ep.step(TTR("Loading central editor layout..."), 3, true); - _load_central_editor_layout_from_config(config); + ep.step(TTR("Loading central editor layout..."), 3, true); + _load_central_editor_layout_from_config(config); - ep.step(TTR("Loading plugin window layout..."), 4, true); - editor_data.set_plugin_window_layout(config); + ep.step(TTR("Loading plugin window layout..."), 4, true); + editor_data.set_plugin_window_layout(config); - ep.step(TTR("Editor layout ready."), 5, true); + ep.step(TTR("Editor layout ready."), 5, true); + } + load_editor_layout_done = true; } void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) { @@ -6980,6 +6992,10 @@ EditorNode::EditorNode() { for (const String &E : textfile_ext) { textfile_extensions.insert(E); } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + other_file_extensions.insert(E); + } resource_preview = memnew(EditorResourcePreview); add_child(resource_preview); diff --git a/editor/editor_node.h b/editor/editor_node.h index e24bae73f0..e9d2c28528 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -462,6 +462,7 @@ private: bool requested_first_scan = false; bool waiting_for_first_scan = true; + bool load_editor_layout_done = false; int current_menu_option = 0; @@ -488,6 +489,7 @@ private: String import_reload_fn; HashSet<String> textfile_extensions; + HashSet<String> other_file_extensions; HashSet<FileDialog *> file_dialogs; HashSet<EditorFileDialog *> editor_file_dialogs; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 36fbd91313..b1a91fa3dd 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -593,6 +593,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16") _initial_set("docks/filesystem/always_show_folders", true); _initial_set("docks/filesystem/textfile_extensions", "txt,md,cfg,ini,log,json,yml,yaml,toml,xml"); + _initial_set("docks/filesystem/other_file_extensions", "ico,icns"); // Property editor EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "docks/property_editor/auto_refresh_interval", 0.2, "0.01,1,0.001"); // Update 5 times per second by default. diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 2e4f2da9a8..5360b6fb60 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -571,7 +571,7 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ unzClose(pkg); _update_template_status(); - EditorSettings::get_singleton()->set_meta("export_template_download_directory", p_file.get_base_dir()); + EditorSettings::get_singleton()->set("_export_template_download_directory", p_file.get_base_dir()); return true; } @@ -1102,7 +1102,7 @@ ExportTemplateManager::ExportTemplateManager() { install_file_dialog->set_title(TTR("Select Template File")); install_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); install_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); - install_file_dialog->set_current_dir(EditorSettings::get_singleton()->get_meta("export_template_download_directory", "")); + install_file_dialog->set_current_dir(EDITOR_DEF("_export_template_download_directory", "")); install_file_dialog->add_filter("*.tpz", TTR("Godot Export Templates")); install_file_dialog->connect("file_selected", callable_mp(this, &ExportTemplateManager::_install_file_selected).bind(false)); add_child(install_file_dialog); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 03e9fba12d..3ad8ab0b19 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -903,7 +903,7 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem if (p_export_filter == EditorExportPreset::EXPORT_SELECTED_SCENES && type != "PackedScene") { continue; } - if (type == "TextFile") { + if (type == "TextFile" || type == "OtherFile") { continue; } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index c58b24e078..f7e81b329c 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -271,7 +271,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory List<FileInfo> file_list; for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); - if (file_type != "TextFile" && _is_file_type_disabled_by_feature_profile(file_type)) { + if (file_type != "TextFile" && file_type != "OtherFile" && _is_file_type_disabled_by_feature_profile(file_type)) { // If type is disabled, file won't be displayed. continue; } diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 5372d33b4c..731f682605 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -665,6 +665,10 @@ bool EditorSpinSlider::is_grabbing() const { } void EditorSpinSlider::_focus_entered() { + if (is_read_only()) { + return; + } + _ensure_input_popup(); value_input->set_text(get_text_value()); value_input_popup->set_size(get_size()); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 2de22238f4..a7a14de527 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -516,10 +516,14 @@ void SceneTreeEditor::_update_node_tooltip(Node *p_node, TreeItem *p_item) { String tooltip = p_node->get_name(); if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path()); } else if (p_node != get_scene_node() && !p_node->get_scene_file_path().is_empty() && can_open_instance) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Instance:") + " " + p_node->get_scene_file_path()); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 6e41e98360..4b7bf1674f 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4120,7 +4120,8 @@ void CanvasItemEditor::_notification(int p_what) { } } break; - case NOTIFICATION_APPLICATION_FOCUS_OUT: { + case NOTIFICATION_APPLICATION_FOCUS_OUT: + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { if (drag_type != DRAG_NONE) { _reset_drag(); viewport->queue_redraw(); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index d4d918743d..8aff3c9aec 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -431,12 +431,13 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< colors.resize(p_handles.size()); Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { + int id = p_ids.is_empty() ? i : p_ids[i]; + Color col(1, 1, 1, 1); - if (is_handle_highlighted(i, p_secondary)) { + if (is_handle_highlighted(id, p_secondary)) { col = Color(0, 0, 1, 0.9); } - int id = p_ids.is_empty() ? i : p_ids[i]; if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 8679dc25a4..44673e7224 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1975,7 +1975,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } - if (_edit.mode != TRANSFORM_NONE) { + if (!_edit.instant && _edit.mode != TRANSFORM_NONE) { Node3D *selected = spatial_editor->get_single_selected_node(); Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; @@ -2399,15 +2399,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) { cancel_transform(); } - if (!is_freelook_active()) { - if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) { - begin_transform(TRANSFORM_TRANSLATE, true); + if (!is_freelook_active() && !k->is_echo()) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_TRANSLATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_TRANSLATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) { - begin_transform(TRANSFORM_ROTATE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_ROTATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_ROTATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) { - begin_transform(TRANSFORM_SCALE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_SCALE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_SCALE, true); + } } } @@ -3077,8 +3092,11 @@ void Node3DEditorViewport::_notification(int p_what) { update_preview_node = false; } break; + case NOTIFICATION_APPLICATION_FOCUS_OUT: case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { set_freelook_active(false); + cursor.region_select = false; + surface->queue_redraw(); } break; case NOTIFICATION_ENTER_TREE: { @@ -7847,6 +7865,9 @@ void Node3DEditor::_sun_environ_settings_pressed() { sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0)); sun_environ_popup->reset_size(); sun_environ_popup->popup(); + // Grabbing the focus is required for Shift modifier checking to be functional + // (when the Add sun/environment buttons are pressed). + sun_environ_popup->grab_focus(); } void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 9978c55d7b..ed64ccfa4c 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2247,8 +2247,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { } void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) { - Node *new_parent = p_new_parent; - ERR_FAIL_NULL(new_parent); + ERR_FAIL_NULL(p_new_parent); if (p_nodes.size() == 0) { return; // Nothing to reparent. @@ -2288,7 +2287,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V // Prevent selecting the hovered node and keep the reparented node(s) selected instead. hovered_but_reparenting = true; - Node *validate = new_parent; + Node *validate = p_new_parent; while (validate) { ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent."); validate = validate->get_parent(); @@ -2310,7 +2309,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V // No undo implemented for this yet. Node *node = p_nodes[ni]; - fill_path_renames(node, new_parent, &path_renames); + fill_path_renames(node, p_new_parent, &path_renames); former_names.push_back(node->get_name()); List<Node *> owned; @@ -2320,7 +2319,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V owners.push_back(E); } - bool same_parent = new_parent == node->get_parent(); + bool same_parent = p_new_parent == node->get_parent(); if (same_parent && node->get_index(false) < p_position_in_parent + ni) { inc--; // If the child will generate a gap when moved, adjust. } @@ -2331,17 +2330,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V need_edit = select_node_hovered_at_end_of_drag; } else { undo_redo->add_do_method(node->get_parent(), "remove_child", node); - undo_redo->add_do_method(new_parent, "add_child", node, true); + undo_redo->add_do_method(p_new_parent, "add_child", node, true); } int new_position_in_parent = p_position_in_parent == -1 ? -1 : p_position_in_parent + inc; if (new_position_in_parent >= 0 || same_parent) { - undo_redo->add_do_method(new_parent, "move_child", node, new_position_in_parent); + undo_redo->add_do_method(p_new_parent, "move_child", node, new_position_in_parent); } EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); String old_name = former_names[ni]; - String new_name = new_parent->validate_child_name(node); + String new_name = p_new_parent->validate_child_name(node); // Name was modified, fix the path renames. if (old_name.casecmp_to(new_name) != 0) { @@ -2366,8 +2365,12 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } } - undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, new_position_in_parent); - undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false)); + // FIXME: Live editing for "Reparent to New Node" option is broken. + // We must get the path to `p_new_parent` *after* it was added to the scene. + if (p_new_parent->is_inside_tree()) { + undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(p_new_parent), new_name, new_position_in_parent); + undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(p_new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false)); + } if (p_keep_global_xform) { if (Object::cast_to<Node2D>(node)) { @@ -2387,7 +2390,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V undo_redo->add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } - undo_redo->add_undo_method(new_parent, "remove_child", node); + undo_redo->add_undo_method(p_new_parent, "remove_child", node); undo_redo->add_undo_method(node, "set_name", former_names[ni]); inc++; @@ -2405,7 +2408,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } int child_pos = node->get_index(false); - bool reparented_to_container = Object::cast_to<Container>(new_parent) && Object::cast_to<Control>(node); + bool reparented_to_container = Object::cast_to<Container>(p_new_parent) && Object::cast_to<Control>(node); undo_redo->add_undo_method(node->get_parent(), "add_child", node, true); undo_redo->add_undo_method(node->get_parent(), "move_child", node, child_pos); @@ -2785,10 +2788,10 @@ void SceneTreeDock::_selection_changed() { _update_script_button(); } -void SceneTreeDock::_do_create(Node *p_parent) { +Node *SceneTreeDock::_do_create(Node *p_parent) { Variant c = create_dialog->instantiate_selected(); Node *child = Object::cast_to<Node>(c); - ERR_FAIL_NULL(child); + ERR_FAIL_NULL_V(child, nullptr); String new_name = p_parent->validate_child_name(child); if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { @@ -2802,8 +2805,6 @@ void SceneTreeDock::_do_create(Node *p_parent) { if (edited_scene) { undo_redo->add_do_method(p_parent, "add_child", child, true); undo_redo->add_do_method(child, "set_owner", edited_scene); - undo_redo->add_do_method(editor_selection, "clear"); - undo_redo->add_do_method(editor_selection, "add_node", child); undo_redo->add_do_reference(child); undo_redo->add_undo_method(p_parent, "remove_child", child); @@ -2818,28 +2819,34 @@ void SceneTreeDock::_do_create(Node *p_parent) { undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); } + undo_redo->add_do_method(this, "_post_do_create", child); undo_redo->commit_action(); - _push_item(c); + + return child; +} + +void SceneTreeDock::_post_do_create(Node *p_child) { editor_selection->clear(); - editor_selection->add_node(child); - if (Object::cast_to<Control>(c)) { - //make editor more comfortable, so some controls don't appear super shrunk - Control *ct = Object::cast_to<Control>(c); + editor_selection->add_node(p_child); + _push_item(p_child); - Size2 ms = ct->get_minimum_size(); + // Make editor more comfortable, so some controls don't appear super shrunk. + Control *control = Object::cast_to<Control>(p_child); + if (control) { + Size2 ms = control->get_minimum_size(); if (ms.width < 4) { ms.width = 40; } if (ms.height < 4) { ms.height = 40; } - if (ct->is_layout_rtl()) { - ct->set_position(ct->get_position() - Vector2(ms.x, 0)); + if (control->is_layout_rtl()) { + control->set_position(control->get_position() - Vector2(ms.x, 0)); } - ct->set_size(ms); + control->set_size(ms); } - emit_signal(SNAME("node_created"), c); + emit_signal(SNAME("node_created"), p_child); } void SceneTreeDock::_create() { @@ -2922,22 +2929,24 @@ void SceneTreeDock::_create() { } Node *parent = nullptr; + int original_position = -1; if (only_one_top_node) { parent = top_node->get_parent(); + original_position = top_node->get_index(); } else { parent = top_node->get_parent()->get_parent(); } - _do_create(parent); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action_for_history(TTR("Reparent to New Node"), editor_data->get_current_edited_scene_history_id()); + + Node *last_created = _do_create(parent); Vector<Node *> nodes; for (Node *E : selection) { nodes.push_back(E); } - // This works because editor_selection was cleared and populated with last created node in _do_create() - Node *last_created = editor_selection->get_selected_node_list().front()->get(); - if (center_parent) { // Find parent type and only average positions of relevant nodes. Node3D *parent_node_3d = Object::cast_to<Node3D>(last_created); @@ -2976,6 +2985,11 @@ void SceneTreeDock::_create() { } _do_reparent(last_created, -1, nodes, true); + + if (only_one_top_node) { + undo_redo->add_do_method(parent, "move_child", last_created, original_position); + } + undo_redo->commit_action(); } scene_tree->get_scene_tree()->grab_focus(); @@ -4390,6 +4404,7 @@ void SceneTreeDock::_edit_subresource(int p_idx, const PopupMenu *p_from_menu) { } void SceneTreeDock::_bind_methods() { + ClassDB::bind_method(D_METHOD("_post_do_create"), &SceneTreeDock::_post_do_create); ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners); ClassDB::bind_method(D_METHOD("_reparent_nodes_to_root"), &SceneTreeDock::_reparent_nodes_to_root); ClassDB::bind_method(D_METHOD("_reparent_nodes_to_paths_with_transform_and_name"), &SceneTreeDock::_reparent_nodes_to_paths_with_transform_and_name); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index f01e6598cf..1807ec5896 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -179,7 +179,8 @@ class SceneTreeDock : public VBoxContainer { bool first_enter = true; void _create(); - void _do_create(Node *p_parent); + Node *_do_create(Node *p_parent); + void _post_do_create(Node *p_child); Node *scene_root = nullptr; Node *edited_scene = nullptr; Node *pending_click_select = nullptr; diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index f8673ddd44..0cb4952b04 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -111,15 +111,7 @@ static Vector<String> _get_hierarchy(const String &p_class_name) { void ScriptCreateDialog::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type()); - if (language_icon.is_valid()) { - language_menu->set_item_icon(i, language_icon); - } - } - + case NOTIFICATION_ENTER_TREE: { String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); if (!last_language.is_empty()) { for (int i = 0; i < language_menu->get_item_count(); i++) { @@ -131,9 +123,16 @@ void ScriptCreateDialog::_notification(int p_what) { } else { language_menu->select(default_language); } - if (EditorSettings::get_singleton()->has_meta("script_setup_use_script_templates")) { - is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup_use_script_templates")); - use_templates->set_pressed(is_using_templates); + is_using_templates = EDITOR_DEF("_script_setup_use_script_templates", false); + use_templates->set_pressed(is_using_templates); + } break; + + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type()); + if (language_icon.is_valid()) { + language_menu->set_item_icon(i, language_icon); + } } path_button->set_icon(get_editor_theme_icon(SNAME("Folder"))); @@ -297,12 +296,9 @@ void ScriptCreateDialog::_template_changed(int p_template) { EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project); } else { // Save template info to editor dictionary (not a project template). - Dictionary dic_templates; - if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) { - dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary"); - } + Dictionary dic_templates = EDITOR_GET("_script_setup_templates_dictionary"); dic_templates[parent_name->get_text()] = sinfo.get_hash(); - EditorSettings::get_singleton()->set_meta("script_setup_templates_dictionary", dic_templates); + EditorSettings::get_singleton()->set("_script_setup_templates_dictionary", dic_templates); // Remove template from project dictionary as we last used an editor level template. Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); if (dic_templates_project.has(parent_name->get_text())) { @@ -415,7 +411,7 @@ void ScriptCreateDialog::_built_in_pressed() { void ScriptCreateDialog::_use_template_pressed() { is_using_templates = use_templates->is_pressed(); - EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates); + EditorSettings::get_singleton()->set("_script_setup_use_script_templates", is_using_templates); validation_panel->update(); } @@ -509,10 +505,7 @@ void ScriptCreateDialog::_update_template_menu() { if (is_language_using_templates) { // Get the latest templates used for each type of node from project settings then global settings. Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); - Dictionary last_global_templates; - if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) { - last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary"); - } + Dictionary last_global_templates = EDITOR_GET("_script_setup_templates_dictionary"); String inherits_base_type = parent_name->get_text(); // If it inherits from a script, get its parent class first. @@ -825,6 +818,8 @@ void ScriptCreateDialog::_bind_methods() { } ScriptCreateDialog::ScriptCreateDialog() { + EDITOR_DEF("_script_setup_templates_dictionary", Dictionary()); + /* Main Controls */ GridContainer *gc = memnew(GridContainer); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 7517bc3cb9..119508f59b 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1507,7 +1507,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_constant("buttons_vertical_separation", "SpinBox", 0); p_theme->set_constant("field_and_buttons_separation", "SpinBox", 2); p_theme->set_constant("buttons_width", "SpinBox", 16); +#ifndef DISABLE_DEPRECATED p_theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1); +#endif } // ProgressBar. diff --git a/main/main.cpp b/main/main.cpp index db5e6ec398..1af08bc036 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3548,13 +3548,16 @@ int Main::start() { gdscript_docs_path = E->next()->get(); #endif } else if (E->get() == "--export-release") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; //needs editor _export_preset = E->next()->get(); } else if (E->get() == "--export-debug") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; //needs editor _export_preset = E->next()->get(); export_debug = true; } else if (E->get() == "--export-pack") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; _export_preset = E->next()->get(); export_pack_only = true; @@ -3566,6 +3569,8 @@ int Main::start() { if (parsed_pair) { E = E->next(); } + } else if (E->get().begins_with("--export-")) { + ERR_FAIL_V_MSG(EXIT_FAILURE, "Missing export preset name, aborting."); } #ifdef TOOLS_ENABLED // Handle case where no path is given to --doctool. @@ -4409,7 +4414,7 @@ bool Main::iteration() { } #ifdef TOOLS_ENABLED - if (wait_for_import && EditorFileSystem::get_singleton()->doing_first_scan()) { + if (wait_for_import && EditorFileSystem::get_singleton() && EditorFileSystem::get_singleton()->doing_first_scan()) { exit = false; } #endif diff --git a/misc/dist/macos_template.app/Contents/Info.plist b/misc/dist/macos_template.app/Contents/Info.plist index 78bb559c0e..708498dd9c 100644 --- a/misc/dist/macos_template.app/Contents/Info.plist +++ b/misc/dist/macos_template.app/Contents/Info.plist @@ -49,12 +49,17 @@ $usage_descriptions <string>NSApplication</string> <key>LSApplicationCategoryType</key> <string>public.app-category.$app_category</string> - <key>LSMinimumSystemVersion</key> - <string>$min_version</string> + <key>LSArchitecturePriority</key> + <array> + <string>arm64</string> + <string>x86_64</string> + </array> <key>LSMinimumSystemVersionByArchitecture</key> <dict> + <key>arm64</key> + <string>$min_version_arm64</string> <key>x86_64</key> - <string>$min_version</string> + <string>$min_version_x86_64</string> </dict> <key>NSHighResolutionCapable</key> $highres diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7e29a9c0fe..2c5e6d46e7 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3245,6 +3245,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } + +#ifdef DEBUG_ENABLED + // Consider `Signal(self, "my_signal")` as an implicit use of the signal. + if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) { + const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0]; + if (object_arg && object_arg->type == GDScriptParser::Node::SELF) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } + } +#endif + p_call->set_datatype(call_type); return; } else if (GDScriptUtilityFunctions::function_exists(function_name)) { @@ -3479,6 +3499,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } + + // Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal. + if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } #endif // DEBUG_ENABLED call_type = return_type; diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd index d937dfdcfe..37f118dc5d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd @@ -1,12 +1,29 @@ -signal s1() -signal s2() -signal s3() +# Doesn't produce the warning: +signal used_as_first_class_signal() +signal used_with_signal_constructor() +signal used_with_signal_emit() +signal used_with_object_emit_signal() +signal used_with_object_connect() +signal used_with_object_disconnect() +signal used_with_self_prefix() + +# Produce the warning: +signal used_with_dynamic_name() +signal just_unused() @warning_ignore("unused_signal") -signal s4() +signal unused_but_ignored() func no_exec(): - s1.emit() - print(s2) + print(used_as_first_class_signal) + print(Signal(self, "used_with_signal_constructor")) + used_with_signal_emit.emit() + print(emit_signal("used_with_object_emit_signal")) + print(connect("used_with_object_connect", Callable())) + disconnect("used_with_object_disconnect", Callable()) + print(self.emit_signal("used_with_self_prefix")) + + var dynamic_name := "used_with_dynamic_name" + print(emit_signal(dynamic_name)) func test(): pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out index ff57017830..39ddf91c76 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out @@ -1,5 +1,9 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 11 >> UNUSED_SIGNAL ->> The signal "s3" is declared but never explicitly used in the class. +>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class. +>> WARNING +>> Line: 12 +>> UNUSED_SIGNAL +>> The signal "just_unused" is declared but never explicitly used in the class. diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 8472c4e352..8656be988d 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -777,7 +777,7 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_ if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) { int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip; - if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && next_clip != playback_current && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { + if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { auto_advance_to = next_clip; } } @@ -905,7 +905,9 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ // time to start! from_frame = state.fade_wait * mix_rate; state.fade_wait = 0; - queue_next = state.auto_advance; + if (state.fade_speed == 0.0) { + queue_next = state.auto_advance; + } playback_current = p_state_idx; state.first_mix = false; } else { @@ -919,7 +921,6 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame); double frame_fade_inc = state.fade_speed * frame_inc; - for (int i = from_frame; i < p_frames; i++) { if (state.fade_wait) { // This is for fade out of existing stream; @@ -933,6 +934,7 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.fade_speed = 0.0; frame_fade_inc = 0.0; state.fade_volume = 1.0; + queue_next = state.auto_advance; } } else if (frame_fade_inc < 0.0) { state.fade_volume += frame_fade_inc; diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml index e8f8e7b760..17448724d1 100644 --- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml +++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml @@ -4,7 +4,7 @@ Audio stream that can playback music interactively, combining clips and a transition table. </brief_description> <description> - This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and the transition rules via the [method add_transition]. Additionally, this stream export a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. + This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and then the transition rules via the [method add_transition]. Additionally, this stream exports a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. The way this is used is by filling a number of clips, then configuring the transition table. From there, clips are selected for playback and the music will smoothly go from the current to the new one while using the corresponding transition rule defined in the transition table. </description> <tutorials> diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index efbf298f99..9f88b0575e 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -194,7 +194,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: Opt return subprocess.call(args, env=msbuild_env, cwd=chdir_to) -def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision): +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated): target_filenames = [ "GodotSharp.dll", "GodotSharp.pdb", @@ -217,6 +217,8 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, pre args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) @@ -336,12 +338,14 @@ def generate_sdk_package_versions(): f.write(constants) -def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): +def build_all( + msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated +): # Generate SdkPackageVersions.props and VersionDocsUrl constant generate_sdk_package_versions() # Godot API - exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision) + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated) if exit_code != 0: return exit_code @@ -364,6 +368,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) if exit_code != 0: @@ -390,6 +396,12 @@ def main(): parser.add_argument( "--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level" ) + parser.add_argument( + "--no-deprecated", + action="store_true", + default=False, + help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.", + ) args = parser.parse_args() @@ -414,6 +426,7 @@ def main(): args.dev_debug, push_nupkgs_local, args.precision, + args.no_deprecated, ) sys.exit(exit_code) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index b838f8eac7..3a3134d160 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -135,7 +135,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 65b4824f94..715c1a4d51 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -36,7 +36,7 @@ </ProjectReference> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 34ad52bbf6..c261c252ba 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -38,8 +38,11 @@ <member name="application/icon_interpolation" type="int" setter="" getter=""> Interpolation method used to resize application icon. </member> - <member name="application/min_macos_version" type="String" setter="" getter=""> - Minimum version of macOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + <member name="application/min_macos_version_arm64" type="String" setter="" getter=""> + Minimum version of macOS required for this application to run on Apple Silicon Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="application/min_macos_version_x86_64" type="String" setter="" getter=""> + Minimum version of macOS required for this application to run on Intel Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). </member> <member name="application/short_version" type="String" setter="" getter=""> Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 8372600ae9..770871ae19 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -451,7 +451,8 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version"), "10.12")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_x86_64"), "10.12")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_arm64"), "11.00")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true)); @@ -823,8 +824,12 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; } else if (lines[i].contains("$copyright")) { strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].contains("$min_version_arm64")) { + strnew += lines[i].replace("$min_version_arm64", p_preset->get("application/min_macos_version_arm64")) + "\n"; + } else if (lines[i].contains("$min_version_x86_64")) { + strnew += lines[i].replace("$min_version_x86_64", p_preset->get("application/min_macos_version_x86_64")) + "\n"; } else if (lines[i].contains("$min_version")) { - strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n"; + strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version_x86_64")) + "\n"; // Old template, use x86-64 version for both. } else if (lines[i].contains("$highres")) { strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n"; } else if (lines[i].contains("$additional_plist_content")) { diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 9b3c7bb9ea..f65a3c0ecc 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -452,6 +452,7 @@ void NavigationRegion2D::_update_debug_mesh() { const Transform2D region_gt = get_global_transform(); rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas()); + rs->canvas_item_set_z_index(debug_instance_rid, RS::CANVAS_ITEM_Z_MAX - 2); rs->canvas_item_set_transform(debug_instance_rid, region_gt); if (!debug_mesh_dirty) { diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp index aa63fb623f..3b533da701 100644 --- a/scene/3d/xr_hand_modifier_3d.cpp +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -30,6 +30,7 @@ #include "xr_hand_modifier_3d.h" +#include "core/config/project_settings.h" #include "servers/xr/xr_pose.h" #include "servers/xr_server.h" @@ -283,6 +284,17 @@ void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { _get_joint_data(); } +PackedStringArray XRHandModifier3D::get_configuration_warnings() const { + PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings(); + + // Detect OpenXR without the Hand Tracking extension. + if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { + warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled."); + } + + return warnings; +} + void XRHandModifier3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h index 3d78f32b64..d58ccd0adf 100644 --- a/scene/3d/xr_hand_modifier_3d.h +++ b/scene/3d/xr_hand_modifier_3d.h @@ -55,6 +55,8 @@ public: void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; + PackedStringArray get_configuration_warnings() const override; + void _notification(int p_what); protected: diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 0ab4dbe470..1c5f40f56e 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -81,9 +81,8 @@ bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) { List<Variant> keys; d.get_key_list(&keys); for (const Variant &K : keys) { - StringName lib_name = K; - Ref<AnimationLibrary> lib = d[lib_name]; - add_animation_library(lib_name, lib); + Ref<AnimationLibrary> lib = d[K]; + add_animation_library(K, lib); } emit_signal(SNAME("animation_libraries_updated")); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index e8be38e680..c3287035ff 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -40,8 +40,18 @@ void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + _update_line_number_gutter_width(); set_gutter_width(fold_gutter, get_line_height() / 1.2); + _clear_line_number_text_cache(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_VISIBILITY_CHANGED: { + // Avoid having many hidden text editors with unused cache filling up memory. + _clear_line_number_text_cache(); } break; case NOTIFICATION_DRAW: { @@ -1287,9 +1297,9 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line); if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) { bool breakpointed = is_line_breakpointed(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { @@ -1308,7 +1318,6 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) { bool bookmarked = is_line_bookmarked(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { @@ -1442,7 +1451,13 @@ bool CodeEdit::is_draw_line_numbers_enabled() const { } void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { - p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; + String new_line_number_padding = p_zero_padded ? "0" : " "; + if (line_number_padding == new_line_number_padding) { + return; + } + + line_number_padding = new_line_number_padding; + _clear_line_number_text_cache(); queue_redraw(); } @@ -1451,19 +1466,55 @@ bool CodeEdit::is_line_numbers_zero_padded() const { } void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); - if (is_localizing_numeral_system()) { - fc = TS->format_number(fc); - } - Ref<TextLine> tl; - tl.instantiate(); - tl->add_string(fc, theme_cache.font, theme_cache.font_size); - int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2; + if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) { + return; + } + + bool rtl = is_layout_rtl(); + HashMap<int, RID>::Iterator E = line_number_text_cache.find(p_line); + RID text_rid; + if (E) { + text_rid = E->value; + } else { + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + if (is_localizing_numeral_system()) { + fc = TS->format_number(fc); + } + + text_rid = TS->create_shaped_text(); + if (theme_cache.font.is_valid()) { + TS->shaped_text_add_string(text_rid, fc, theme_cache.font->get_rids(), theme_cache.font_size, theme_cache.font->get_opentype_features()); + } + line_number_text_cache.insert(p_line, text_rid); + } + + Size2 text_size = TS->shaped_text_get_size(text_rid); + Point2 ofs = p_region.get_center() - text_size / 2; + ofs.y += TS->shaped_text_get_ascent(text_rid); + + if (rtl) { + ofs.x = p_region.position.x; + } else { + ofs.x = p_region.get_end().x - text_size.width; + } + Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); if (number_color == Color(1, 1, 1)) { number_color = theme_cache.line_number_color; } - tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color); + + TS->shaped_text_draw(text_rid, get_canvas_item(), ofs, -1, -1, number_color); +} + +void CodeEdit::_clear_line_number_text_cache() { + for (const KeyValue<int, RID> &KV : line_number_text_cache) { + TS->free_rid(KV.value); + } + line_number_text_cache.clear(); +} + +void CodeEdit::_update_line_number_gutter_width() { + set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); } /* Fold Gutter */ @@ -1983,12 +2034,18 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { /* Code hint */ void CodeEdit::set_code_hint(const String &p_hint) { + if (code_hint == p_hint) { + return; + } code_hint = p_hint; code_hint_xpos = -0xFFFF; queue_redraw(); } void CodeEdit::set_code_hint_draw_below(bool p_below) { + if (code_hint_draw_below == p_below) { + return; + } code_hint_draw_below = p_below; queue_redraw(); } @@ -3609,16 +3666,13 @@ void CodeEdit::_text_changed() { } int lc = get_line_count(); - line_number_digits = 1; - while (lc /= 10) { - line_number_digits++; - } - - if (theme_cache.font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + int new_line_number_digits = log10l(lc) + 1; + if (line_number_digits != new_line_number_digits) { + _clear_line_number_text_cache(); } + line_number_digits = new_line_number_digits; + _update_line_number_gutter_width(); - lc = get_line_count(); List<int> breakpoints; for (const KeyValue<int, bool> &E : breakpointed_lines) { breakpoints.push_back(E.key); @@ -3705,6 +3759,7 @@ CodeEdit::CodeEdit() { } CodeEdit::~CodeEdit() { + _clear_line_number_text_cache(); } // Return true if l should come before r diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 09340be035..ab443e95e1 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -113,6 +113,9 @@ private: int line_number_gutter = -1; int line_number_digits = 1; String line_number_padding = " "; + HashMap<int, RID> line_number_text_cache; + void _clear_line_number_text_cache(); + void _update_line_number_gutter_width(); void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); /* Fold Gutter */ diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index bd4128384f..00175b4347 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -912,84 +912,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o double uth = TS->shaped_text_get_underline_thickness(rid); off.y += l_ascent; - // Draw inlined objects. - Array objects = TS->shaped_text_get_objects(rid); - for (int i = 0; i < objects.size(); i++) { - Item *it = items.get_or_null(objects[i]); - if (it != nullptr) { - Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); - if (trim_chars && l.char_offset + obj_range.y > visible_characters) { - continue; - } - if (trim_glyphs_ltr || trim_glyphs_rtl) { - int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); - if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { - continue; - } - } - Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); - switch (it->type) { - case ITEM_IMAGE: { - ItemImage *img = static_cast<ItemImage *>(it); - if (img->pad) { - Size2 pad_size = rect.size.min(img->image->get_size()); - Vector2 pad_off = (rect.size - pad_size) / 2; - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); - } else { - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); - } - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = theme_cache.table_odd_row_bg; - Color even_row_bg = theme_cache.table_even_row_bg; - Color border = theme_cache.table_border; - float h_separation = theme_cache.table_h_separation; - float v_separation = theme_cache.table_v_separation; - - int col_count = table->columns.size(); - int row_count = table->rows.size(); - - int idx = 0; - for (Item *E : table->subitems) { - ItemFrame *frame = static_cast<ItemFrame *>(E); - - int col = idx % col_count; - int row = idx / col_count; - - if (frame->lines.size() != 0 && row < row_count) { - Vector2 coff = frame->lines[0].offset; - if (rtl) { - coff.x = rect.size.width - table->columns[col].width - coff.x; - } - if (row % 2 == 0) { - Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } else { - Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } - Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; - if (bc.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); - } - } - - for (int j = 0; j < (int)frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); - } - idx++; - } - } break; - default: - break; - } - } - } const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); int gl_size = TS->shaped_text_get_glyph_count(rid); @@ -1005,6 +927,86 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o int processed_glyphs_step = 0; for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_TEXT) { + // Draw inlined objects. + Array objects = TS->shaped_text_get_objects(rid); + for (int i = 0; i < objects.size(); i++) { + Item *it = items.get_or_null(objects[i]); + if (it != nullptr) { + Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); + if (trim_chars && l.char_offset + obj_range.y > visible_characters) { + continue; + } + if (trim_glyphs_ltr || trim_glyphs_rtl) { + int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); + if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { + continue; + } + } + Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); + switch (it->type) { + case ITEM_IMAGE: { + ItemImage *img = static_cast<ItemImage *>(it); + if (img->pad) { + Size2 pad_size = rect.size.min(img->image->get_size()); + Vector2 pad_off = (rect.size - pad_size) / 2; + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); + } else { + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + } + } break; + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + float h_separation = theme_cache.table_h_separation; + float v_separation = theme_cache.table_v_separation; + + int col_count = table->columns.size(); + int row_count = table->rows.size(); + + int idx = 0; + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); + + int col = idx % col_count; + int row = idx / col_count; + + if (frame->lines.size() != 0 && row < row_count) { + Vector2 coff = frame->lines[0].offset; + if (rtl) { + coff.x = rect.size.width - table->columns[col].width - coff.x; + } + if (row % 2 == 0) { + Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } else { + Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } + Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; + if (bc.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); + } + } + + for (int j = 0; j < (int)frame->lines.size(); j++) { + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); + } + idx++; + } + } break; + default: + break; + } + } + } + } Vector2 off_step = off; processed_glyphs_step = r_processed_glyphs; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 4212cd709f..ac81f0de56 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -287,7 +287,12 @@ inline void SpinBox::_compute_sizes() { int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation; int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation; - int w = theme_cache.set_min_buttons_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; +#ifndef DISABLE_DEPRECATED + const bool min_width_from_icons = theme_cache.set_min_buttons_width_from_icons || (theme_cache.buttons_width < 0); +#else + const bool min_width_from_icons = theme_cache.buttons_width < 0; +#endif + int w = min_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; if (w != sizing_cache.buttons_block_width) { line_edit->set_offset(SIDE_LEFT, 0); @@ -551,7 +556,9 @@ void SpinBox::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width); +#ifndef DISABLE_DEPRECATED BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons); +#endif BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up"); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 7c6974f6a8..592805f43a 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -127,8 +127,9 @@ class SpinBox : public Range { int buttons_vertical_separation = 0; int field_and_buttons_separation = 0; int buttons_width = 0; - int set_min_buttons_width_from_icons = 0; - +#ifndef DISABLE_DEPRECATED + bool set_min_buttons_width_from_icons = false; +#endif } theme_cache; void _mouse_exited(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index c4b33fd889..ab0ad2f4b7 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -112,8 +112,34 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } +int TextEdit::Text::get_max_width() const { + if (max_line_width_dirty) { + int new_max_line_width = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_width = MAX(new_max_line_width, l.width); + } + max_line_width = new_max_line_width; + } + + return max_line_width; +} + int TextEdit::Text::get_line_height() const { - return line_height; + if (max_line_height_dirty) { + int new_max_line_height = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_height = MAX(new_max_line_height, l.height); + } + max_line_height = new_max_line_height; + } + + return max_line_height; } void TextEdit::Text::set_width(float p_width) { @@ -135,15 +161,17 @@ BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const { int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - return text[p_line].data_buf->get_line_count() - 1; + return text[p_line].line_count - 1; } Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const { Vector<Vector2i> ret; ERR_FAIL_INDEX_V(p_line, text.size(), ret); - for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) { - ret.push_back(text[p_line].data_buf->get_line_range(i)); + Ref<TextParagraph> data_buf = text[p_line].data_buf; + int line_count = data_buf->get_line_count(); + for (int i = 0; i < line_count; i++) { + ret.push_back(data_buf->get_line_range(i)); } return ret; } @@ -153,40 +181,11 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const { return text[p_line].data_buf; } -_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { +_FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); return text[p_line].data; } -void TextEdit::Text::_calculate_line_height() { - int height = 0; - for (const Line &l : text) { - // Found another line with the same height...nothing to update. - if (l.height == line_height) { - height = line_height; - break; - } - height = MAX(height, l.height); - } - line_height = height; -} - -void TextEdit::Text::_calculate_max_line_width() { - int line_width = 0; - for (const Line &l : text) { - if (l.hidden) { - continue; - } - - // Found another line with the same width...nothing to update. - if (l.width == max_width) { - line_width = max_width; - break; - } - line_width = MAX(line_width, l.width); - } - max_width = line_width; -} - void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -194,8 +193,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan return; // Not in tree? } + Line &text_line = text.write[p_line]; if (p_text_changed) { - text.write[p_line].data_buf->clear(); + text_line.data_buf->clear(); } BitField<TextServer::LineBreakFlag> flags = brk_flags; @@ -203,30 +203,30 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan flags.set_flag(TextServer::BREAK_TRIM_INDENT); } - text.write[p_line].data_buf->set_width(width); - text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); - text.write[p_line].data_buf->set_break_flags(flags); - text.write[p_line].data_buf->set_preserve_control(draw_control_chars); - text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators()); + text_line.data_buf->set_width(width); + text_line.data_buf->set_direction((TextServer::Direction)direction); + text_line.data_buf->set_break_flags(flags); + text_line.data_buf->set_preserve_control(draw_control_chars); + text_line.data_buf->set_custom_punctuation(get_enabled_word_separators()); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); + text_line.data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); + text_line.data_buf->add_string(text_line.data, font, font_size, language); } - if (!text[p_line].bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); + if (!text_line.bidi_override.is_empty()) { + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), text_line.bidi_override); } } if (!p_text_changed) { - RID r = text.write[p_line].data_buf->get_rid(); + RID r = text_line.data_buf->get_rid(); int spans = TS->shaped_get_span_count(r); for (int i = 0; i < spans; i++) { TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features()); @@ -237,61 +237,58 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[p_line].data_buf->tab_align(tabs); + text_line.data_buf->tab_align(tabs); + } + + // Update wrap amount. + const int old_line_count = text_line.line_count; + text_line.line_count = text_line.data_buf->get_line_count(); + if (!text_line.hidden && text_line.line_count != old_line_count) { + total_visible_line_count += text_line.line_count - old_line_count; } // Update height. - const int old_height = text.write[p_line].height; - const int wrap_amount = get_line_wrap_amount(p_line); - int height = font_height; - for (int i = 0; i <= wrap_amount; i++) { - height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + const int old_height = text_line.height; + text_line.height = font_height; + for (int i = 0; i < text_line.line_count; i++) { + text_line.height = MAX(text_line.height, text_line.data_buf->get_line_size(i).y); } - text.write[p_line].height = height; - // If this line has shrunk, this may no longer the tallest line. - if (old_height == line_height && height < line_height) { - _calculate_line_height(); - } else { - line_height = MAX(height, line_height); + // If this line has shrunk, this may no longer be the tallest line. + if (!text_line.hidden) { + if (old_height == max_line_height && text_line.height < old_height) { + max_line_height_dirty = true; + } else { + max_line_height = MAX(text_line.height, max_line_height); + } } // Update width. - const int old_width = text.write[p_line].width; - int line_width = get_line_width(p_line); - text.write[p_line].width = line_width; + const int old_width = text_line.width; + text_line.width = get_line_width(p_line); - // If this line has shrunk, this may no longer the longest line. - if (old_width == max_width && line_width < max_width) { - _calculate_max_line_width(); - } else if (!is_hidden(p_line)) { - max_width = MAX(line_width, max_width); + if (!text_line.hidden) { + // If this line has shrunk, this may no longer be the longest line. + if (old_width == max_line_width && text_line.width < old_width) { + max_line_width_dirty = true; + } else { + max_line_width = MAX(text_line.width, max_line_width); + } } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { - BitField<TextServer::LineBreakFlag> flags = brk_flags; - if (indent_wrapped_lines) { - flags.set_flag(TextServer::BREAK_TRIM_INDENT); - } - text.write[i].data_buf->set_width(width); - text.write[i].data_buf->set_break_flags(flags); - text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators()); - if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + text[i].data_buf->tab_align(tabs); } } - text.write[i].width = get_line_width(i); + invalidate_cache(i, -1, false); } tab_size_dirty = false; - - max_width = -1; - _calculate_max_line_width(); } void TextEdit::Text::invalidate_font() { @@ -299,8 +296,8 @@ void TextEdit::Text::invalidate_font() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -317,8 +314,8 @@ void TextEdit::Text::invalidate_all() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -333,8 +330,8 @@ void TextEdit::Text::invalidate_all() { void TextEdit::Text::clear() { text.clear(); - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; Line line; line.gutters.resize(gutter_count); @@ -343,8 +340,8 @@ void TextEdit::Text::clear() { invalidate_cache(0, -1, true); } -int TextEdit::Text::get_max_width() const { - return max_width; +int TextEdit::Text::get_total_visible_line_count() const { + return total_visible_line_count; } void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) { @@ -355,7 +352,37 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o invalidate_cache(p_line, -1, true); } +void TextEdit::Text::set_hidden(int p_line, bool p_hidden) { + ERR_FAIL_INDEX(p_line, text.size()); + + Line &text_line = text.write[p_line]; + if (text_line.hidden == p_hidden) { + return; + } + text_line.hidden = p_hidden; + if (p_hidden) { + total_visible_line_count -= text_line.line_count; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; + } + if (text_line.height == max_line_height) { + max_line_height_dirty = true; + } + } else { + total_visible_line_count += text_line.line_count; + max_line_width = MAX(text_line.width, max_line_width); + max_line_height = MAX(text_line.height, max_line_height); + } +} + +bool TextEdit::Text::is_hidden(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), true); + return text[p_line].hidden; +} + void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + ERR_FAIL_INDEX(p_at, text.size() + 1); + int new_line_count = p_text.size() - 1; if (new_line_count > 0) { text.resize(text.size() + new_line_count); @@ -382,24 +409,25 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector } void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + p_from_line = MAX(p_from_line, 0); + ERR_FAIL_INDEX(p_from_line, text.size()); + + p_to_line = MIN(p_to_line, text.size()); + ERR_FAIL_COND(p_to_line < p_from_line); + if (p_from_line == p_to_line) { return; } - bool dirty_height = false; - bool dirty_width = false; for (int i = p_from_line; i < p_to_line; i++) { - if (!dirty_height && text[i].height == line_height) { - dirty_height = true; + const Line &text_line = text[i]; + if (text_line.height == max_line_height) { + max_line_height_dirty = true; } - - if (!dirty_width && text[i].width == max_width) { - dirty_width = true; - } - - if (dirty_height && dirty_width) { - break; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; } + total_visible_line_count -= text_line.line_count; } int diff = (p_to_line - p_from_line); @@ -407,16 +435,6 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { text.write[(i - diff) + 1] = text[i + 1]; } text.resize(text.size() - diff); - - if (dirty_height) { - line_height = -1; - _calculate_line_height(); - } - - if (dirty_width) { - max_width = -1; - _calculate_max_line_width(); - } } void TextEdit::Text::add_gutter(int p_at) { @@ -431,6 +449,8 @@ void TextEdit::Text::add_gutter(int p_at) { } void TextEdit::Text::remove_gutter(int p_gutter) { + ERR_FAIL_INDEX(p_gutter, text.size()); + for (int i = 0; i < text.size(); i++) { text.write[i].gutters.remove_at(p_gutter); } @@ -438,6 +458,9 @@ void TextEdit::Text::remove_gutter(int p_gutter) { } void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + text.write[p_to_line].gutters = text[p_from_line].gutters; text.write[p_from_line].gutters.clear(); text.write[p_from_line].gutters.resize(gutter_count); @@ -625,6 +648,8 @@ void TextEdit::_notification(int p_what) { brace_matching.resize(get_caret_count()); for (int caret = 0; caret < get_caret_count(); caret++) { + BraceMatchingData &brace_match = brace_matching.write[caret]; + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { continue; } @@ -678,20 +703,20 @@ void TextEdit::_notification(int p_what) { } 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; + brace_match.open_match_line = i; + brace_match.open_match_column = j; + brace_match.open_matching = true; break; } } - if (brace_matching.write[caret].open_match_line != -1) { + if (brace_match.open_match_line != -1) { break; } } - if (!brace_matching.write[caret].open_matching) { - brace_matching.write[caret].open_mismatch = true; + if (!brace_match.open_matching) { + brace_match.open_mismatch = true; } } } @@ -744,20 +769,20 @@ void TextEdit::_notification(int p_what) { } 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; + brace_match.close_match_line = i; + brace_match.close_match_column = j; + brace_match.close_matching = true; break; } } - if (brace_matching.write[caret].close_match_line != -1) { + if (brace_match.close_match_line != -1) { break; } } - if (!brace_matching.write[caret].close_matching) { - brace_matching.write[caret].close_mismatch = true; + if (!brace_match.close_matching) { + brace_match.close_mismatch = true; } } } @@ -772,13 +797,14 @@ void TextEdit::_notification(int p_what) { // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<Pair<int, int>> highlighted_lines; + highlighted_lines.resize(carets.size()); 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); + highlighted_lines.write[i] = Pair<int, int>(get_caret_line(i), wrap_index); carets_wrap_index.write[i] = wrap_index; } @@ -842,7 +868,7 @@ void TextEdit::_notification(int p_what) { break; } - Dictionary color_map = _get_line_syntax_highlighting(minimap_line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(minimap_line); Color line_background_color = text.get_line_background_color(minimap_line); @@ -853,12 +879,9 @@ void TextEdit::_notification(int p_what) { line_background_color.a *= 0.6; } - Color current_color = theme_cache.font_color; - if (!editable) { - current_color = theme_cache.font_readonly_color; - } + Color current_color = editable ? theme_cache.font_color : theme_cache.font_readonly_color; - Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); + const Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); int line_wrap_amount = get_line_wrap_count(minimap_line); int last_wrap_column = 0; @@ -881,13 +904,13 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(minimap_line, line_wrap_index))) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color); } - } else if (line_background_color != Color(0, 0, 0, 0)) { + } else if (line_background_color.a > 0) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color); } else { @@ -905,13 +928,17 @@ void TextEdit::_notification(int p_what) { // Get the number of characters to draw together. for (characters = 0; j + characters < str.length(); characters++) { int next_char_index = j + characters; - const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index); - if (color_data != nullptr) { - next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable) { - next_color.a = theme_cache.font_readonly_color.a; + + for (const Pair<int64_t, Color> &color_data : color_map) { + if (last_wrap_column + next_char_index >= color_data.first) { + next_color = color_data.second; + if (!editable) { + next_color.a = theme_cache.font_readonly_color.a; + } + next_color.a *= 0.6; + } else { + break; } - next_color.a *= 0.6; } if (characters == 0) { current_color = next_color; @@ -1002,7 +1029,7 @@ void TextEdit::_notification(int p_what) { LineDrawingCache cache_entry; - Dictionary color_map = _get_line_syntax_highlighting(line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; @@ -1012,7 +1039,7 @@ void TextEdit::_notification(int p_what) { const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); - Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); + const Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { @@ -1053,20 +1080,20 @@ void TextEdit::_notification(int p_what) { break; } - if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) { + if (text.get_line_background_color(line).a > 0.0) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } } // Draw current line highlight. - if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(line, line_wrap_index))) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } @@ -1077,7 +1104,7 @@ void TextEdit::_notification(int p_what) { int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { - const GutterInfo gutter = gutters[g]; + const GutterInfo &gutter = gutters[g]; if (!gutter.draw || gutter.width <= 0) { continue; @@ -1184,7 +1211,7 @@ void TextEdit::_notification(int p_what) { if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.selection_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.selection_color); } } } @@ -1194,7 +1221,7 @@ void TextEdit::_notification(int p_what) { int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); 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) { @@ -1206,7 +1233,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.search_result_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.search_result_color); draw_rect(rect, theme_cache.search_result_border_color, false); } @@ -1218,7 +1245,7 @@ void TextEdit::_notification(int p_what) { int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); 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) { @@ -1230,7 +1257,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.word_highlighted_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.word_highlighted_color); } highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); @@ -1243,7 +1270,7 @@ void TextEdit::_notification(int p_what) { int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int lookup_symbol_word_len = lookup_symbol_word.length(); while (lookup_symbol_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1257,7 +1284,7 @@ void TextEdit::_notification(int p_what) { } rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size)); rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size)); - draw_rect(rect, highlight_underline_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, highlight_underline_color); } lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); @@ -1293,21 +1320,16 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - int64_t color_start = -1; - for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) { - if (int64_t(*key) <= glyphs[j].start) { - color_start = *key; + for (const Pair<int64_t, Color> &color_data : color_map) { + if (color_data.first <= glyphs[j].start) { + current_color = color_data.second; + if (!editable && current_color.a > theme_cache.font_readonly_color.a) { + current_color.a = theme_cache.font_readonly_color.a; + } } else { break; } } - const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr; - if (color_data != nullptr) { - current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable && current_color.a > theme_cache.font_readonly_color.a) { - current_color.a = theme_cache.font_readonly_color.a; - } - } Color gl_color = current_color; for (int c = 0; c < get_caret_count(); c++) { @@ -1325,22 +1347,23 @@ void TextEdit::_notification(int p_what) { if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { for (int c = 0; c < get_caret_count(); 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) { + const BraceMatchingData &brace_match = brace_matching[c]; + if ((brace_match.open_match_line == line && brace_match.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_match.open_matching || brace_match.open_mismatch))) { + if (brace_match.open_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_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) { + if ((brace_match.close_match_line == line && brace_match.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_match.close_matching || brace_match.close_mismatch))) { + if (brace_match.close_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } } } @@ -1451,11 +1474,11 @@ void TextEdit::_notification(int p_what) { // Draw split caret (leading part). 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, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } } else { // End of the line. if (gl_size > 0) { @@ -1488,28 +1511,28 @@ void TextEdit::_notification(int p_what) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * 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, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } 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, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); 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, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.t_caret, theme_cache.caret_color); } } } @@ -1517,7 +1540,7 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { { // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + const 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) { @@ -1536,7 +1559,7 @@ void TextEdit::_notification(int p_what) { } { // 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); + const 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) { @@ -1575,26 +1598,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } - _update_ime_window_position(); - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - int caret_start = -1; - int caret_end = -1; - - 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, 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(); - } - - DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); - } + _show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { @@ -1943,8 +1947,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - // Notify to show soft keyboard. - notification(NOTIFICATION_FOCUS_ENTER); + _show_virtual_keyboard(); } } @@ -2929,6 +2932,29 @@ void TextEdit::_update_ime_text() { queue_redraw(); } +void TextEdit::_show_virtual_keyboard() { + _update_ime_window_position(); + + if (virtual_keyboard_enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) { + int caret_start = -1; + int caret_end = -1; + + 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, 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(); + } + + DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); + } +} + /* General overrides. */ Size2 TextEdit::get_minimum_size() const { Size2 size = theme_cache.style_normal->get_minimum_size(); @@ -5888,7 +5914,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co } int TextEdit::get_total_visible_line_count() const { - return get_visible_line_count_in_range(0, text.size() - 1); + return text.get_total_visible_line_count(); } // Auto adjust. @@ -8162,8 +8188,36 @@ void TextEdit::_update_gutter_width() { } /* Syntax highlighting. */ -Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { - return (syntax_highlighter.is_null() || setting_text) ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); +Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighter.is_null() || setting_text) { + return Vector<Pair<int64_t, Color>>(); + } + + HashMap<int, Vector<Pair<int64_t, Color>>>::Iterator E = syntax_highlighting_cache.find(p_line); + if (E) { + return E->value; + } + + Dictionary color_map = syntax_highlighter->get_line_syntax_highlighting(p_line); + Vector<Pair<int64_t, Color>> result; + result.resize(color_map.size()); + int i = 0; + for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key), i++) { + int64_t key_data = *key; + const Variant *color_data = color_map.getptr(*key); + Color color_value = editable ? theme_cache.font_color : theme_cache.font_readonly_color; + if (color_data != nullptr) { + color_value = (color_data->operator Dictionary()).get("color", color_value); + } + result.write[i] = Pair<int64_t, Color>(key_data, color_value); + } + syntax_highlighting_cache.insert(p_line, result); + + return result; +} + +void TextEdit::_clear_syntax_highlighting_cache() { + syntax_highlighting_cache.clear(); } /* Deprecated. */ @@ -8189,6 +8243,7 @@ int TextEdit::get_selection_column(int p_caret) const { /*** Super internal Core API. Everything builds on it. ***/ void TextEdit::_text_changed() { + _clear_syntax_highlighting_cache(); _cancel_drag_and_drop_text(); queue_redraw(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 1f2fd6619a..a448e185b1 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -153,6 +153,7 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int line_count = 0; int height = 0; int width = 0; @@ -178,16 +179,19 @@ private: bool use_default_word_separators = true; bool use_custom_word_separators = false; - int line_height = -1; - int max_width = -1; + mutable bool max_line_width_dirty = true; + mutable bool max_line_height_dirty = true; + mutable int max_line_width = 0; + mutable int max_line_height = 0; + mutable int total_visible_line_count = 0; int width = -1; int tab_size = 4; int gutter_count = 0; bool indent_wrapped_lines = false; - void _calculate_line_height(); - void _calculate_max_line_width(); + void _calculate_line_height() const; + void _calculate_max_line_width() const; public: void set_tab_size(int p_tab_size); @@ -203,6 +207,7 @@ private: int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; int get_max_width() const; + int get_total_visible_line_count() const; void set_use_default_word_separators(bool p_enabled); bool is_default_word_separators_enabled() const; @@ -226,18 +231,8 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Array &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { - if (text[p_line].hidden == p_hidden) { - return; - } - text.write[p_line].hidden = p_hidden; - if (!p_hidden && text[p_line].width > max_width) { - max_width = text[p_line].width; - } else if (p_hidden && text[p_line].width == max_width) { - _calculate_max_line_width(); - } - } - bool is_hidden(int p_line) const { return text[p_line].hidden; } + void set_hidden(int p_line, bool p_hidden); + bool is_hidden(int p_line) const; void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override); void remove_range(int p_from_line, int p_to_line); int size() const { return text.size(); } @@ -248,7 +243,7 @@ private: void invalidate_all(); void invalidate_all_lines(); - _FORCE_INLINE_ const String &operator[](int p_line) const; + _FORCE_INLINE_ String operator[](int p_line) const; /* Gutters. */ void add_gutter(int p_at); @@ -453,6 +448,7 @@ private: void _caret_changed(int p_caret = -1); void _emit_caret_changed(); + void _show_virtual_keyboard(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -568,8 +564,10 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; + HashMap<int, Vector<Pair<int64_t, Color>>> syntax_highlighting_cache; - Dictionary _get_line_syntax_highlighting(int p_line); + Vector<Pair<int64_t, Color>> _get_line_syntax_highlighting(int p_line); + void _clear_syntax_highlighting_cache(); /* Visual. */ struct ThemeCache { @@ -1023,6 +1021,7 @@ public: void add_gutter(int p_at = -1); void remove_gutter(int p_gutter); int get_gutter_count() const; + Vector2i get_hovered_gutter() const { return hovered_gutter; } void set_gutter_name(int p_gutter, const String &p_name); String get_gutter_name(int p_gutter) const; diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp index 52d02e92cb..60b91ef0cb 100644 --- a/scene/resources/style_box_flat.cpp +++ b/scene/resources/style_box_flat.cpp @@ -300,8 +300,8 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - const float x_skew = -skew.x * (y - ring_rect.get_center().y); - const float y_skew = -skew.y * (x - ring_rect.get_center().x); + const float x_skew = -skew.x * (y - style_rect.get_center().y); + const float y_skew = -skew.y * (x - style_rect.get_center().x); verts.push_back(Vector2(x + x_skew, y + y_skew)); colors.push_back(color); } diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 5da47966dd..29a8541cb0 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -173,6 +173,7 @@ void TextParagraph::_shape_lines() { v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; } + Size2i range = TS->shaped_text_get_range(rid); if (h_offset > 0) { // Dropcap, flow around. PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags); @@ -182,7 +183,7 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - start = line_breaks[i + 1]; + start = (i < line_breaks.size() - 2) ? line_breaks[i + 2] : range.y; lines_rid.push_back(line); if (v_offset < h) { break; @@ -192,13 +193,15 @@ void TextParagraph::_shape_lines() { } } // Use fixed for the rest of lines. - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); - for (int i = 0; i < line_breaks.size(); i = i + 2) { - RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); - if (!tab_stops.is_empty()) { - TS->shaped_text_tab_align(line, tab_stops); + if (start == 0 || start < range.y) { + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + if (!tab_stops.is_empty()) { + TS->shaped_text_tab_align(line, tab_stops); + } + lines_rid.push_back(line); } - lines_rid.push_back(line); } BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; @@ -550,18 +553,42 @@ Size2 TextParagraph::get_size() const { _THREAD_SAFE_METHOD_ const_cast<TextParagraph *>(this)->_shape_lines(); + + float h_offset = 0.f; + float v_offset = 0.f; + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + v_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + } else { + h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + } + Size2 size; int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.x += h_offset; + } size.x = MAX(size.x, lsize.x); size.y += lsize.y; } else { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.y += h_offset; + } size.x += lsize.x; size.y = MAX(size.y, lsize.y); } } + if (h_offset > 0) { + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + size.y = MAX(size.y, v_offset); + } else { + size.x = MAX(size.x, v_offset); + } + } return size; } @@ -624,7 +651,7 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -793,7 +820,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -895,7 +922,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 8a9e784c47..749d4e3530 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -648,7 +648,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("buttons_vertical_separation", "SpinBox", 0); theme->set_constant("field_and_buttons_separation", "SpinBox", 2); theme->set_constant("buttons_width", "SpinBox", 16); +#ifndef DISABLE_DEPRECATED theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1); +#endif // ScrollContainer diff --git a/tests/scene/test_height_map_shape_3d.h b/tests/scene/test_height_map_shape_3d.h new file mode 100644 index 0000000000..60a8db4e58 --- /dev/null +++ b/tests/scene/test_height_map_shape_3d.h @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* test_height_map_shape_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_HEIGHT_MAP_SHAPE_3D_H +#define TEST_HEIGHT_MAP_SHAPE_3D_H + +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/image_texture.h" + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestHeightMapShape3D { + +TEST_CASE("[SceneTree][HeightMapShape3D] Constructor") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + CHECK(height_map_shape->get_map_width() == 2); + CHECK(height_map_shape->get_map_depth() == 2); + CHECK(height_map_shape->get_map_data().size() == 4); + CHECK(height_map_shape->get_min_height() == 0.0); + CHECK(height_map_shape->get_max_height() == 0.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_width and get_map_width") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(10); + CHECK(height_map_shape->get_map_width() == 10); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_depth and get_map_depth") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_depth(15); + CHECK(height_map_shape->get_map_depth() == 15); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_data and get_map_data") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + Vector<real_t> map_data; + map_data.push_back(1.0); + map_data.push_back(2.0); + height_map_shape->set_map_data(map_data); + CHECK(height_map_shape->get_map_data().size() == 4.0); + CHECK(height_map_shape->get_map_data()[0] == 0.0); + CHECK(height_map_shape->get_map_data()[1] == 0.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] get_min_height") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(3); + height_map_shape->set_map_depth(1); + height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 }); + CHECK(height_map_shape->get_min_height() == 0.5); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] get_max_height") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(3); + height_map_shape->set_map_depth(1); + height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 }); + CHECK(height_map_shape->get_max_height() == 2.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] update_map_data_from_image") { + // Create a HeightMapShape3D instance. + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + + // Create a mock image with FORMAT_R8 and set its data. + Vector<uint8_t> image_data; + image_data.push_back(0); + image_data.push_back(128); + image_data.push_back(255); + image_data.push_back(64); + + Ref<Image> image = memnew(Image); + image->set_data(2, 2, false, Image::FORMAT_R8, image_data); + + height_map_shape->update_map_data_from_image(image, 0.0, 10.0); + + // Check the map data. + Vector<real_t> expected_map_data = { 0.0, 5.0, 10.0, 2.5 }; + Vector<real_t> actual_map_data = height_map_shape->get_map_data(); + real_t tolerance = 0.1; + + for (int i = 0; i < expected_map_data.size(); ++i) { + CHECK(Math::abs(actual_map_data[i] - expected_map_data[i]) < tolerance); + } + + // Check the min and max heights. + CHECK(height_map_shape->get_min_height() == 0.0); + CHECK(height_map_shape->get_max_height() == 10.0); +} + +} // namespace TestHeightMapShape3D + +#endif // TEST_HEIGHT_MAP_SHAPE_3D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 502aed6a6e..4487167dba 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -155,6 +155,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_camera_3d.h" +#include "tests/scene/test_height_map_shape_3d.h" #include "tests/scene/test_path_3d.h" #include "tests/scene/test_path_follow_3d.h" #include "tests/scene/test_primitives.h" |