diff options
Diffstat (limited to 'modules')
114 files changed, 3029 insertions, 1544 deletions
diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h index 6377f8915d..dffcadbcdc 100644 --- a/modules/bullet/shape_bullet.h +++ b/modules/bullet/shape_bullet.h @@ -105,7 +105,7 @@ private: }; class SphereShapeBullet : public ShapeBullet { - real_t radius; + real_t radius = 0.0; public: SphereShapeBullet(); @@ -137,8 +137,8 @@ private: }; class CapsuleShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CapsuleShapeBullet(); @@ -155,8 +155,8 @@ private: }; class CylinderShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CylinderShapeBullet(); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 41b4682c84..fbddedbe55 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -1451,8 +1451,8 @@ Ref<Material> CSGCylinder3D::get_material() const { CSGCylinder3D::CSGCylinder3D() { // defaults - radius = 1.0; - height = 1.0; + radius = 0.5; + height = 2.0; sides = 8; cone = false; smooth_faces = true; @@ -1671,8 +1671,8 @@ Ref<Material> CSGTorus3D::get_material() const { CSGTorus3D::CSGTorus3D() { // defaults - inner_radius = 2.0; - outer_radius = 3.0; + inner_radius = 0.5; + outer_radius = 1.0; sides = 8; ring_sides = 6; smooth_faces = true; @@ -1694,7 +1694,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { } int shape_sides = shape_polygon.size(); Vector<int> shape_faces = Geometry2D::triangulate_polygon(shape_polygon); - ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon"); + ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon. Make sure the polygon doesn't have any intersecting edges."); // Get polygon enclosing Rect2. Rect2 shape_rect(shape_polygon[0], Vector2()); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index eed995a40e..6da9893368 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -239,7 +239,7 @@ class CSGBox3D : public CSGPrimitive3D { virtual CSGBrush *_build_brush() override; Ref<Material> material; - Vector3 size = Vector3(2, 2, 2); + Vector3 size = Vector3(1, 1, 1); protected: static void _bind_methods(); diff --git a/modules/csg/doc_classes/CSGBox3D.xml b/modules/csg/doc_classes/CSGBox3D.xml index d64e58ae4d..4b479ed42e 100644 --- a/modules/csg/doc_classes/CSGBox3D.xml +++ b/modules/csg/doc_classes/CSGBox3D.xml @@ -12,7 +12,7 @@ <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the box. </member> - <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)"> + <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)"> The box's width, height and depth. </member> </members> diff --git a/modules/csg/doc_classes/CSGCylinder3D.xml b/modules/csg/doc_classes/CSGCylinder3D.xml index 40e989bfb3..1fe2025bab 100644 --- a/modules/csg/doc_classes/CSGCylinder3D.xml +++ b/modules/csg/doc_classes/CSGCylinder3D.xml @@ -12,13 +12,13 @@ <member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false"> If [code]true[/code] a cone is created, the [member radius] will only apply to one side. </member> - <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> + <member name="height" type="float" setter="set_height" getter="get_height" default="2.0"> The height of the cylinder. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the cylinder. </member> - <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> + <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5"> The radius of the cylinder. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides" default="8"> diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml index 2810343139..42fcb7bd2b 100644 --- a/modules/csg/doc_classes/CSGMesh3D.xml +++ b/modules/csg/doc_classes/CSGMesh3D.xml @@ -4,7 +4,7 @@ A CSG Mesh shape that uses a mesh resource. </brief_description> <description> - This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. + This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes. </description> <tutorials> </tutorials> diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index ecbb7962d1..5a49eebc7b 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -4,7 +4,7 @@ Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> - An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. + An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGMesh3D] for using 3D meshes as CSG nodes. </description> <tutorials> </tutorials> @@ -46,7 +46,8 @@ When [member mode] is [constant MODE_PATH], this is the distance along the path, in meters, the texture coordinates will tile. When set to 0, texture coordinates will match geometry exactly with no tiling. </member> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)"> - The point array that defines the 2D polygon that is extruded. + The point array that defines the 2D polygon that is extruded. This can be a convex or concave polygon with 3 or more points. The polygon must [i]not[/i] have any intersecting edges. Otherwise, triangulation will fail and no mesh will be generated. + [b]Note:[/b] If only 1 or 2 points are defined in [member polygon], no mesh will be generated. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false"> If [code]true[/code], applies smooth shading to the extrusions. diff --git a/modules/csg/doc_classes/CSGTorus3D.xml b/modules/csg/doc_classes/CSGTorus3D.xml index 91ee63a4c9..2c0eef8f09 100644 --- a/modules/csg/doc_classes/CSGTorus3D.xml +++ b/modules/csg/doc_classes/CSGTorus3D.xml @@ -9,13 +9,13 @@ <tutorials> </tutorials> <members> - <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="2.0"> + <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5"> The inner radius of the torus. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the torus. </member> - <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="3.0"> + <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0"> The outer radius of the torus. </member> <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6"> diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub index e56177d6e9..1d5a7ff6a3 100644 --- a/modules/cvtt/SCsub +++ b/modules/cvtt/SCsub @@ -11,7 +11,16 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/cvtt/" thirdparty_sources = [ - "ConvectionKernels.cpp", + "ConvectionKernels_API.cpp", + "ConvectionKernels_ETC.cpp", + "ConvectionKernels_BC67.cpp", + "ConvectionKernels_IndexSelector.cpp", + "ConvectionKernels_BC6H_IO.cpp", + "ConvectionKernels_S3TC.cpp", + "ConvectionKernels_BC7_PrioData.cpp", + "ConvectionKernels_SingleFile.cpp", + "ConvectionKernels_BCCommon.cpp", + "ConvectionKernels_Util.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 9e0579740b..d18340a2c8 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -41,7 +41,7 @@ struct CVTTCompressionJobParams { bool is_hdr = false; bool is_signed = false; int bytes_per_pixel = 0; - + cvtt::BC7EncodingPlan bc7_plan; cvtt::Options options; }; @@ -116,7 +116,7 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options); } } else { - cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options); + cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options, p_job_params.bc7_plan); } unsigned int num_real_blocks = ((w - x_start) + 3) / 4; @@ -141,7 +141,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) { return; //do not compress, already compressed } - int w = p_image->get_width(); int h = p_image->get_height(); @@ -153,22 +152,8 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann } cvtt::Options options; - uint32_t flags = cvtt::Flags::Fastest; - - if (p_lossy_quality > 0.85) { - flags = cvtt::Flags::Ultra; - } else if (p_lossy_quality > 0.75) { - flags = cvtt::Flags::Better; - } else if (p_lossy_quality > 0.55) { - flags = cvtt::Flags::Default; - } else if (p_lossy_quality > 0.35) { - flags = cvtt::Flags::Fast; - } else if (p_lossy_quality > 0.15) { - flags = cvtt::Flags::Faster; - } - + uint32_t flags = cvtt::Flags::Default; flags |= cvtt::Flags::BC7_RespectPunchThrough; - if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normal map flags |= cvtt::Flags::Uniform; } @@ -215,12 +200,15 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann job_queue.job_params.is_signed = is_signed; job_queue.job_params.options = options; job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4; + cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5); -#ifdef NO_THREADS int num_job_threads = 0; -#else - int num_job_threads = OS::get_singleton()->can_use_threads() ? (OS::get_singleton()->get_processor_count() - 1) : 0; -#endif + // Amdahl's law (Wikipedia) + // If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized, + // therefore only the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many threads are devoted + // to a parallelized execution of this program, the minimum execution time cannot be less than one hour. + // + // The number of executions with different inputs can be increased while the latency is the same. Vector<CVTTCompressionRowTask> tasks; @@ -278,7 +266,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann memdelete(threads_wb[i]); } } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } @@ -388,6 +375,5 @@ void image_decompress_cvtt(Image *p_image) { w >>= 1; h >>= 1; } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index c79d449d41..7d5557d197 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -132,8 +132,39 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua // Compress image data and (if required) mipmaps. const bool mipmaps = r_img->has_mipmaps(); - const int width = r_img->get_width(); - const int height = r_img->get_height(); + int width = r_img->get_width(); + int height = r_img->get_height(); + + /* + The first mipmap level of a compressed texture must be a multiple of 4. Quote from D3D11.3 spec: + + BC format surfaces are always multiples of full blocks, each block representing 4x4 pixels. + For mipmaps, the top level map is required to be a multiple of 4 size in all dimensions. + The sizes for the lower level maps are computed as they are for all mipmapped surfaces, + and thus may not be a multiple of 4, for example a top level map of 20 results in a second level + map size of 10. For these cases, there is a differing 'physical' size and a 'virtual' size. + The virtual size is that computed for each mip level without adjustment, which is 10 for the example. + The physical size is the virtual size rounded up to the next multiple of 4, which is 12 for the example, + and this represents the actual memory size. The sampling hardware will apply texture address + processing based on the virtual size (using, for example, border color if specified for accesses + beyond 10), and thus for the example case will not access the 11th and 12th row of the resource. + So for mipmap chains when an axis becomes < 4 in size, only texels 'a','b','e','f' + are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from, + the surface pitch, which can encompass additional padding beyond the physical surface size. + */ + int next_width = width <= 2 ? width : (width + 3) & ~3; + int next_height = height <= 2 ? height : (height + 3) & ~3; + if (next_width != width || next_height != height) { + r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS); + width = r_img->get_width(); + height = r_img->get_height(); + } + // ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed. + // Multiple-of-4 should be guaranteed by above. + // However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels, + // which are individually compressed Image objects that violate the above rule. + // Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4. + const uint8_t *src_read = r_img->get_data().ptr(); print_verbose(vformat("ETCPAK: Encoding image size %dx%d to format %s.", width, height, Image::get_format_name(target_format))); @@ -144,24 +175,48 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua uint8_t *dest_write = dest_data.ptrw(); int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + Vector<uint32_t> padded_src; for (int i = 0; i < mip_count + 1; i++) { // Get write mip metrics for target image. - int mip_w, mip_h; - int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, mip_w, mip_h); + int orig_mip_w, orig_mip_h; + int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). ERR_FAIL_COND(mip_ofs % 8 != 0); uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs]; // Block size. Align stride to multiple of 4 (RGBA8). - mip_w = (mip_w + 3) & ~3; - mip_h = (mip_h + 3) & ~3; + int mip_w = (orig_mip_w + 3) & ~3; + int mip_h = (orig_mip_h + 3) & ~3; const uint32_t blocks = mip_w * mip_h / 16; // Get mip data from source image for reading. int src_mip_ofs = r_img->get_mipmap_offset(i); const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; + // Pad textures to nearest block by smearing. + if (mip_w != orig_mip_w || mip_h != orig_mip_h) { + padded_src.resize(mip_w * mip_h); + uint32_t *ptrw = padded_src.ptrw(); + int x = 0, y = 0; + for (y = 0; y < orig_mip_h; y++) { + for (x = 0; x < orig_mip_w; x++) { + ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x]; + } + // First, smear in x. + for (; x < mip_w; x++) { + ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1]; + } + } + // Then, smear in y. + for (; y < mip_h; y++) { + for (x = 0; x < mip_w; x++) { + ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w]; + } + } + // Override the src_mip_read pointer to our temporary Vector. + src_mip_read = padded_src.ptr(); + } if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2 || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index 4cca907bf2..758c47eecc 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -123,7 +123,7 @@ Node3D *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_ bool corrupt = false; // safer to check this way as there can be different formatted headers - if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) { + if (fbx_header_string.contains("Kaydara FBX Binary")) { is_binary = true; print_verbose("[doc] is binary"); diff --git a/modules/fbx/fbx_parser/FBXAnimation.cpp b/modules/fbx/fbx_parser/FBXAnimation.cpp index 8c43aac8f6..8627c95012 100644 --- a/modules/fbx/fbx_parser/FBXAnimation.cpp +++ b/modules/fbx/fbx_parser/FBXAnimation.cpp @@ -130,7 +130,7 @@ AnimationCurve::~AnimationCurve() { AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc, const char *const *target_prop_whitelist /*= nullptr*/, size_t whitelist_size /*= 0*/) : - Object(id, element, name), target(), doc(doc) { + Object(id, element, name), doc(doc) { // find target node const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); diff --git a/modules/fbx/fbx_parser/FBXDeformer.cpp b/modules/fbx/fbx_parser/FBXDeformer.cpp index b888afd90e..a2b216ab09 100644 --- a/modules/fbx/fbx_parser/FBXDeformer.cpp +++ b/modules/fbx/fbx_parser/FBXDeformer.cpp @@ -104,7 +104,7 @@ Constraint::~Constraint() { // ------------------------------------------------------------------------------------------------ Cluster::Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name), valid_transformAssociateModel(false) { + Deformer(id, element, doc, name) { const ScopePtr sc = GetRequiredScope(element); // for( auto element : sc.Elements()) // { @@ -177,7 +177,7 @@ Cluster::~Cluster() { // ------------------------------------------------------------------------------------------------ Skin::Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name), accuracy(0.0f) { + Deformer(id, element, doc, name) { const ScopePtr sc = GetRequiredScope(element); // keep this it is used for debugging and any FBX format changes diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp index d8ccb4179c..e345b7fc18 100644 --- a/modules/fbx/fbx_parser/FBXParser.cpp +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -235,7 +235,7 @@ Scope::~Scope() { // ------------------------------------------------------------------------------------------------ Parser::Parser(const TokenList &tokens, bool is_binary) : - corrupt(false), tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { + tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { root = new_Scope(*this, true); scopes.push_back(root); } diff --git a/modules/fbx/fbx_parser/FBXProperties.cpp b/modules/fbx/fbx_parser/FBXProperties.cpp index 531f0743d6..7cbb3a2eda 100644 --- a/modules/fbx/fbx_parser/FBXProperties.cpp +++ b/modules/fbx/fbx_parser/FBXProperties.cpp @@ -145,8 +145,7 @@ std::string PeekPropertyName(const Element &element) { } // namespace // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable() : - element(nullptr) { +PropertyTable::PropertyTable() { } // Is used when dealing with FBX Objects not metadata. diff --git a/modules/gdnative/gdnative/packed_arrays.cpp b/modules/gdnative/gdnative/packed_arrays.cpp index bb6f0324a8..0c49694e0b 100644 --- a/modules/gdnative/gdnative/packed_arrays.cpp +++ b/modules/gdnative/gdnative/packed_arrays.cpp @@ -32,7 +32,7 @@ #include "core/variant/variant.h" -#include "core/math/vector2.h" +#include "core/math/vector2i.h" #include "core/math/vector3i.h" static_assert(sizeof(godot_packed_byte_array) == sizeof(PackedByteArray), "PackedByteArray size mismatch"); diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index f4674850e3..7e0ce76c26 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -31,6 +31,8 @@ #include "gdnative/rect2.h" #include "core/math/rect2.h" +#include "core/math/rect2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_rect2) == sizeof(Rect2), "Rect2 size mismatch"); static_assert(sizeof(godot_rect2i) == sizeof(Rect2i), "Rect2i size mismatch"); diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp index 45ba790dc1..7dc07024e5 100644 --- a/modules/gdnative/gdnative/transform2d.cpp +++ b/modules/gdnative/gdnative/transform2d.cpp @@ -31,6 +31,7 @@ #include "gdnative/transform2d.h" #include "core/math/transform_2d.h" +#include "core/os/memory.h" static_assert(sizeof(godot_transform2d) == sizeof(Transform2D), "Transform2D size mismatch"); diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp index eb8ffd74cd..a8d4281d25 100644 --- a/modules/gdnative/gdnative/vector2.cpp +++ b/modules/gdnative/gdnative/vector2.cpp @@ -31,6 +31,8 @@ #include "gdnative/vector2.h" #include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_vector2) == sizeof(Vector2), "Vector2 size mismatch"); static_assert(sizeof(godot_vector2i) == sizeof(Vector2i), "Vector2i size mismatch"); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index eea898475b..879291c2e0 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -79,6 +79,7 @@ typedef enum { GODOT_PROPERTY_HINT_PROPERTY_OF_BASE_TYPE, ///< a property of a base type GODOT_PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance GODOT_PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base + GODOT_PROPERTY_HINT_LOCALE_ID, GODOT_PROPERTY_HINT_MAX, } godot_nativescript_property_hint; diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index ddde28811c..0309d1d9c7 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -288,7 +288,7 @@ List<ClassAPI> generate_c_api_classes() { String type; String name = argument.name; - if (argument.name.find(":") != -1) { + if (argument.name.contains(":")) { type = argument.name.get_slice(":", 1); name = argument.name.get_slice(":", 0); } else { @@ -324,7 +324,7 @@ List<ClassAPI> generate_c_api_classes() { property_api.getter = ClassDB::get_property_getter(class_name, p->get().name); property_api.setter = ClassDB::get_property_setter(class_name, p->get().name); - if (p->get().name.find(":") != -1) { + if (p->get().name.contains(":")) { property_api.type = p->get().name.get_slice(":", 1); property_api.name = p->get().name.get_slice(":", 0); } else { @@ -355,7 +355,7 @@ List<ClassAPI> generate_c_api_classes() { //method name method_api.method_name = method_info.name; //method return type - if (method_api.method_name.find(":") != -1) { + if (method_api.method_name.contains(":")) { method_api.return_type = method_api.method_name.get_slice(":", 1); method_api.method_name = method_api.method_name.get_slice(":", 0); } else { @@ -388,7 +388,7 @@ List<ClassAPI> generate_c_api_classes() { arg_name = arg_info.name; - if (arg_info.name.find(":") != -1) { + if (arg_info.name.contains(":")) { arg_type = arg_info.name.get_slice(":", 1); arg_name = arg_info.name.get_slice(":", 0); } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5cc295bbab..ac6684a29c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -33,18 +33,6 @@ #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -static bool _is_hex_symbol(char32_t c) { - return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -static bool _is_bin_symbol(char32_t c) { - return (c == '0' || c == '1'); -} - Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; @@ -102,7 +90,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); - bool is_number = (str[j] >= '0' && str[j] <= '9'); + bool is_number = is_digit(str[j]); /* color regions */ if (is_a_symbol || in_region != -1) { @@ -241,14 +229,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } // allow ABCDEF in hex notation - if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { + if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) { is_number = true; } else { is_hex_notation = false; } // disallow anything not a 0 or 1 - if (is_bin_notation && (_is_bin_symbol(str[j]))) { + if (is_bin_notation && (is_binary_digit(str[j]))) { is_number = true; } else if (is_bin_notation) { is_bin_notation = false; @@ -270,7 +258,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } } - if (!in_word && _is_char(str[j]) && !is_number) { + if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } @@ -585,7 +573,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (name.find("/") != -1) { + if (name.contains("/")) { continue; } member_keywords[name] = member_variable_color; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 84db97625b..a80874d785 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -827,7 +827,7 @@ Error GDScript::reload(bool p_keep_state) { return OK; } #else - if (source.find("_BASE_") != -1) { + if (source.contains("_BASE_")) { return OK; } #endif @@ -2022,8 +2022,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "preload", "signal", "super", - "trait", - "yield", // var "const", "enum", @@ -2040,6 +2038,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "return", "match", "while", + // These keywords are not implemented currently, but reserved for (potential) future use. + // We highlight them as keywords to make errors easier to understand. + "trait", + "namespace", + "yield", nullptr }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3a79190149..9ff52347e9 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; - type.builtin_type = Variant::OBJECT; + type.builtin_type = Variant::INT; type.is_constant = true; type.is_meta_type = true; @@ -650,9 +650,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype = specified_type; if (member.variable->initializer != nullptr) { - if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { + if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); } else { // TODO: Add warning. @@ -1400,9 +1400,9 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable type.is_meta_type = false; if (p_variable->initializer != nullptr) { - if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { + if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); } else { // TODO: Add warning. @@ -1586,7 +1586,7 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc if (p_match_pattern->dictionary[i].key) { reduce_expression(p_match_pattern->dictionary[i].key); if (!p_match_pattern->dictionary[i].key->is_constant) { - push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression); + push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->dictionary[i].key); } } @@ -1877,11 +1877,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) { if (compatible) { - compatible = is_type_compatible(assignee_type, op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); if (!compatible) { if (assignee_type.is_hard_type()) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, assignee_type, true)) { + if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. @@ -2416,6 +2416,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { + // Enum type is treated as a dictionary value for function calls. + base_type.is_meta_type = false; + } + if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); } else if (!is_self && base_type.is_meta_type && !is_static) { @@ -2474,17 +2479,24 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); if (!cast_type.is_set()) { + mark_node_unsafe(p_cast); return; } - cast_type.is_meta_type = false; // The casted value won't be a type name. + cast_type = type_from_metatype(cast_type); // The casted value won't be a type name. p_cast->set_datatype(cast_type); if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!op_type.is_variant()) { bool valid = false; - if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { + if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are compatible between each other, so it's a safe cast. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Convertint int to enum is always valid. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); @@ -2586,6 +2598,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod const StringName &name = p_identifier->name; + if (base.kind == GDScriptParser::DataType::ENUM) { + if (base.is_meta_type) { + if (base.enum_values.has(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = base.enum_values[name]; + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::ENUM; + result.is_constant = true; + result.builtin_type = Variant::INT; + result.native_type = base.native_type; + result.enum_type = base.enum_type; + p_identifier->set_datatype(result); + return; + } else { + // Consider as a Dictionary, so it can be anything. + // This will be evaluated in the next if block. + base.kind = GDScriptParser::DataType::BUILTIN; + base.builtin_type = Variant::DICTIONARY; + base.is_meta_type = false; + } + } else { + push_error(R"(Cannot get property from enum value.)", p_identifier); + return; + } + } + if (base.kind == GDScriptParser::DataType::BUILTIN) { if (base.is_meta_type) { bool valid = true; @@ -2632,31 +2672,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } - if (base.kind == GDScriptParser::DataType::ENUM) { - if (base.is_meta_type) { - if (base.enum_values.has(name)) { - p_identifier->is_constant = true; - p_identifier->reduced_value = base.enum_values[name]; - - GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::ENUM_VALUE; - result.builtin_type = base.builtin_type; - result.native_type = base.native_type; - result.enum_type = name; - p_identifier->set_datatype(result); - } else { - // Consider as a Dictionary - GDScriptParser::DataType dummy; - dummy.kind = GDScriptParser::DataType::VARIANT; - p_identifier->set_datatype(dummy); - } - } else { - push_error(R"(Cannot get property from enum value.)", p_identifier); - } - return; - } - GDScriptParser::ClassNode *base_class = base.class_type; // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). @@ -2792,7 +2807,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (element.identifier->name == p_identifier->name) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN; type.builtin_type = Variant::INT; type.is_constant = true; if (element.parent_enum->identifier) { @@ -3492,6 +3507,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; result.is_constant = false; + if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { + result.builtin_type = Variant::INT; + } return result; } @@ -3548,6 +3566,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source r_default_arg_count = 0; StringName function_name = p_function; + if (p_base_type.kind == GDScriptParser::DataType::ENUM) { + if (p_base_type.is_meta_type) { + // Enum type can be treated as a dictionary value. + p_base_type.kind = GDScriptParser::DataType::BUILTIN; + p_base_type.builtin_type = Variant::DICTIONARY; + p_base_type.is_meta_type = false; + } else { + push_error("Cannot call function on enum value.", p_source); + return false; + } + } + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. Callable::CallError err; @@ -3798,6 +3828,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; + + if (p_a.kind == GDScriptParser::DataType::ENUM) { + if (p_a.is_meta_type) { + a_type = Variant::DICTIONARY; + } else { + a_type = Variant::INT; + } + } + if (p_b.kind == GDScriptParser::DataType::ENUM) { + if (p_b.is_meta_type) { + b_type = Variant::DICTIONARY; + } else { + b_type = Variant::INT; + } + } + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); @@ -3827,7 +3873,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // TODO: Add safe/unsafe return variable (for variant cases) -bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const { +bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -3847,7 +3893,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } - if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM && !p_source.is_meta_type) { // Enum value is also integer. valid = true; } @@ -3868,6 +3914,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM); + } +#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { @@ -3875,11 +3926,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ return true; } } - if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { - if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { - return true; - } - } return false; } @@ -3934,7 +3980,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -3971,7 +4016,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4cee5cb44a..2697a6ec2b 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -112,7 +112,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); - bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); bool class_exists(const StringName &p_class) const; diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 8623122edc..82aa14795e 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -688,6 +688,7 @@ void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) void GDScriptByteCodeGenerator::write_end_ternary() { patch_jump(ternary_jump_skip_pos.back()->get()); ternary_jump_skip_pos.pop_back(); + ternary_result.pop_back(); } void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) { diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 4ac5a4a60e..6ada7d36f5 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -122,6 +122,10 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP } if (singleton->parser_map.has(p_path)) { ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); + if (ref.is_null()) { + r_error = ERR_INVALID_DATA; + return ref; + } } else { if (!FileAccess::exists(p_path)) { r_error = ERR_FILE_NOT_FOUND; @@ -133,7 +137,6 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP ref->path = p_path; singleton->parser_map[p_path] = ref.ptr(); } - r_error = ref->raise_status(p_status); return ref; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 117ca68c18..108c988add 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -141,10 +141,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; - result.builtin_type = Variant::INT; + if (p_datatype.is_meta_type) { + result.builtin_type = Variant::DICTIONARY; + } else { + result.builtin_type = Variant::INT; + } break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); @@ -469,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); + GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + + if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. + cast_type.kind = GDScriptDataType::BUILTIN; + cast_type.builtin_type = Variant::INT; + } // Create temporary for result first since it will be deleted last. GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); @@ -2019,7 +2029,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; - GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true); + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value); if (r_error) { memdelete(codegen.generator); return nullptr; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index d10e120410..33a88dd2dd 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -486,7 +486,7 @@ struct GDScriptCompletionIdentifier { static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; - if (enum_name.find(".") == -1) { + if (!enum_name.contains(".")) { return enum_name; } return enum_name.get_slice(".", 1); @@ -610,7 +610,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value); if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { def_val = sub->get_datatype().to_string(); } else if (sub->reduced) { const Variant::Type vt = sub->reduced_value.get_type(); @@ -955,7 +955,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { continue; } - if (E.name.find("/") != -1) { + if (E.name.contains("/")) { continue; } ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); @@ -1000,7 +1000,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } for (const PropertyInfo &E : members) { - if (String(E.name).find("/") == -1) { + if (!String(E.name).contains("/")) { ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } @@ -2182,7 +2182,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_enum_hint.find(".") == -1) { + if (!p_enum_hint.contains(".")) { // Global constant or in the current class. StringName current_enum = p_enum_hint; if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { @@ -2291,7 +2291,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) { + if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().contains("action")) { // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2639,7 +2639,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c ClassDB::get_virtual_methods(class_name, &virtual_methods); for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; - if (method_hint.find(":") != -1) { + if (method_hint.contains(":")) { method_hint = method_hint.get_slice(":", 0); } method_hint += "("; @@ -2650,7 +2650,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c method_hint += ", "; } String arg = mi.arguments[i].name; - if (arg.find(":") != -1) { + if (arg.contains(":")) { arg = arg.substr(0, arg.find(":")); } method_hint += arg; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index f1877df326..9424de9d22 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -248,7 +248,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { // If the return value is a GDScriptFunctionState reference, // then the function did await again after resuming. - if (ret.is_ref()) { + if (ret.is_ref_counted()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { completed = false; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 10f1dd0a41..cfad832a6c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2105,7 +2105,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { - if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) { + if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL)) { return previous_operand; } // Also switch multiline mode on here for infix operators. @@ -2415,6 +2415,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode push_error("Assignment is not allowed inside an expression."); return parse_expression(false); // Return the following expression. } + if (p_previous_operand == nullptr) { + return parse_expression(false); // Return the following expression. + } #ifdef DEBUG_ENABLED VariableNode *source_variable = nullptr; @@ -3737,8 +3740,6 @@ String GDScriptParser::DataType::to_string() const { } case ENUM: return enum_type.operator String() + " (enum)"; - case ENUM_VALUE: - return enum_type.operator String() + " (enum value)"; case UNRESOLVED: return "<unresolved type>"; } @@ -4236,7 +4237,11 @@ void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) { } void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { - push_text(p_identifier->name); + if (p_identifier != nullptr) { + push_text(p_identifier->name); + } else { + push_text("<invalid identifier>"); + } } void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index e4311d2d5e..c09b07282f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -106,8 +106,7 @@ public: NATIVE, SCRIPT, CLASS, // GDScript. - ENUM, // Full enumeration. - ENUM_VALUE, // Value from enumeration. + ENUM, // Enumeration. VARIANT, // Can be any type. UNRESOLVED, }; @@ -185,8 +184,6 @@ public: return builtin_type == p_other.builtin_type; case NATIVE: case ENUM: - return native_type == p_other.native_type; - case ENUM_VALUE: return native_type == p_other.native_type && enum_type == p_other.enum_type; case SCRIPT: return script_type == p_other.script_type; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 05ea061798..d3287ab345 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -312,22 +312,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { return error; } -static bool _is_alphanumeric(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} - -static bool _is_digit(char32_t c) { - return (c >= '0' && c <= '9'); -} - -static bool _is_hex_digit(char32_t c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -static bool _is_binary_digit(char32_t c) { - return (c == '0' || c == '1'); -} - GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { Token token(p_type); token.start_line = start_line; @@ -448,10 +432,10 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To } GDScriptTokenizer::Token GDScriptTokenizer::annotation() { - if (!_is_alphanumeric(_peek())) { + if (!is_ascii_identifier_char(_peek())) { push_error("Expected annotation identifier after \"@\"."); } - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { // Consume all identifier characters. _advance(); } @@ -526,7 +510,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #define MAX_KEYWORD_LENGTH 10 // Consume all alphanumeric characters. - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { _advance(); } @@ -612,7 +596,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; - bool (*digit_check_func)(char32_t) = _is_digit; + bool (*digit_check_func)(char32_t) = is_digit; if (_peek(-1) == '.') { has_decimal = true; @@ -620,20 +604,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { if (_peek() == 'x') { // Hexadecimal. base = 16; - digit_check_func = _is_hex_digit; + digit_check_func = is_hex_digit; _advance(); } else if (_peek() == 'b') { // Binary. base = 2; - digit_check_func = _is_binary_digit; + digit_check_func = is_binary_digit; _advance(); } } // Allow '_' to be used in a number, for readability. bool previous_was_underscore = false; - while (digit_check_func(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (digit_check_func(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -682,7 +666,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); // Consume decimal digits. - while (_is_digit(_peek()) || _peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { _advance(); } } @@ -696,7 +680,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); } // Consume exponent digits. - if (!_is_digit(_peek())) { + if (!is_digit(_peek())) { Token error = make_error(R"(Expected exponent value after "e".)"); error.start_column = column; error.leftmost_column = column; @@ -705,8 +689,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { push_error(error); } previous_was_underscore = false; - while (_is_digit(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -733,7 +717,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { error.rightmost_column = column + 1; push_error(error); has_error = true; - } else if (_is_alphanumeric(_peek())) { + } else if (is_ascii_identifier_char(_peek())) { // Letter at the end of the number. push_error("Invalid numeric notation."); } @@ -786,6 +770,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } String result; + char32_t prev = 0; + int prev_pos = 0; for (;;) { // Consume actual string. @@ -852,16 +838,18 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { case '\\': escaped = '\\'; break; - case 'u': + case 'U': + case 'u': { // Hexadecimal sequence. - for (int i = 0; i < 4; i++) { + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { if (_is_at_end()) { return make_error("Unterminated string."); } char32_t digit = _peek(); char32_t value = 0; - if (digit >= '0' && digit <= '9') { + if (is_digit(digit)) { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { value = digit - 'a'; @@ -886,7 +874,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - break; + } break; case '\r': if (_peek() != '\n') { // Carriage return without newline in string. (???) @@ -909,11 +897,53 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { valid_escape = false; break; } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } + } if (valid_escape) { result += escaped; } } else if (ch == quote_char) { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } _advance(); if (is_multiline) { if (_peek() == quote_char && _peek(1) == quote_char) { @@ -930,6 +960,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { break; } } else { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } result += ch; _advance(); if (ch == '\n') { @@ -937,6 +974,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } } } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } // Make the literal. Variant string; @@ -1262,9 +1306,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { line_continuation = false; - if (_is_digit(c)) { + if (is_digit(c)) { return number(); - } else if (_is_alphanumeric(c)) { + } else if (is_ascii_identifier_char(c)) { return potential_identifier(); } @@ -1332,7 +1376,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (_peek() == '.') { _advance(); return make_token(Token::PERIOD_PERIOD); - } else if (_is_digit(_peek())) { + } else if (is_digit(_peek())) { // Number starting with '.'. return number(); } else { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index e0facaf61d..95122714f9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -1532,7 +1532,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - if (base->is_ref()) { + if (base->is_ref_counted()) { err_text = "Attempted to free a reference."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { @@ -1620,7 +1620,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - if (base->is_ref()) { + if (base->is_ref_counted()) { err_text = "Attempted to free a reference."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 73536f5f8e..ad96e36640 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -152,6 +152,9 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(3); return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); } + case INT_ASSIGNED_TO_ENUM: { + return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -199,6 +202,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "REDUNDANT_AWAIT", "EMPTY_FILE", "SHADOWED_GLOBAL_IDENTIFIER", + "INT_ASSIGNED_TO_ENUM", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 112b40781a..82efe3568f 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -70,6 +70,7 @@ public: REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). EMPTY_FILE, // A script file is empty. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. + INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting. WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 49f5303ae6..17886181d5 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -541,7 +541,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & for (int c = p_position.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } @@ -550,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index a944844226..d20b243616 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -63,7 +63,7 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr String function_signature = "func " + function; String source = script->get_source_code(); - if (source.find(function_signature) != -1) { + if (source.contains(function_signature)) { return; } @@ -269,7 +269,7 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { Vector<lsp::DocumentedSymbolInformation> script_symbols; E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { - if (query.is_subsequence_ofi(script_symbols[i].name)) { + if (query.is_subsequence_ofn(script_symbols[i].name)) { lsp::DocumentedSymbolInformation symbol = script_symbols[i]; symbol.location.uri = get_file_uri(symbol.location.uri); arr.push_back(symbol.to_json()); @@ -380,7 +380,7 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } - if (data.qualifiers.find("vararg") != -1) { + if (data.qualifiers.contains("vararg")) { params += params.is_empty() ? "..." : ", ..."; } @@ -585,7 +585,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S stack.push_back(owner_scene_node); while (!stack.is_empty()) { - current = stack.pop_back(); + current = Object::cast_to<Node>(stack.pop_back()); Ref<GDScript> script = current->get_script(); if (script.is_valid() && script->get_path() == path) { break; diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 8e503a9df9..a63f9df918 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -1940,7 +1940,7 @@ static String marked_documentation(const String &p_bbcode) { line = "\t" + line.substr(code_block_indent, line.length()); } - if (in_code_block && line.find("[/codeblock]") != -1) { + if (in_code_block && line.contains("[/codeblock]")) { line = "\n"; in_code_block = false; } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 47772b8039..73a424dae4 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -134,12 +134,14 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l if (do_init_languages) { init_language(p_source_dir); } +#ifdef DEBUG_ENABLED // Enable all warnings for GDScript, so we can test them. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); } +#endif // Enable printing to show results _print_line_enabled = true; @@ -153,6 +155,21 @@ GDScriptTestRunner::~GDScriptTestRunner() { } } +#ifndef DEBUG_ENABLED +static String strip_warnings(const String &p_expected) { + // On release builds we don't have warnings. Here we remove them from the output before comparison + // so it doesn't fail just because of difference in warnings. + String expected_no_warnings; + for (String line : p_expected.split("\n")) { + if (line.begins_with(">> ")) { + continue; + } + expected_no_warnings += line + "\n"; + } + return expected_no_warnings.strip_edges() + "\n"; +} +#endif + int GDScriptTestRunner::run_tests() { if (!make_tests()) { FAIL("An error occurred while making the tests."); @@ -170,6 +187,9 @@ int GDScriptTestRunner::run_tests() { GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif INFO(test.get_source_file()); if (!result.passed) { INFO(expected); @@ -233,6 +253,22 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { } } else { if (next.get_extension().to_lower() == "gd") { +#ifndef DEBUG_ENABLED + // On release builds, skip tests marked as debug only. + Error open_err = OK; + FileAccessRef script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err)); + if (open_err != OK) { + ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next)); + next = dir->get_next(); + continue; + } else { + if (script_file->get_line() == "#debug-only") { + next = dir->get_next(); + continue; + } + } +#endif + String out_file = next.get_basename() + ".out"; if (!is_generating && !dir->file_exists(out_file)) { ERR_FAIL_V_MSG(false, "Could not find output file for " + next); @@ -387,6 +423,10 @@ bool GDScriptTest::check_output(const String &p_output) const { String got = p_output.strip_edges(); // TODO: may be hacky. got += "\n"; // Make sure to insert newline for CI static checks. +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif + return got == expected; } @@ -469,6 +509,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { return result; } +#ifdef DEBUG_ENABLED StringBuilder warning_string; for (const GDScriptWarning &E : parser.get_warnings()) { const GDScriptWarning warning = E; @@ -482,6 +523,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { warning_string.append("\n"); } result.output += warning_string.as_string(); +#endif // Test compiling. GDScriptCompiler compiler; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..928c886650 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,10 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..03a1711d7b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + +func test(): + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..d08d3dd7b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..ca6d892218 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd @@ -0,0 +1,6 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd new file mode 100644 index 0000000000..05d9bd6a3d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/56702 + +func test(): + # somewhat obscure feature: referencing parameters in defaults, but only earlier ones! + ref_default("non-optional") + + +func ref_default(nondefault1, defa=nondefault1, defb=defc, defc=1): + prints(nondefault1, nondefault2, defa, defb, defc) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out new file mode 100644 index 0000000000..1d5b5bf393 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/params_default_forward_reference.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "defc" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd new file mode 100644 index 0000000000..edb785c8b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: int = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: int = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd new file mode 100644 index 0000000000..726e4fd413 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = 0 as MyEnum + +func test(): + print(class_var) + class_var = 1 as MyEnum + print(class_var) + + var local_var: MyEnum = 0 as MyEnum + print(local_var) + local_var = 1 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd new file mode 100644 index 0000000000..798912c987 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd @@ -0,0 +1,14 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(class_var) + + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd new file mode 100644 index 0000000000..2bfb318c3c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd new file mode 100644 index 0000000000..7022d14566 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd @@ -0,0 +1,21 @@ +# Enum is equivalent to int for comparisons and operations. +enum MyEnum { + ZERO, + ONE, + TWO, +} + +enum OtherEnum { + ZERO, + ONE, + TWO, +} + +func test(): + print(MyEnum.ZERO == OtherEnum.ZERO) + print(MyEnum.ZERO == 1) + print(MyEnum.ZERO != OtherEnum.ONE) + print(MyEnum.ZERO != 0) + + print(MyEnum.ONE + OtherEnum.TWO) + print(2 - MyEnum.ONE) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out new file mode 100644 index 0000000000..c8f34c11db --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +true +false +3 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd new file mode 100644 index 0000000000..885d70408a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd @@ -0,0 +1,13 @@ +enum MyEnum { + ZERO, + ONE, + TWO, +} + +func test(): + for key in MyEnum.keys(): + prints(key, MyEnum[key]) + + # https://github.com/godotengine/godot/issues/55491 + for key in MyEnum: + prints(key, MyEnum[key]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out new file mode 100644 index 0000000000..d29f53109c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out @@ -0,0 +1,7 @@ +GDTEST_OK +ZERO 0 +ONE 1 +TWO 2 +ZERO 0 +ONE 1 +TWO 2 diff --git a/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd new file mode 100644 index 0000000000..e9690ee93d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd @@ -0,0 +1,2 @@ +func test(): + $=$ diff --git a/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out index 447d7e223c..5b0ea9df43 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -1,2 +1,2 @@ GDTEST_OK -{2:4, a:1, b:2, with spaces:3} +{a:1, b:2, with spaces:3, 2:4} diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd new file mode 100644 index 0000000000..2be1024214 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd @@ -0,0 +1,15 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +# Assigning int value to enum-typed variable without explicit cast causes a warning. +# While it is valid it may be a mistake in the assignment. +var class_var: MyEnum = 0 + +func test(): + print(class_var) + class_var = 1 + print(class_var) + + var local_var: MyEnum = 0 + print(local_var) + local_var = 1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out new file mode 100644 index 0000000000..eef13bbff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 9 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 12 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 14 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd index 10780b5379..7b3c112fe9 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd @@ -1,3 +1,4 @@ +#debug-only func test(): var node := Node.new() var inside_tree = node.is_inside_tree diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out index e585c374e2..fe48ade26b 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out @@ -2,5 +2,5 @@ GDTEST_RUNTIME_ERROR >> SCRIPT ERROR >> on function: test() >> runtime/errors/callable_call_after_free_object.gd ->> 5 +>> 6 >> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance. diff --git a/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd b/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd new file mode 100644 index 0000000000..8156b4ec68 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/params_default_values.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/56702 + +func test(): + const_default() + func_result_default() + # calling again will run the initializer again, + # as the default is not evaluated at time of defining the function (as in python) + # but every time the function is called (as in C++) + func_result_default() + lots_of_defaults("non-optional") + # somewhat obscure feature: referencing earlier parameters + ref_default("non-optional", 42) + + +func const_default(param=42): + print(param) + + +var default_val := 0 + +func get_default(): + default_val += 1 + return default_val + + +func func_result_default(param=get_default()): + print(param) + + +func lots_of_defaults(nondefault, one=1, two=2, three=get_default()): + prints(nondefault, one, two, three) + + +func ref_default(nondefault1, nondefault2, defa=nondefault1, defb=nondefault2 - 1): + prints(nondefault1, nondefault2, defa, defb) diff --git a/modules/gdscript/tests/scripts/runtime/features/params_default_values.out b/modules/gdscript/tests/scripts/runtime/features/params_default_values.out new file mode 100644 index 0000000000..50e0885ae5 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/params_default_values.out @@ -0,0 +1,6 @@ +GDTEST_OK +42 +1 +2 +non-optional 1 2 3 +non-optional 42 non-optional 41 diff --git a/modules/glslang/glslang_resource_limits.h b/modules/glslang/glslang_resource_limits.h index 05390f95ad..02d3daff07 100644 --- a/modules/glslang/glslang_resource_limits.h +++ b/modules/glslang/glslang_resource_limits.h @@ -132,15 +132,15 @@ const TBuiltInResource DefaultTBuiltInResource = { /* .maxDualSourceDrawBuffersEXT = */ 1, /* .limits = */ { - /* .nonInductiveForLoops = */ 1, - /* .whileLoops = */ 1, - /* .doWhileLoops = */ 1, - /* .generalUniformIndexing = */ 1, - /* .generalAttributeMatrixVectorIndexing = */ 1, - /* .generalVaryingIndexing = */ 1, - /* .generalSamplerIndexing = */ 1, - /* .generalVariableIndexing = */ 1, - /* .generalConstantMatrixVectorIndexing = */ 1, + /* .nonInductiveForLoops = */ true, + /* .whileLoops = */ true, + /* .doWhileLoops = */ true, + /* .generalUniformIndexing = */ true, + /* .generalAttributeMatrixVectorIndexing = */ true, + /* .generalVaryingIndexing = */ true, + /* .generalSamplerIndexing = */ true, + /* .generalVariableIndexing = */ true, + /* .generalConstantMatrixVectorIndexing = */ true, } }; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 5a931ed839..baa39a3b80 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -50,6 +50,7 @@ #include "core/io/file_access.h" #include "core/io/file_access_memory.h" #include "core/io/json.h" +#include "core/io/stream_peer.h" #include "core/math/disjoint_set.h" #include "core/math/vector2.h" #include "core/variant/dictionary.h" @@ -6277,7 +6278,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state p_track.rotation_track.values.write[key_i] = rotation; } } else if (track_type == Animation::TYPE_VALUE) { - if (path.find(":position") != -1) { + if (path.contains(":position")) { p_track.position_track.times = times; p_track.position_track.interpolation = gltf_interpolation; @@ -6288,7 +6289,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 position = p_animation->track_get_key_value(p_track_i, key_i); p_track.position_track.values.write[key_i] = position; } - } else if (path.find(":rotation") != -1) { + } else if (path.contains(":rotation")) { p_track.rotation_track.times = times; p_track.rotation_track.interpolation = gltf_interpolation; @@ -6299,7 +6300,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i); p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian); } - } else if (path.find(":scale") != -1) { + } else if (path.contains(":scale")) { p_track.scale_track.times = times; p_track.scale_track.interpolation = gltf_interpolation; @@ -6312,7 +6313,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state } } } else if (track_type == Animation::TYPE_BEZIER) { - if (path.find("/scale") != -1) { + if (path.contains("/scale")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.scale_track.times.size()) { Vector<real_t> new_times; @@ -6333,16 +6334,16 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.scale_track.values[key_i]; - if (path.find("/scale:x") != -1) { + if (path.contains("/scale:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:y") != -1) { + } else if (path.contains("/scale:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:z") != -1) { + } else if (path.contains("/scale:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.scale_track.values.write[key_i] = bezier_track; } - } else if (path.find("/position") != -1) { + } else if (path.contains("/position")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.position_track.times.size()) { Vector<real_t> new_times; @@ -6359,11 +6360,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.position_track.values[key_i]; - if (path.find("/position:x") != -1) { + if (path.contains("/position:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:y") != -1) { + } else if (path.contains("/position:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:z") != -1) { + } else if (path.contains("/position:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.position_track.values.write[key_i] = bezier_track; @@ -6384,7 +6385,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, continue; } String orig_track_path = animation->track_get_path(track_i); - if (String(orig_track_path).find(":position") != -1) { + if (String(orig_track_path).contains(":position")) { const Vector<String> node_suffix = String(orig_track_path).split(":position"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6400,7 +6401,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":rotation_degrees") != -1) { + } else if (String(orig_track_path).contains(":rotation_degrees")) { const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6416,7 +6417,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":scale") != -1) { + } else if (String(orig_track_path).contains(":scale")) { const Vector<String> node_suffix = String(orig_track_path).split(":scale"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6432,7 +6433,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":transform") != -1) { + } else if (String(orig_track_path).contains(":transform")) { const Vector<String> node_suffix = String(orig_track_path).split(":transform"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6443,7 +6444,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(transform_track_i.key, track); } } - } else if (String(orig_track_path).find(":blend_shapes/") != -1) { + } else if (String(orig_track_path).contains(":blend_shapes/")) { const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/"); const NodePath path = node_suffix[0]; const String suffix = node_suffix[1]; @@ -6499,7 +6500,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, } tracks[mesh_index] = track; } - } else if (String(orig_track_path).find(":") != -1) { + } else if (String(orig_track_path).contains(":")) { //Process skeleton const Vector<String> node_suffix = String(orig_track_path).split(":"); const String node = node_suffix[0]; @@ -6529,7 +6530,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks()[node_i] = track; } } - } else if (String(orig_track_path).find(":") == -1) { + } else if (!String(orig_track_path).contains(":")) { ERR_CONTINUE(!ap->get_parent()); Node *godot_node = ap->get_parent()->get_node_or_null(orig_track_path); for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : state->scene_nodes) { @@ -6843,6 +6844,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> state, const String &p_pa } Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { + ERR_FAIL_NULL_V(state, nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); GLTFNodeIndex gltf_root = state->root_nodes.write[0]; Node *gltf_root_node = state->get_scene_node(gltf_root); diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 885817caf1..9c28421a46 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -67,7 +67,7 @@ Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [code]layer_number[/code] between 1 and 32. </description> </method> - <method name="get_meshes"> + <method name="get_meshes" qualifiers="const"> <return type="Array" /> <description> Returns an array of [Transform3D] and [Mesh] references corresponding to the non-empty cells in the grid. The transforms are specified in world space. diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index a861efcbf4..67deedf839 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -35,6 +35,7 @@ #include "scene/3d/light_3d.h" #include "scene/resources/mesh_library.h" #include "scene/resources/physics_material.h" +#include "scene/resources/primitive_meshes.h" #include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" #include "servers/navigation_server_3d.h" @@ -197,6 +198,24 @@ bool GridMap::get_collision_mask_value(int p_layer_number) const { return get_collision_mask() & (1 << (p_layer_number - 1)); } +Array GridMap::get_collision_shapes() const { + Array shapes; + for (const KeyValue<OctantKey, Octant *> &E : octant_map) { + Octant *g = E.value; + RID body = g->static_body; + Transform3D body_xform = PhysicsServer3D::get_singleton()->body_get_state(body, PhysicsServer3D::BODY_STATE_TRANSFORM); + int nshapes = PhysicsServer3D::get_singleton()->body_get_shape_count(body); + for (int i = 0; i < nshapes; i++) { + RID shape = PhysicsServer3D::get_singleton()->body_get_shape(body, i); + Transform3D xform = PhysicsServer3D::get_singleton()->body_get_shape_transform(body, i); + shapes.push_back(body_xform * xform); + shapes.push_back(shape); + } + } + + return shapes; +} + void GridMap::set_bake_navigation(bool p_bake_navigation) { bake_navigation = p_bake_navigation; _recreate_octant_data(); @@ -930,7 +949,7 @@ Array GridMap::get_used_cells() const { return a; } -Array GridMap::get_meshes() { +Array GridMap::get_meshes() const { if (mesh_library.is_null()) { return Array(); } @@ -938,7 +957,7 @@ Array GridMap::get_meshes() { Vector3 ofs = _get_offset(); Array meshes; - for (KeyValue<IndexKey, Cell> &E : cell_map) { + for (const KeyValue<IndexKey, Cell> &E : cell_map) { int id = E.value.item; if (!mesh_library->has_item(id)) { continue; diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 546b530148..6cdc3b178d 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -229,6 +229,8 @@ public: void set_physics_material(Ref<PhysicsMaterial> p_material); Ref<PhysicsMaterial> get_physics_material() const; + Array get_collision_shapes() const; + void set_bake_navigation(bool p_bake_navigation); bool is_baking_navigation(); @@ -265,7 +267,7 @@ public: Array get_used_cells() const; - Array get_meshes(); + Array get_meshes() const; void clear_baked_meshes(); void make_baked_meshes(bool p_gen_lightmap_uv = false, float p_lightmap_uv_texel_size = 0.1); diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 5ef48f8645..84510fc71e 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -889,7 +889,7 @@ void GridMapEditor::update_palette() { name = "#" + itos(id); } - if (!filter.is_empty() && !filter.is_subsequence_ofi(name)) { + if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) { continue; } @@ -1459,10 +1459,10 @@ void GridMapEditorPlugin::_notification(int p_what) { if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { case 0: { // Left. - Node3DEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 0); + Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor); } break; case 1: { // Right. - Node3DEditor::get_singleton()->get_palette_split()->move_child(grid_map_editor, 1); + Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor); } break; } } @@ -1498,10 +1498,10 @@ GridMapEditorPlugin::GridMapEditorPlugin(EditorNode *p_node) { grid_map_editor = memnew(GridMapEditor(editor)); switch ((int)EditorSettings::get_singleton()->get("editors/grid_map/editor_side")) { case 0: { // Left. - add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, grid_map_editor); + Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor); } break; case 1: { // Right. - add_control_to_container(CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT, grid_map_editor); + Node3DEditor::get_singleton()->add_control_to_right_panel(grid_map_editor); } break; } grid_map_editor->hide(); diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 59854ad527..8cd23ffb24 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -181,6 +181,7 @@ void MobileVRInterface::set_position_from_sensors() { orientation = rotate * orientation; tracking_state = XRInterface::XR_NORMAL_TRACKING; + tracking_confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; }; ///@TODO improve this, the magnetometer is very fidgety sometimes flipping the axis for no apparent reason (probably a bug on my part) @@ -193,6 +194,7 @@ void MobileVRInterface::set_position_from_sensors() { orientation = Basis(transform_quat); tracking_state = XRInterface::XR_NORMAL_TRACKING; + tracking_confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; } else if (has_grav) { // use gravity vector to make sure down is down... // transform gravity into our world space @@ -461,7 +463,7 @@ CameraMatrix MobileVRInterface::get_projection_for_view(uint32_t p_view, double return eye; }; -Vector<BlitToScreen> MobileVRInterface::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { +Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { _THREAD_SAFE_METHOD_ Vector<BlitToScreen> blit_to_screen; @@ -512,7 +514,7 @@ void MobileVRInterface::process() { if (head.is_valid()) { // Set our head position, note in real space, reference frame and world scale is applied later - head->set_pose("default", head_transform, Vector3(), Vector3()); + head->set_pose("default", head_transform, Vector3(), Vector3(), tracking_confidence); } }; }; diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h index ac04763569..8ecca3a2ae 100644 --- a/modules/mobile_vr/mobile_vr_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -51,6 +51,7 @@ class MobileVRInterface : public XRInterface { private: bool initialized = false; XRInterface::TrackingStatus tracking_state; + XRPose::TrackingConfidence tracking_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; // Just set some defaults for these. At some point we need to look at adding a lookup table for common device + headset combos and/or support reading cardboard QR codes double eye_height = 1.85; @@ -150,7 +151,7 @@ public: virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; - virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py index 93a66ebf6f..43c1ec8f8a 100644 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ b/modules/mono/build_scripts/mono_reg_utils.py @@ -96,10 +96,10 @@ def find_msbuild_tools_path_reg(): raise ValueError("Cannot find `installationPath` entry") except ValueError as e: print("Error reading output from vswhere: " + e.message) - except OSError: - pass # Fine, vswhere not found - except (subprocess.CalledProcessError, OSError): - pass + except subprocess.CalledProcessError as e: + print(e.output) + except OSError as e: + print(e) # Try to find 14.0 in the Registry diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9d416dcfce..26436e3ec0 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1168,8 +1168,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #ifdef TOOLS_ENABLED // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->get_inspector()->update_tree(); - NodeDock::singleton->update_lists(); + InspectorDock::get_inspector_singleton()->update_tree(); + NodeDock::get_singleton()->update_lists(); } #endif } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 2be588cac4..2de923c125 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -103,8 +103,6 @@ private: bool valid = false; bool reload_invalidated = false; - bool builtin; - GDMonoClass *base = nullptr; GDMonoClass *native = nullptr; GDMonoClass *script_class = nullptr; diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index ce2b378623..7a4641dbbc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -47,9 +47,13 @@ namespace GodotTools.OpenVisualStudio if (dte == null) { // Open a new instance + dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0"); - var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); - dte = (DTE)Activator.CreateInstance(visualStudioDteType); + if (dte == null) + { + // Launch of VS 2022 failed, fallback to 2019 + dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0"); + } dte.UserControl = true; @@ -133,6 +137,21 @@ namespace GodotTools.OpenVisualStudio return 0; } + private static DTE TryVisualStudioLaunch(string version) + { + try + { + var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true); + var dte = (DTE)Activator.CreateInstance(visualStudioDteType); + + return dte; + } + catch (COMException) + { + return null; + } + } + private static DTE FindInstanceEditingSolution(string solutionPath) { if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) @@ -164,7 +183,7 @@ namespace GodotTools.OpenVisualStudio continue; // The digits after the colon are the process ID - if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]")) + if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]")) continue; if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index e9cf7911be..2dbc78ab77 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -147,7 +147,7 @@ namespace GodotTools.Build Icon = GetThemeIcon("StatusError", "EditorIcons"), ExpandIcon = false, ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _errorsBtn.Toggled += ErrorsToggled; @@ -159,7 +159,7 @@ namespace GodotTools.Build Icon = GetThemeIcon("NodeWarning", "EditorIcons"), ExpandIcon = false, ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _warningsBtn.Toggled += WarningsToggled; @@ -169,7 +169,7 @@ namespace GodotTools.Build { Text = "Show Output".TTR(), ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _viewLogBtn.Toggled += ViewLogToggled; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 98c6881166..69960bdbeb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -428,7 +428,7 @@ namespace GodotTools Shortcut = buildSolutionShortcut, ShortcutInTooltip = true }; - _toolBarBuildButton.PressedSignal += BuildSolutionPressed; + _toolBarBuildButton.Pressed += BuildSolutionPressed; AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1b4ab0ef4b..1de41821f9 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -690,11 +690,11 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI continue; } - if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') { + if (is_digit(parts[curr_prefix_length][0])) { // The name of enum constants may begin with a numeric digit when strip from the enum prefix, // so we make the prefix for this constant one word shorter in those cases. for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) { - if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') { + if (!is_digit(parts[curr_prefix_length][0])) { break; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index a89dca6c34..eba0ea9a79 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -727,7 +727,7 @@ namespace Godot /// <summary> /// Check whether this string is a subsequence of the given string. /// </summary> - /// <seealso cref="IsSubsequenceOfI(string, string)"/> + /// <seealso cref="IsSubsequenceOfN(string, string)"/> /// <param name="instance">The subsequence to search.</param> /// <param name="text">The string that contains the subsequence.</param> /// <param name="caseSensitive">If <see langword="true"/>, the check is case sensitive.</param> @@ -779,7 +779,7 @@ namespace Godot /// <param name="instance">The subsequence to search.</param> /// <param name="text">The string that contains the subsequence.</param> /// <returns>If the string is a subsequence of the given string.</returns> - public static bool IsSubsequenceOfI(this string instance, string text) + public static bool IsSubsequenceOfN(this string instance, string text) { return instance.IsSubsequenceOf(text, caseSensitive: false); } diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 6cbdb645ca..52d5379e8b 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -140,12 +140,12 @@ void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, cons } } -void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { +void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node); Ref<Mesh> mesh = mesh_instance->get_mesh(); if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * mesh_instance->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices); } } @@ -159,7 +159,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor n = multimesh->get_instance_count(); } for (int i = 0; i < n; i++) { - _add_mesh(mesh, p_accumulated_transform * multimesh->get_instance_transform(i), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices); } } } @@ -171,7 +171,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (!meshes.is_empty()) { Ref<Mesh> mesh = meshes[1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices); } } } @@ -186,7 +186,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (Object::cast_to<CollisionShape3D>(child)) { CollisionShape3D *col_shape = Object::cast_to<CollisionShape3D>(child); - Transform3D transform = p_accumulated_transform * static_body->get_transform() * col_shape->get_transform(); + Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * col_shape->get_transform(); Ref<Mesh> mesh; Ref<Shape3D> s = col_shape->get_shape(); @@ -265,27 +265,108 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor } #ifdef MODULE_GRIDMAP_ENABLED - if (Object::cast_to<GridMap>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { - GridMap *gridmap_instance = Object::cast_to<GridMap>(p_node); - Array meshes = gridmap_instance->get_meshes(); - Transform3D xform = gridmap_instance->get_transform(); - for (int i = 0; i < meshes.size(); i += 2) { - Ref<Mesh> mesh = meshes[i + 1]; - if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); + GridMap *gridmap = Object::cast_to<GridMap>(p_node); + + if (gridmap) { + if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { + Array meshes = gridmap->get_meshes(); + Transform3D xform = gridmap->get_global_transform(); + for (int i = 0; i < meshes.size(); i += 2) { + Ref<Mesh> mesh = meshes[i + 1]; + if (mesh.is_valid()) { + _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); + } } } - } -#endif - if (Object::cast_to<Node3D>(p_node)) { - Node3D *spatial = Object::cast_to<Node3D>(p_node); - p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); + if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES && (gridmap->get_collision_layer() & p_collision_mask)) { + Array shapes = gridmap->get_collision_shapes(); + for (int i = 0; i < shapes.size(); i += 2) { + RID shape = shapes[i + 1]; + PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape); + Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape); + Ref<Mesh> mesh; + + switch (type) { + case PhysicsServer3D::SHAPE_SPHERE: { + real_t radius = data; + Ref<SphereMesh> sphere_mesh; + sphere_mesh.instantiate(); + sphere_mesh->set_radius(radius); + sphere_mesh->set_height(radius * 2.0); + mesh = sphere_mesh; + } break; + case PhysicsServer3D::SHAPE_BOX: { + Vector3 extents = data; + Ref<BoxMesh> box_mesh; + box_mesh.instantiate(); + box_mesh->set_size(2.0 * extents); + mesh = box_mesh; + } break; + case PhysicsServer3D::SHAPE_CAPSULE: { + Dictionary dict = data; + real_t radius = dict["radius"]; + real_t height = dict["height"]; + Ref<CapsuleMesh> capsule_mesh; + capsule_mesh.instantiate(); + capsule_mesh->set_radius(radius); + capsule_mesh->set_height(height); + mesh = capsule_mesh; + } break; + case PhysicsServer3D::SHAPE_CYLINDER: { + Dictionary dict = data; + real_t radius = dict["radius"]; + real_t height = dict["height"]; + Ref<CylinderMesh> cylinder_mesh; + cylinder_mesh.instantiate(); + cylinder_mesh->set_height(height); + cylinder_mesh->set_bottom_radius(radius); + cylinder_mesh->set_top_radius(radius); + mesh = cylinder_mesh; + } break; + case PhysicsServer3D::SHAPE_CONVEX_POLYGON: { + PackedVector3Array vertices = data; + Geometry3D::MeshData md; + + Error err = ConvexHullComputer::convex_hull(vertices, md); + + if (err == OK) { + PackedVector3Array faces; + + for (int j = 0; j < md.faces.size(); ++j) { + Geometry3D::MeshData::Face face = md.faces[j]; + + for (int k = 2; k < face.indices.size(); ++k) { + faces.push_back(md.vertices[face.indices[0]]); + faces.push_back(md.vertices[face.indices[k - 1]]); + faces.push_back(md.vertices[face.indices[k]]); + } + } + + _add_faces(faces, shapes[i], p_vertices, p_indices); + } + } break; + case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: { + Dictionary dict = data; + PackedVector3Array faces = Variant(dict["faces"]); + _add_faces(faces, shapes[i], p_vertices, p_indices); + } break; + default: { + WARN_PRINT("Unsupported collision shape type."); + } break; + } + + if (mesh.is_valid()) { + _add_mesh(mesh, shapes[i], p_vertices, p_indices); + } + } + } } +#endif if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); } } } @@ -530,7 +611,7 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); } - Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_transform().affine_inverse(); + Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_global_transform().affine_inverse(); for (Node *E : parse_nodes) { NavigationMesh::ParsedGeometryType geometry_type = p_nav_mesh->get_parsed_geometry_type(); uint32_t collision_mask = p_nav_mesh->get_collision_mask(); diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 1ffdd39aee..21f7a4941b 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -52,7 +52,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); - static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); + static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh( diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h index 7e56b14a24..73e3cb4fff 100644 --- a/modules/ogg/ogg_packet_sequence.h +++ b/modules/ogg/ogg_packet_sequence.h @@ -104,7 +104,7 @@ class OGGPacketSequencePlayback : public RefCounted { mutable ogg_packet *packet; - uint64_t data_version; + uint64_t data_version = 0; mutable int64_t packetno = 0; diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index d6a96282f3..6a06619840 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -62,7 +62,6 @@ if env["builtin_harfbuzz"]: #'src/hb-gobject-structs.cc', "src/hb-icu.cc", "src/hb-map.cc", - "src/hb-ms-feature-ranges.cc", "src/hb-number.cc", "src/hb-ot-cff1-table.cc", "src/hb-ot-cff2-table.cc", diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 6002dc80da..c7511f587e 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -303,26 +303,10 @@ _FORCE_INLINE_ bool is_connected_to_prev(char32_t p_chr, char32_t p_pchr) { return (prop != U_JT_RIGHT_JOINING) && (prop != U_JT_NON_JOINING) ? !is_ligature(p_pchr, p_chr) : false; } -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - /*************************************************************************/ String TextServerAdvanced::interface_name = "ICU / HarfBuzz / Graphite"; -uint32_t TextServerAdvanced::interface_features = FEATURE_BIDI_LAYOUT | FEATURE_VERTICAL_LAYOUT | FEATURE_SHAPING | FEATURE_KASHIDA_JUSTIFICATION | FEATURE_BREAK_ITERATORS | FEATURE_USE_SUPPORT_DATA | FEATURE_FONT_VARIABLE; +uint32_t TextServerAdvanced::interface_features = FEATURE_BIDI_LAYOUT | FEATURE_VERTICAL_LAYOUT | FEATURE_SHAPING | FEATURE_KASHIDA_JUSTIFICATION | FEATURE_BREAK_ITERATORS | FEATURE_USE_SUPPORT_DATA | FEATURE_FONT_VARIABLE | FEATURE_CONTEXT_SENSITIVE_CASE_CONVERSION; bool TextServerAdvanced::has_feature(Feature p_feature) const { return (interface_features & p_feature) == p_feature; @@ -2901,7 +2885,7 @@ void TextServerAdvanced::font_set_global_oversampling(float p_oversampling) { List<RID> text_bufs; shaped_owner.get_owned_list(&text_bufs); for (const RID &E : text_bufs) { - invalidate(shaped_owner.get_or_null(E)); + invalidate(shaped_owner.get_or_null(E), false); } } } @@ -2936,7 +2920,7 @@ int TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int return limit; } -void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped) { +void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2951,27 +2935,32 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->glyphs_logical.clear(); p_shaped->overrun_trim_data = TrimData(); p_shaped->utf16 = Char16String(); - if (p_shaped->script_iter != nullptr) { - memdelete(p_shaped->script_iter); - p_shaped->script_iter = nullptr; - } for (int i = 0; i < p_shaped->bidi_iter.size(); i++) { ubidi_close(p_shaped->bidi_iter[i]); } p_shaped->bidi_iter.clear(); + + if (p_text) { + if (p_shaped->script_iter != nullptr) { + memdelete(p_shaped->script_iter); + p_shaped->script_iter = nullptr; + } + p_shaped->break_ops_valid = false; + p_shaped->js_ops_valid = false; + } } void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) { ShapedTextDataAdvanced *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } - for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextDataAdvanced::Span span = parent->spans[k]; + for (int i = 0; i < parent->spans.size(); i++) { + ShapedTextDataAdvanced::Span span = parent->spans[i]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -3004,7 +2993,7 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) { sd->spans.clear(); sd->objects.clear(); sd->bidi_override.clear(); - invalidate(sd); + invalidate(sd, true); } void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) { @@ -3017,7 +3006,7 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir full_copy(sd); } sd->direction = p_direction; - invalidate(sd); + invalidate(sd, false); } } @@ -3047,7 +3036,7 @@ void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const full_copy(sd); } sd->custom_punct = p_punct; - invalidate(sd); + invalidate(sd, false); } } @@ -3070,7 +3059,7 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Array for (int i = 0; i < p_override.size(); i++) { sd->bidi_override.push_back(p_override[i]); } - invalidate(sd); + invalidate(sd, false); } void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { @@ -3083,7 +3072,7 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O full_copy(sd); } sd->orientation = p_orientation; - invalidate(sd); + invalidate(sd, false); } } @@ -3095,7 +3084,7 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e ERR_FAIL_COND(sd->parent != RID()); if (sd->preserve_invalid != p_enabled) { sd->preserve_invalid = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3117,7 +3106,7 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e full_copy(sd); } sd->preserve_control = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3137,7 +3126,41 @@ TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_sh return sd->orientation; } -bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { +int TextServerAdvanced::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerAdvanced::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerAdvanced::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataAdvanced::Span &span = sd->spans.write[p_index]; + bool changed = (span.font_size != p_size) || (span.features != p_opentype_features) || (p_fonts.size() != span.fonts.size()); + if (!changed) { + for (int i = 0; i < p_fonts.size(); i++) { + changed = changed || (span.fonts[i] != p_fonts[i]); + } + } + if (changed) { + span.fonts = p_fonts; + span.font_size = p_size; + span.features = p_opentype_features; + + invalidate(sd, false); + } +} + +bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); ERR_FAIL_COND_V(p_size <= 0, false); @@ -3162,11 +3185,12 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te span.font_size = p_size; span.language = p_language; span.features = p_opentype_features; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; sd->end += p_text.length(); - invalidate(sd); + invalidate(sd, true); return true; } @@ -3196,13 +3220,13 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con sd->text += String::chr(0xfffc).repeat(p_length); sd->end += p_length; sd->objects[p_key] = obj; - invalidate(sd); + invalidate(sd, true); return true; } bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3222,7 +3246,7 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -3262,75 +3286,78 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { @@ -3423,7 +3450,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -3466,72 +3493,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S } } - // Align embedded objects to baseline. - float full_ascent = p_new_sd->ascent; - float full_descent = p_new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_new_sd->objects) { - if ((E.value.pos >= p_new_sd->start) && (E.value.pos < p_new_sd->end)) { - if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - } - p_new_sd->ascent = full_ascent; - p_new_sd->descent = full_descent; + _realign(p_new_sd); } p_new_sd->valid = true; @@ -3968,40 +3930,43 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { const UChar *data = sd->utf16.ptr(); - HashMap<int, bool> breaks; - UErrorCode err = U_ZERO_ERROR; - int i = 0; - while (i < sd->spans.size()) { - String language = sd->spans[i].language; - int r_start = sd->spans[i].start; - while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { - i++; - } - int r_end = sd->spans[i].end; - UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); - if (U_FAILURE(err)) { - // No data loaded - use fallback. - for (int j = r_start; j < r_end; j++) { - char32_t c = sd->text[j - sd->start]; - if (is_whitespace(c)) { - breaks[j + 1] = false; - } - if (is_linebreak(c)) { - breaks[j + 1] = true; - } + if (!sd->break_ops_valid) { + sd->breaks.clear(); + UErrorCode err = U_ZERO_ERROR; + int i = 0; + while (i < sd->spans.size()) { + String language = sd->spans[i].language; + int r_start = sd->spans[i].start; + while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { + i++; } - } else { - while (ubrk_next(bi) != UBRK_DONE) { - int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; - if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { - breaks[pos] = true; - } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { - breaks[pos] = false; + int r_end = sd->spans[i].end; + UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); + if (U_FAILURE(err)) { + // No data loaded - use fallback. + for (int j = r_start; j < r_end; j++) { + char32_t c = sd->text[j - sd->start]; + if (is_whitespace(c)) { + sd->breaks[j + 1] = false; + } + if (is_linebreak(c)) { + sd->breaks[j + 1] = true; + } + } + } else { + while (ubrk_next(bi) != UBRK_DONE) { + int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; + if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { + sd->breaks[pos] = true; + } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { + sd->breaks[pos] = false; + } } } + ubrk_close(bi); + i++; } - ubrk_close(bi); - i++; + sd->break_ops_valid = true; } sd->sort_valid = false; @@ -4013,7 +3978,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { int c_punct_size = sd->custom_punct.length(); const char32_t *c_punct = sd->custom_punct.ptr(); - for (i = 0; i < sd_size; i++) { + for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = ch[sd_glyphs[i].start - sd->start]; if (c == 0xfffc) { @@ -4040,8 +4005,8 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { if (is_underscore(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE; } - if (breaks.has(sd_glyphs[i].end)) { - if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) { + if (sd->breaks.has(sd_glyphs[i].end)) { + if (sd->breaks[sd_glyphs[i].end] && (is_linebreak(c))) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD; } else if (is_whitespace(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT; @@ -4186,41 +4151,45 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { const UChar *data = sd->utf16.ptr(); int32_t data_size = sd->utf16.length(); - Map<int, bool> jstops; + if (!sd->js_ops_valid) { + sd->jstops.clear(); - // Use ICU word iterator and custom kashida detection. - UErrorCode err = U_ZERO_ERROR; - UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); - if (U_FAILURE(err)) { - // No data - use fallback. - int limit = 0; - for (int i = 0; i < sd->text.length(); i++) { - if (is_whitespace(data[i])) { - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - limit = i + 1; + // Use ICU word iterator and custom kashida detection. + UErrorCode err = U_ZERO_ERROR; + UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); + if (U_FAILURE(err)) { + // No data - use fallback. + int limit = 0; + for (int i = 0; i < sd->text.length(); i++) { + if (is_whitespace(data[i])) { + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; + } + limit = i + 1; + } } - } - int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - } else { - int limit = 0; - while (ubrk_next(bi) != UBRK_DONE) { - if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { - int i = _convert_pos(sd, ubrk_current(bi)); - jstops[i + sd->start] = false; - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); - if (ks != -1) { - jstops[ks + sd->start] = true; - } - limit = i; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; } + } else { + int limit = 0; + while (ubrk_next(bi) != UBRK_DONE) { + if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { + int i = _convert_pos(sd, ubrk_current(bi)); + sd->jstops[i + sd->start] = false; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); + if (ks != -1) { + sd->jstops[ks + sd->start] = true; + } + limit = i; + } + } + ubrk_close(bi); } - ubrk_close(bi); + + sd->js_ops_valid = true; } sd->sort_valid = false; @@ -4228,18 +4197,18 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { Glyph *sd_glyphs = sd->glyphs.ptrw(); int sd_size = sd->glyphs.size(); - if (jstops.size() > 0) { + if (sd->jstops.size() > 0) { for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = sd->text[sd_glyphs[i].start - sd->start]; if (c == 0x0640) { sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION; } - if (jstops.has(sd_glyphs[i].start)) { + if (sd->jstops.has(sd_glyphs[i].start)) { if (c == 0xfffc) { continue; } - if (jstops[sd_glyphs[i].start]) { + if (sd->jstops[sd_glyphs[i].start]) { if (c != 0x0640) { if (sd_glyphs[i].font_rid != RID()) { Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size); @@ -4574,7 +4543,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { return true; } - invalidate(sd); + invalidate(sd, false); if (sd->parent != RID()) { shaped_text_shape(sd->parent); ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); @@ -4733,70 +4702,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } } - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - sd->ascent = full_ascent; - sd->descent = full_descent; + _realign(sd); sd->valid = true; return sd->valid; } @@ -4863,7 +4769,7 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const { ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } @@ -5229,6 +5135,40 @@ String TextServerAdvanced::strip_diacritics(const String &p_string) const { return result; } +String TextServerAdvanced::string_to_upper(const String &p_string, const String &p_language) const { + // Convert to UTF-16. + Char16String utf16 = p_string.utf16(); + + Char16String upper; + UErrorCode err = U_ZERO_ERROR; + int32_t len = u_strToUpper(nullptr, 0, utf16.ptr(), -1, p_language.ascii().get_data(), &err); + ERR_FAIL_COND_V_MSG(err != U_BUFFER_OVERFLOW_ERROR, p_string, u_errorName(err)); + upper.resize(len); + err = U_ZERO_ERROR; + u_strToUpper(upper.ptrw(), len, utf16.ptr(), -1, p_language.ascii().get_data(), &err); + ERR_FAIL_COND_V_MSG(U_FAILURE(err), p_string, u_errorName(err)); + + // Convert back to UTF-32. + return String::utf16(upper.ptr(), len); +} + +String TextServerAdvanced::string_to_lower(const String &p_string, const String &p_language) const { + // Convert to UTF-16. + Char16String utf16 = p_string.utf16(); + + Char16String lower; + UErrorCode err = U_ZERO_ERROR; + int32_t len = u_strToLower(nullptr, 0, utf16.ptr(), -1, p_language.ascii().get_data(), &err); + ERR_FAIL_COND_V_MSG(err != U_BUFFER_OVERFLOW_ERROR, p_string, u_errorName(err)); + lower.resize(len); + err = U_ZERO_ERROR; + u_strToLower(lower.ptrw(), len, utf16.ptr(), -1, p_language.ascii().get_data(), &err); + ERR_FAIL_COND_V_MSG(U_FAILURE(err), p_string, u_errorName(err)); + + // Convert back to UTF-32. + return String::utf16(lower.ptr(), len); +} + TextServerAdvanced::TextServerAdvanced() { _insert_num_systems_lang(); _insert_feature_sets(); diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index d088219d91..145d740b68 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -242,6 +242,21 @@ class TextServerAdvanced : public TextServer { // Shaped text cache data. struct ShapedTextDataAdvanced : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + /* Intermediate data */ Char16String utf16; Vector<UBiDi *> bidi_iter; @@ -249,6 +264,11 @@ class TextServerAdvanced : public TextServer { ScriptIterator *script_iter = nullptr; hb_buffer_t *hb_buffer = nullptr; + HashMap<int, bool> jstops; + HashMap<int, bool> breaks; + bool break_ops_valid = false; + bool js_ops_valid = false; + ~ShapedTextDataAdvanced() { for (int i = 0; i < bidi_iter.size(); i++) { ubidi_close(bidi_iter[i]); @@ -268,6 +288,7 @@ class TextServerAdvanced : public TextServer { mutable RID_PtrOwner<FontDataAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + void _realign(ShapedTextDataAdvanced *p_sd) const; int _convert_pos(const ShapedTextDataAdvanced *p_sd, int p_pos) const; int _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int p_pos) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int p_start, int p_length) const; @@ -302,7 +323,7 @@ protected: static void _bind_methods(){}; void full_copy(ShapedTextDataAdvanced *p_shaped); - void invalidate(ShapedTextDataAdvanced *p_shaped); + void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false); public: virtual bool has_feature(Feature p_feature) const override; @@ -482,10 +503,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; @@ -527,6 +552,9 @@ public: virtual String strip_diacritics(const String &p_string) const override; + virtual String string_to_upper(const String &p_string, const String &p_language = "") const override; + virtual String string_to_lower(const String &p_string, const String &p_language = "") const override; + TextServerAdvanced(); ~TextServerAdvanced(); }; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index c7a7c4aa70..182d2a02ad 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -32,6 +32,7 @@ #include "core/error/error_macros.h" #include "core/string/print_string.h" +#include "core/string/ucaps.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen. @@ -43,30 +44,6 @@ #endif /*************************************************************************/ -/* Character properties. */ -/*************************************************************************/ - -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_punct(char32_t p_char) { - return (p_char >= 0x0020 && p_char <= 0x002F) || (p_char >= 0x003A && p_char <= 0x0040) || (p_char >= 0x005B && p_char <= 0x005E) || (p_char == 0x0060) || (p_char >= 0x007B && p_char <= 0x007E) || (p_char >= 0x2000 && p_char <= 0x206F) || (p_char >= 0x3000 && p_char <= 0x303F); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - -/*************************************************************************/ String TextServerFallback::interface_name = "Fallback"; uint32_t TextServerFallback::interface_features = 0; // Nothing is supported. @@ -90,7 +67,7 @@ void TextServerFallback::free(RID p_rid) { font_owner.free(p_rid); memdelete(fd); } else if (shaped_owner.owns(p_rid)) { - ShapedTextData *sd = shaped_owner.get_or_null(p_rid); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid); shaped_owner.free(p_rid); memdelete(sd); } @@ -126,8 +103,9 @@ _FORCE_INLINE_ int32_t ot_tag_from_string(const char *p_str, int p_len) { char tag[4]; uint32_t i; - if (!p_str || !p_len || !*p_str) + if (!p_str || !p_len || !*p_str) { return OT_TAG(0, 0, 0, 0); + } if (p_len < 0 || p_len > 4) { p_len = 4; @@ -2059,7 +2037,7 @@ void TextServerFallback::font_set_global_oversampling(float p_oversampling) { /* Shaped text buffer interface */ /*************************************************************************/ -void TextServerFallback::invalidate(ShapedTextData *p_shaped) { +void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2073,17 +2051,17 @@ void TextServerFallback::invalidate(ShapedTextData *p_shaped) { p_shaped->glyphs_logical.clear(); } -void TextServerFallback::full_copy(ShapedTextData *p_shaped) { - ShapedTextData *parent = shaped_owner.get_or_null(p_shaped->parent); +void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) { + ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextData::Span span = parent->spans[k]; + ShapedTextDataFallback::Span span = parent->spans[k]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -2097,7 +2075,7 @@ void TextServerFallback::full_copy(ShapedTextData *p_shaped) { RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = memnew(ShapedTextData); + ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback); sd->direction = p_direction; sd->orientation = p_orientation; @@ -2105,7 +2083,7 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te } void TextServerFallback::shaped_text_clear(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2134,7 +2112,7 @@ TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); if (sd->custom_punct != p_punct) { @@ -2148,13 +2126,13 @@ void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String TextServerFallback::shaped_text_get_custom_punctuation(RID p_shaped) const { _THREAD_SAFE_METHOD_ - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, String()); return sd->custom_punct; } void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2172,7 +2150,7 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Array } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); @@ -2180,7 +2158,7 @@ TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_sh } void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); MutexLock lock(sd->mutex); ERR_FAIL_COND(!sd); @@ -2194,7 +2172,7 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2202,7 +2180,7 @@ bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { } void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2216,15 +2194,52 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; } -bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); +int TextServerFallback::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerFallback::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerFallback::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataFallback::Span &span = sd->spans.write[p_index]; + span.fonts.clear(); + // Pre-sort fonts, push fonts with the language support first. + Vector<RID> fonts_no_match; + int font_count = p_fonts.size(); + for (int i = 0; i < font_count; i++) { + if (font_is_language_supported(p_fonts[i], span.language)) { + span.fonts.push_back(p_fonts[i]); + } else { + fonts_no_match.push_back(p_fonts[i]); + } + } + span.fonts.append_array(fonts_no_match); + span.font_size = p_size; + span.features = p_opentype_features; + + sd->valid = false; +} + +bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2242,7 +2257,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->text.length(); span.end = span.start + p_text.length(); @@ -2261,6 +2276,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te ERR_FAIL_COND_V(span.fonts.is_empty(), false); span.font_size = p_size; span.language = p_language; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; @@ -2271,7 +2287,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te } bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2282,12 +2298,12 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->start + sd->text.length(); span.end = span.start + p_length; span.embedded_key = p_key; - ShapedTextData::EmbeddedObject obj; + ShapedTextDataFallback::EmbeddedObject obj; obj.inline_align = p_inline_align; obj.rect.size = p_size; obj.pos = span.start; @@ -2302,7 +2318,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con } bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2322,7 +2338,7 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -2362,79 +2378,82 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2448,7 +2467,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID()); ERR_FAIL_COND_V(sd->end < p_start + p_length, RID()); - ShapedTextData *new_sd = memnew(ShapedTextData); + ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback); new_sd->parent = p_shaped; new_sd->start = p_start; new_sd->end = p_start + p_length; @@ -2474,7 +2493,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -2518,7 +2537,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng // Align embedded objects to baseline. float full_ascent = new_sd->ascent; float full_descent = new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : new_sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : new_sd->objects) { if ((E.value.pos >= new_sd->start) && (E.value.pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { @@ -2588,7 +2607,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2596,7 +2615,7 @@ RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { } float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint16_t /*JustificationFlag*/ p_jst_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2705,7 +2724,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, } float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2761,7 +2780,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat3 } bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2817,7 +2836,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { } bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2833,7 +2852,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { } void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint16_t p_trim_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped_line); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); @@ -2861,9 +2880,9 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl return; } - Vector<ShapedTextData::Span> &spans = sd->spans; + Vector<ShapedTextDataFallback::Span> &spans = sd->spans; if (sd->parent != RID()) { - ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent); + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); ERR_FAIL_COND(!parent_sd->valid); spans = parent_sd->spans; } @@ -2985,39 +3004,39 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } int TextServerFallback::shaped_text_get_trim_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; } int TextServerFallback::shaped_text_get_ellipsis_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; } const Glyph *TextServerFallback::shaped_text_get_ellipsis_glyphs(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); } int TextServerFallback::shaped_text_get_ellipsis_glyph_count(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); } bool TextServerFallback::shaped_text_shape(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3044,7 +3063,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // "Shape" string. for (int i = 0; i < sd->spans.size(); i++) { - const ShapedTextData::Span &span = sd->spans[i]; + const ShapedTextDataFallback::Span &span = sd->spans[i]; if (span.embedded_key != Variant()) { // Embedded object. if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -3144,7 +3163,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // Align embedded objects to baseline. float full_ascent = sd->ascent; float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { case INLINE_ALIGNMENT_TO_TOP: { @@ -3210,7 +3229,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3218,7 +3237,7 @@ bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3229,7 +3248,7 @@ const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { } int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0); MutexLock lock(sd->mutex); @@ -3240,7 +3259,7 @@ int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3252,7 +3271,7 @@ const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { } Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Vector2i()); MutexLock lock(sd->mutex); @@ -3261,11 +3280,11 @@ Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { Array ret; - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } @@ -3273,7 +3292,7 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { } Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Rect2()); MutexLock lock(sd->mutex); @@ -3285,7 +3304,7 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke } Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Size2()); MutexLock lock(sd->mutex); @@ -3300,7 +3319,7 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { } float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3311,7 +3330,7 @@ float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3322,7 +3341,7 @@ float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_width(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3333,7 +3352,7 @@ float TextServerFallback::shaped_text_get_width(RID p_shaped) const { } float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3345,7 +3364,7 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const } float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3356,6 +3375,34 @@ float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) cons return sd->uthk; } +String TextServerFallback::string_to_upper(const String &p_string, const String &p_language) const { + String upper = p_string; + + for (int i = 0; i < upper.size(); i++) { + const char32_t s = upper[i]; + const char32_t t = _find_upper(s); + if (s != t) { // avoid copy on write + upper[i] = t; + } + } + + return upper; +} + +String TextServerFallback::string_to_lower(const String &p_string, const String &p_language) const { + String lower = p_string; + + for (int i = 0; i < lower.size(); i++) { + const char32_t s = lower[i]; + const char32_t t = _find_lower(s); + if (s != t) { // avoid copy on write + lower[i] = t; + } + } + + return lower; +} + TextServerFallback::TextServerFallback() { _insert_feature_sets(); }; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index e8619e0825..be944cde58 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -203,17 +203,38 @@ class TextServerFallback : public TextServer { } } + // Shaped text cache data. + + struct ShapedTextDataFallback : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + }; + // Common data. float oversampling = 1.f; mutable RID_PtrOwner<FontDataFallback> font_owner; - mutable RID_PtrOwner<ShapedTextData> shaped_owner; + mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + + void _realign(ShapedTextDataFallback *p_sd) const; protected: static void _bind_methods(){}; - void full_copy(ShapedTextData *p_shaped); - void invalidate(ShapedTextData *p_shaped); + void full_copy(ShapedTextDataFallback *p_shaped); + void invalidate(ShapedTextDataFallback *p_shaped); public: virtual bool has_feature(Feature p_feature) const override; @@ -391,10 +412,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; @@ -430,6 +455,9 @@ public: virtual float shaped_text_get_underline_position(RID p_shaped) const override; virtual float shaped_text_get_underline_thickness(RID p_shaped) const override; + virtual String string_to_upper(const String &p_string, const String &p_language = "") const override; + virtual String string_to_lower(const String &p_string, const String &p_language = "") const override; + TextServerFallback(); ~TextServerFallback(); }; diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 9d297fcb93..a9652cbba1 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -337,6 +337,7 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { Ref<Image> img; img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8); + texture->create_from_image(img); } else { /* tear down the partial theora setup */ diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp index ec1a8a6b42..1a7d473bd4 100644 --- a/modules/visual_script/editor/visual_script_editor.cpp +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -42,6 +42,7 @@ #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" +#include "scene/gui/view_panner.h" #include "scene/main/window.h" #ifdef TOOLS_ENABLED @@ -271,7 +272,7 @@ protected: if (String(p_name) == "export") { script->set_variable_export(var, p_value); - EditorNode::get_singleton()->get_inspector()->update_tree(); + InspectorDock::get_inspector_singleton()->update_tree(); return true; } @@ -1366,7 +1367,7 @@ void VisualScriptEditor::_create_function() { } void VisualScriptEditor::_add_node_dialog() { - _generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true); + _generic_search(graph->get_global_position() + Vector2(55, 80), true); } void VisualScriptEditor::_add_func_input() { @@ -1442,7 +1443,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt if (p_button == 1) { // Ensure script base exists otherwise use custom base type. ERR_FAIL_COND(script.is_null()); - new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true); + new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), true); return; } else if (p_button == 0) { String name = _validate_name("new_function"); @@ -1948,14 +1949,14 @@ void VisualScriptEditor::_on_nodes_duplicate() { } } -void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) { +void VisualScriptEditor::_generic_search(Vector2 pos, bool node_centered) { if (node_centered) { port_action_pos = graph->get_size() / 2.0f; } else { port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); } - new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text + new_connect_node_select->select_from_visual_script(script, false); // do not reset text // Ensure that the dialog fits inside the graph. Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); @@ -1992,7 +1993,7 @@ void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } } if (is_empty_selection && clipboard->nodes.is_empty()) { - _generic_search(script->get_instance_base_type(), mouse_up_position); + _generic_search(); } else { popup_menu->set_item_disabled(int(EDIT_CUT_NODES), is_empty_selection); popup_menu->set_item_disabled(int(EDIT_COPY_NODES), is_empty_selection); @@ -2446,7 +2447,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da drop_position = pos; drop_node = node; drop_path = sn->get_path_to(node); - new_connect_node_select->select_from_instance(node, "", false, node->get_class()); + new_connect_node_select->select_from_instance(node, false); } undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2802,7 +2803,7 @@ void VisualScriptEditor::add_callback(const String &p_function, PackedStringArra String name = p_args[i]; Variant::Type type = Variant::NIL; - if (name.find(":") != -1) { + if (name.contains(":")) { String tt = name.get_slice(":", 1); name = name.get_slice(":", 0); for (int j = 0; j < Variant::VARIANT_MAX; j++) { @@ -2846,7 +2847,7 @@ Control *VisualScriptEditor::get_base_editor() const { return graph; } -void VisualScriptEditor::set_tooltip_request_func(String p_method, Object *p_obj) { +void VisualScriptEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { } Control *VisualScriptEditor::get_edit_menu() { @@ -3234,19 +3235,34 @@ void VisualScriptEditor::_port_action_menu(int p_option) { n->set_base_type("Object"); } String type_string; + String base_script = ""; if (script->get_node(port_action_node)->get_output_value_port_count() > 0) { type_string = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + VisualScriptFunctionCall *vsfc = Object::cast_to<VisualScriptFunctionCall>(*script->get_node(port_action_node)); + if (vsfc) { + base_script = vsfc->get_base_script(); + } else { + VisualScriptPropertyGet *vspg = Object::cast_to<VisualScriptPropertyGet>(*script->get_node(port_action_node)); + if (vspg) { + base_script = vspg->get_base_script(); + } else { + VisualScriptPropertySet *vsps = Object::cast_to<VisualScriptPropertySet>(*script->get_node(port_action_node)); + if (vsps) { + base_script = vsps->get_base_script(); + } + } + } } if (tg.type == Variant::OBJECT) { if (tg.script.is_valid()) { - new_connect_node_select->select_from_script(tg.script, ""); - } else if (!type_string.is_empty()) { - new_connect_node_select->select_from_base_type(type_string); + new_connect_node_select->select_from_script(tg.script); + } else if (type_string != String()) { + new_connect_node_select->select_from_base_type(type_string, base_script); } else { - new_connect_node_select->select_from_base_type(n->get_base_type()); + new_connect_node_select->select_from_base_type(n->get_base_type(), base_script); } } else if (tg.type == Variant::NIL) { - new_connect_node_select->select_from_base_type(""); + new_connect_node_select->select_from_base_type("", base_script); } else { new_connect_node_select->select_from_basic_type(tg.type); } @@ -3309,66 +3325,54 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua } void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { +#ifdef OSX_ENABLED + bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif Vector2 pos = _get_pos_in_graph(port_action_pos); Set<int> vn; + bool port_node_exists = true; if (drop_position != Vector2()) { pos = drop_position; } drop_position = Vector2(); - bool port_node_exists = true; - - // if (func == StringName()) { - // func = default_func; - // port_node_exists = false; - // } - - if (p_category == "visualscript") { - Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text); - Ref<VisualScriptNode> vnode_old; - if (port_node_exists && p_connecting) { - vnode_old = script->get_node(port_action_node); - } - int new_id = script->get_available_id(); + Ref<VisualScriptNode> vnode; + Ref<VisualScriptNode> vnode_old; + if (port_node_exists && p_connecting) { + vnode_old = script->get_node(port_action_node); + } - if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && vnode_old.is_valid()) { - Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; - Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type); - } + if (p_category.begins_with("VisualScriptNode")) { + Ref<VisualScriptNode> n = VisualScriptLanguage::singleton->create_node_from_name(p_text); - if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && vnode_old.is_valid()) { + if (Object::cast_to<VisualScriptTypeCast>(n.ptr()) && vnode_old.is_valid()) { Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; String hint_name = vnode_old->get_output_value_port_info(port_action_output).hint_string; if (type == Variant::OBJECT) { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(hint_name); } else if (type == Variant::NIL) { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(""); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(""); } else { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type)); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(Variant::get_type_name(type)); } } - - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, pos); - if (vnode_old.is_valid() && p_connecting) { - connect_seq(vnode_old, vnode_new, new_id); - connect_data(vnode_old, vnode_new, new_id); - } - - undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - return; + vnode = n; } - Ref<VisualScriptNode> vnode; - Ref<VisualScriptPropertySet> script_prop_set; - - if (p_category == String("method")) { + if (p_category == String("Class") && !p_connecting) { + Ref<VisualScriptFunctionCall> n; + n.instantiate(); + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SINGLETON); + n->set_singleton("ClassDB"); + n->set_function("instantiate"); + // Did not find a way to edit the input port value + vnode = n; + } else if (p_category == String("class_method")) { Ref<VisualScriptFunctionCall> n; n.instantiate(); if (!drop_path.is_empty()) { @@ -3386,96 +3390,151 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } vnode = n; - } else if (p_category == String("set")) { - Ref<VisualScriptPropertySet> n; - n.instantiate(); - if (!drop_path.is_empty()) { - if (drop_path == ".") { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); - } else { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); - n->set_base_path(drop_path); + } else if (p_category == String("class_property")) { + Vector<String> property_path = p_text.split(":"); + if (held_ctrl) { + Ref<VisualScriptPropertySet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } } - } - if (drop_node) { - n->set_base_type(drop_node->get_class()); - if (drop_node->get_script_instance()) { - n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } } - } - vnode = n; - script_prop_set = n; - } else if (p_category == String("get")) { - Ref<VisualScriptPropertyGet> n; - n.instantiate(); - n->set_property(p_text); - if (!drop_path.is_empty()) { - if (drop_path == ".") { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); - } else { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); - n->set_base_path(drop_path); + vnode = n; + } else { + Ref<VisualScriptPropertyGet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } } - } - if (drop_node) { - n->set_base_type(drop_node->get_class()); - if (drop_node->get_script_instance()) { - n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } } - } - vnode = n; - } - drop_path = String(); - drop_node = nullptr; - - if (p_category == String("action")) { - if (p_text == "VisualScriptCondition") { - Ref<VisualScriptCondition> n; - n.instantiate(); vnode = n; } - if (p_text == "VisualScriptSwitch") { - Ref<VisualScriptSwitch> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptSequence") { - Ref<VisualScriptSequence> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptIterator") { - Ref<VisualScriptIterator> n; + } else if (p_category == String("class_constant")) { + Vector<String> property_path = p_text.split(":"); + if (ClassDB::class_exists(property_path[0])) { + Ref<VisualScriptClassConstant> n; n.instantiate(); + n->set_base_type(property_path[0]); + n->set_class_constant(property_path[1]); vnode = n; - } else if (p_text == "VisualScriptWhile") { - Ref<VisualScriptWhile> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptReturn") { - Ref<VisualScriptReturn> n; + } else { + Ref<VisualScriptBasicTypeConstant> n; n.instantiate(); + if (property_path[0] == "Nil") { + n->set_basic_type(Variant::NIL); + } else if (property_path[0] == "bool") { + n->set_basic_type(Variant::BOOL); + } else if (property_path[0] == "int") { + n->set_basic_type(Variant::INT); + } else if (property_path[0] == "float") { + n->set_basic_type(Variant::FLOAT); + } else if (property_path[0] == "String") { + n->set_basic_type(Variant::STRING); + } else if (property_path[0] == "Vector2") { + n->set_basic_type(Variant::VECTOR2); + } else if (property_path[0] == "Vector2i") { + n->set_basic_type(Variant::VECTOR2I); + } else if (property_path[0] == "Rect2") { + n->set_basic_type(Variant::RECT2); + } else if (property_path[0] == "Rect2i") { + n->set_basic_type(Variant::RECT2I); + } else if (property_path[0] == "Transform2D") { + n->set_basic_type(Variant::TRANSFORM2D); + } else if (property_path[0] == "Vector3") { + n->set_basic_type(Variant::VECTOR3); + } else if (property_path[0] == "Vector3i") { + n->set_basic_type(Variant::VECTOR3I); + } else if (property_path[0] == "Plane") { + n->set_basic_type(Variant::PLANE); + } else if (property_path[0] == "ABB") { + n->set_basic_type(Variant::AABB); + } else if (property_path[0] == "Quaternion") { + n->set_basic_type(Variant::QUATERNION); + } else if (property_path[0] == "Basis") { + n->set_basic_type(Variant::BASIS); + } else if (property_path[0] == "Transform3D") { + n->set_basic_type(Variant::TRANSFORM3D); + } else if (property_path[0] == "Color") { + n->set_basic_type(Variant::COLOR); + } else if (property_path[0] == "RID") { + n->set_basic_type(Variant::RID); + } else if (property_path[0] == "Object") { + n->set_basic_type(Variant::OBJECT); + } else if (property_path[0] == "Callable") { + n->set_basic_type(Variant::CALLABLE); + } else if (property_path[0] == "Signal") { + n->set_basic_type(Variant::SIGNAL); + } else if (property_path[0] == "StringName") { + n->set_basic_type(Variant::STRING_NAME); + } else if (property_path[0] == "NodePath") { + n->set_basic_type(Variant::NODE_PATH); + } else if (property_path[0] == "Dictionary") { + n->set_basic_type(Variant::DICTIONARY); + } else if (property_path[0] == "Array") { + n->set_basic_type(Variant::ARRAY); + } else if (property_path[0] == "PackedByteArray") { + n->set_basic_type(Variant::PACKED_BYTE_ARRAY); + } else if (property_path[0] == "PackedInt32Array") { + n->set_basic_type(Variant::PACKED_INT32_ARRAY); + } else if (property_path[0] == "PackedInt64Array") { + n->set_basic_type(Variant::PACKED_INT64_ARRAY); + } else if (property_path[0] == "PackedFloat32Array") { + n->set_basic_type(Variant::PACKED_FLOAT32_ARRAY); + } else if (property_path[0] == "PackedStringArray") { + n->set_basic_type(Variant::PACKED_STRING_ARRAY); + } else if (property_path[0] == "PackedVector2Array") { + n->set_basic_type(Variant::PACKED_VECTOR2_ARRAY); + } else if (property_path[0] == "PackedVector3Array") { + n->set_basic_type(Variant::PACKED_VECTOR3_ARRAY); + } else if (property_path[0] == "PackedColorArray") { + n->set_basic_type(Variant::PACKED_COLOR_ARRAY); + } + n->set_basic_type_constant(property_path[1]); vnode = n; } - } - int new_id = script->get_available_id(); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); - undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); - undo_redo->add_do_method(this, "_update_graph", new_id); - undo_redo->add_undo_method(this, "_update_graph", new_id); - undo_redo->commit_action(); + } else if (p_category == String("class_signal")) { + Vector<String> property_path = p_text.split(":"); + ERR_FAIL_COND(!(script->has_custom_signal(property_path[1]) || ClassDB::has_signal(script->get_instance_base_type(), property_path[1]))); - if (script_prop_set.is_valid()) { - script_prop_set->set_property(p_text); + Ref<VisualScriptEmitSignal> n; + n.instantiate(); + n->set_signal(property_path[1]); + vnode = n; + } + if (vnode == nullptr) { + print_error("Category not handled: " + p_category.quote()); } - port_action_new_node = new_id; - - Ref<VisualScriptNode> vsn = script->get_node(port_action_new_node); + if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr()) && p_category != "Class") { + Vector<String> property_path = p_text.split(":"); + String class_of_method = property_path[0]; + String method_name = property_path[1]; - if (Object::cast_to<VisualScriptFunctionCall>(vsn.ptr())) { - Ref<VisualScriptFunctionCall> vsfc = vsn; - vsfc->set_function(p_text); + Ref<VisualScriptFunctionCall> vsfc = vnode; + vsfc->set_function(method_name); if (port_node_exists && p_connecting) { VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); @@ -3492,7 +3551,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri if (!base_type.is_empty() && hint == PROPERTY_HINT_TYPE_STRING) { vsfc->set_base_type(base_type); } - if (p_text == "call" || p_text == "call_deferred") { + if (method_name == "call" || method_name == "call_deferred") { vsfc->set_function(String("")); } } @@ -3510,8 +3569,8 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } if (port_node_exists && p_connecting) { - if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) { - Ref<VisualScriptPropertySet> vsp = vsn; + if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Ref<VisualScriptPropertySet> vsp = vnode; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); if (tg.type == Variant::OBJECT) { @@ -3540,8 +3599,8 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } - if (Object::cast_to<VisualScriptPropertyGet>(vsn.ptr())) { - Ref<VisualScriptPropertyGet> vsp = vsn; + if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Ref<VisualScriptPropertyGet> vsp = vnode; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); if (tg.type == Variant::OBJECT) { @@ -3569,13 +3628,85 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } } + if (vnode == nullptr) { + print_error("Not able to create node from category: \"" + p_category + "\" and text \"" + p_text + "\" Not created"); + return; + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); + + port_action_new_node = new_id; + + String base_script = ""; + String base_type = ""; if (port_node_exists) { - Ref<VisualScriptNode> vnode_old = script->get_node(port_action_node); + if (vnode_old.is_valid()) { + if (Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_script(); + } + } + + Vector<String> property_path = p_text.split(":"); + if (ClassDB::is_parent_class(script->get_instance_base_type(), property_path[0]) || script->get_path().ends_with(property_path[0].unquote())) { + if (!p_connecting) { + base_type = script->get_instance_base_type(); + base_script = script->get_path(); + } + } else { + base_type = property_path[0]; + base_script = ""; + } + + if (drop_node) { + Ref<Script> script = drop_node->get_script(); + if (script != nullptr) { + base_script = script->get_path(); + } + } + if (vnode_old.is_valid() && p_connecting) { + if (base_type == "") { + base_type = property_path[0]; + } else if (ClassDB::is_parent_class(property_path[0], base_type)) { + base_type = property_path[0]; + } connect_seq(vnode_old, vnode, port_action_new_node); connect_data(vnode_old, vnode, port_action_new_node); } } + if (Object::cast_to<VisualScriptTypeCast>(vnode.ptr())) { + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())) { + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_script(base_script); + } + + drop_path = String(); + drop_node = nullptr; + _update_graph(port_action_new_node); } @@ -3625,7 +3756,7 @@ void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<Visual } void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting) { - String name = p_text; + String name = p_text.substr(p_text.find_char(':') + 1); if (script->has_function(name)) { EditorNode::get_singleton()->show_warning(vformat(TTR("Script already has function '%s'"), name)); return; @@ -3782,7 +3913,8 @@ void VisualScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - graph->set_panning_scheme((GraphEdit::PanningScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int()); + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"))); } break; case NOTIFICATION_READY: { @@ -3901,7 +4033,7 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_ void VisualScriptEditor::_menu_option(int p_what) { switch (p_what) { case EDIT_ADD_NODE: { - _generic_search(script->get_instance_base_type(), mouse_up_position); + _generic_search(); } break; case EDIT_DELETE_NODES: { _on_nodes_delete(); @@ -3931,7 +4063,7 @@ void VisualScriptEditor::_menu_option(int p_what) { } break; case EDIT_FIND_NODE_TYPE: { - _generic_search(script->get_instance_base_type()); + _generic_search(); } break; case EDIT_COPY_NODES: { _on_nodes_copy(); diff --git a/modules/visual_script/editor/visual_script_editor.h b/modules/visual_script/editor/visual_script_editor.h index 90e4fb9d56..b01732b2fd 100644 --- a/modules/visual_script/editor/visual_script_editor.h +++ b/modules/visual_script/editor/visual_script_editor.h @@ -85,55 +85,55 @@ class VisualScriptEditor : public ScriptEditorBase { MEMBER_SIGNAL }; - VBoxContainer *members_section; - MenuButton *edit_menu; + VBoxContainer *members_section = nullptr; + MenuButton *edit_menu = nullptr; Ref<VisualScript> script; - Button *base_type_select; + Button *base_type_select = nullptr; - LineEdit *func_name_box; - ScrollContainer *func_input_scroll; - VBoxContainer *func_input_vbox; - ConfirmationDialog *function_create_dialog; + LineEdit *func_name_box = nullptr; + ScrollContainer *func_input_scroll = nullptr; + VBoxContainer *func_input_vbox = nullptr; + ConfirmationDialog *function_create_dialog = nullptr; - GraphEdit *graph; - HBoxContainer *status_bar; - Button *toggle_scripts_button; + GraphEdit *graph = nullptr; + HBoxContainer *status_bar = nullptr; + Button *toggle_scripts_button = nullptr; - VisualScriptEditorSignalEdit *signal_editor; + VisualScriptEditorSignalEdit *signal_editor = nullptr; - AcceptDialog *edit_signal_dialog; - EditorInspector *edit_signal_edit; + AcceptDialog *edit_signal_dialog = nullptr; + EditorInspector *edit_signal_edit = nullptr; - VisualScriptPropertySelector *method_select; - VisualScriptPropertySelector *new_connect_node_select; - VisualScriptPropertySelector *new_virtual_method_select; + VisualScriptPropertySelector *method_select = nullptr; + VisualScriptPropertySelector *new_connect_node_select = nullptr; + VisualScriptPropertySelector *new_virtual_method_select = nullptr; - VisualScriptEditorVariableEdit *variable_editor; + VisualScriptEditorVariableEdit *variable_editor = nullptr; - AcceptDialog *edit_variable_dialog; - EditorInspector *edit_variable_edit; + AcceptDialog *edit_variable_dialog = nullptr; + EditorInspector *edit_variable_edit = nullptr; - CustomPropertyEditor *default_value_edit; + CustomPropertyEditor *default_value_edit = nullptr; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - Tree *members; - AcceptDialog *function_name_edit; - LineEdit *function_name_box; + Tree *members = nullptr; + AcceptDialog *function_name_edit = nullptr; + LineEdit *function_name_box = nullptr; - Label *hint_text; - Timer *hint_text_timer; + Label *hint_text = nullptr; + Timer *hint_text_timer = nullptr; - Label *select_func_text; + Label *select_func_text = nullptr; bool updating_graph = false; void _show_hint(const String &p_hint); void _hide_timer(); - CreateDialog *select_base_type; + CreateDialog *select_base_type = nullptr; struct VirtualInMenu { String name; @@ -173,10 +173,10 @@ class VisualScriptEditor : public ScriptEditorBase { String member_name; PortAction port_action; - int port_action_node; - int port_action_output; + int port_action_node = 0; + int port_action_output = 0; Vector2 port_action_pos; - int port_action_new_node; + int port_action_new_node = 0; bool saved_pos_dirty = false; @@ -196,7 +196,7 @@ class VisualScriptEditor : public ScriptEditorBase { int _create_new_node_from_name(const String &p_text, const Vector2 &p_point); void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); - int error_line; + int error_line = -1; void _node_selected(Node *p_node); void _center_on_node(int p_id); @@ -241,7 +241,7 @@ class VisualScriptEditor : public ScriptEditorBase { bool node_has_sequence_connections(int p_id); - void _generic_search(String p_base_type = "", Vector2 pos = Vector2(), bool node_centered = false); + void _generic_search(Vector2 pos = Vector2(), bool node_centered = false); virtual void input(const Ref<InputEvent> &p_event) override; void _graph_gui_input(const Ref<InputEvent> &p_event); @@ -267,12 +267,12 @@ class VisualScriptEditor : public ScriptEditorBase { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - int editing_id; - int editing_input; + int editing_id = 0; + int editing_input = 0; - bool can_swap; - int data_disconnect_node; - int data_disconnect_port; + bool can_swap = false; + int data_disconnect_node = 0; + int data_disconnect_port = 0; void _default_value_changed(); void _default_value_edited(Node *p_button, int p_id, int p_input_port); @@ -328,7 +328,7 @@ public: virtual void update_settings() override; virtual bool show_members_overview() override; virtual void set_debugger_active(bool p_active) override; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; virtual Control *get_edit_menu() override; virtual void clear_edit_menu() override; virtual void set_find_replace_bar(FindReplaceBar *p_bar) override { p_bar->hide(); }; // Not needed here. diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp index c88d10dabd..4072dcebe5 100644 --- a/modules/visual_script/editor/visual_script_property_selector.cpp +++ b/modules/visual_script/editor/visual_script_property_selector.cpp @@ -37,13 +37,28 @@ #include "../visual_script_nodes.h" #include "core/os/keyboard.h" #include "editor/doc_tools.h" +#include "editor/editor_feature_profile.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/main/node.h" #include "scene/main/window.h" -void VisualScriptPropertySelector::_text_changed(const String &p_newtext) { - _update_search(); +void VisualScriptPropertySelector::_update_icons() { + search_box->set_right_icon(results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + search_box->set_clear_button_enabled(true); + search_box->add_theme_icon_override("right_icon", results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + + search_visual_script_nodes->set_icon(results_tree->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); + search_classes->set_icon(results_tree->get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + search_methods->set_icon(results_tree->get_theme_icon(SNAME("MemberMethod"), SNAME("EditorIcons"))); + search_operators->set_icon(results_tree->get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + search_signals->set_icon(results_tree->get_theme_icon(SNAME("MemberSignal"), SNAME("EditorIcons"))); + search_constants->set_icon(results_tree->get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + search_properties->set_icon(results_tree->get_theme_icon(SNAME("MemberProperty"), SNAME("EditorIcons"))); + search_theme_items->set_icon(results_tree->get_theme_icon(SNAME("MemberTheme"), SNAME("EditorIcons"))); + + case_sensitive_button->set_icon(results_tree->get_theme_icon(SNAME("MatchCase"), SNAME("EditorIcons"))); + hierarchy_button->set_icon(results_tree->get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); } void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { @@ -55,24 +70,8 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { case Key::DOWN: case Key::PAGEUP: case Key::PAGEDOWN: { - search_options->gui_input(k); + results_tree->gui_input(k); search_box->accept_event(); - - TreeItem *root = search_options->get_root(); - if (!root->get_first_child()) { - break; - } - - TreeItem *current = search_options->get_selected(); - - TreeItem *item = search_options->get_next_selected(root); - while (item) { - item->deselect(0); - item = search_options->get_next_selected(item); - } - - current->select(0); - } break; default: break; @@ -80,654 +79,1189 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { } } -void VisualScriptPropertySelector::_update_search() { - set_title(TTR("Search VisualScript")); - - search_options->clear(); - help_bit->set_text(""); - - TreeItem *root = search_options->create_item(); - bool found = false; - StringName base = base_type; - List<StringName> base_list; - while (base) { - base_list.push_back(base); - base = ClassDB::get_parent_class_nocheck(base); - } - - for (const StringName &E : base_list) { - List<MethodInfo> methods; - List<PropertyInfo> props; - TreeItem *category = nullptr; - Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = { - vbc->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("String"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector2i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Rect2"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Rect2i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("RID"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("MiniObject"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Callable"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Signal"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Dictionary"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedByteArray"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedInt32Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedInt64Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedFloat32Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedFloat64Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedStringArray"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedVector2Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons")) - }; - { - String b = String(E); - category = search_options->create_item(root); - if (category) { - category->set_text(0, b.replace_first("*", "")); - category->set_selectable(0, false); - Ref<Texture2D> icon; - String rep = b.replace("*", ""); - icon = EditorNode::get_singleton()->get_class_icon(rep); - category->set_icon(0, icon); - } - } - if (properties || seq_connect) { - if (instance) { - instance->get_property_list(&props, true); - } else { - Object *obj = ObjectDB::get_instance(script); - if (Object::cast_to<Script>(obj)) { - Object::cast_to<Script>(obj)->get_script_property_list(&props); - } else { - ClassDB::get_property_list(E, &props, true); - } - } - for (const PropertyInfo &F : props) { - if (!(F.usage & PROPERTY_USAGE_EDITOR) && !(F.usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) { - continue; - } +void VisualScriptPropertySelector::_update_results_i(int p_int) { + _update_results(); +} - if (type_filter.size() && type_filter.find(F.type) == -1) { - continue; - } +void VisualScriptPropertySelector::_update_results_s(String p_string) { + _update_results(); +} - // capitalize() also converts underscore to space, we'll match again both possible styles - String get_text_raw = String(vformat(TTR("Get %s"), F.name)); - String get_text = get_text_raw.capitalize(); - String set_text_raw = String(vformat(TTR("Set %s"), F.name)); - String set_text = set_text_raw.capitalize(); - String input = search_box->get_text().capitalize(); - - if (input.is_empty() || get_text_raw.findn(input) != -1 || get_text.findn(input) != -1) { - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, get_text); - item->set_metadata(0, F.name); - item->set_icon(0, type_icons[F.type]); - item->set_metadata(1, "get"); - item->set_collapsed(true); - item->set_selectable(0, true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); - } +void VisualScriptPropertySelector::_update_results() { + _update_icons(); + search_runner = Ref<SearchRunner>(memnew(SearchRunner(this, results_tree))); + set_process(true); +} + +void VisualScriptPropertySelector::_confirmed() { + TreeItem *ti = results_tree->get_selected(); + if (!ti) { + return; + } + emit_signal(SNAME("selected"), ti->get_metadata(0), ti->get_metadata(1), connecting); + set_visible(false); +} + +void VisualScriptPropertySelector::_item_selected() { + if (results_tree->get_selected()->has_meta("description")) { + help_bit->set_text(results_tree->get_selected()->get_meta("description")); + } else { + help_bit->set_text("No description available"); + } +} - if (input.is_empty() || set_text_raw.findn(input) != -1 || set_text.findn(input) != -1) { - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, set_text); - item->set_metadata(0, F.name); - item->set_icon(0, type_icons[F.type]); - item->set_metadata(1, "set"); - item->set_selectable(0, true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); +void VisualScriptPropertySelector::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + +void VisualScriptPropertySelector::_notification(int p_what) { + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_icons(); + } break; + case NOTIFICATION_ENTER_TREE: { + connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + } break; + case NOTIFICATION_PROCESS: { + // Update background search. + if (search_runner.is_valid()) { + if (search_runner->work()) { + // Search done. + get_ok_button()->set_disabled(!results_tree->get_selected()); + + search_runner = Ref<SearchRunner>(); + set_process(false); } - } - } - { - if (type != Variant::NIL) { - Variant v; - Callable::CallError ce; - Variant::construct(type, v, nullptr, 0, ce); - v.get_method_list(&methods); } else { - Object *obj = ObjectDB::get_instance(script); - if (Object::cast_to<Script>(obj)) { - Object::cast_to<Script>(obj)->get_script_method_list(&methods); - } - - ClassDB::get_method_list(E, &methods, true, true); - } - } - for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { - String name = M->get().name.get_slice(":", 0); - if (name.begins_with("_") && !(M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; + // if one is valid + set_process(false); } + } break; + } +} - if (virtuals_only && !(M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; - } +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select method from base type")); + base_type = p_base; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; - if (!virtuals_only && (M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; - } + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("._"); // show all _methods + search_box->set_caret_column(2); + } else { + search_box->set_text("."); // show all methods + search_box->set_caret_column(1); + } + } - MethodInfo mi = M->get(); - String desc_arguments; - if (mi.arguments.size() > 0) { - desc_arguments = "("; - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) { - desc_arguments += ", "; - } - if (mi.arguments[i].type == Variant::NIL) { - desc_arguments += "var"; - } else if (mi.arguments[i].name.find(":") != -1) { - desc_arguments += mi.arguments[i].name.get_slice(":", 1); - mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0); - } else { - desc_arguments += Variant::get_type_name(mi.arguments[i].type); - } - } - desc_arguments += ")"; - } - String desc_raw = mi.name + desc_arguments; - String desc = desc_raw.capitalize().replace("( ", "("); + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); - if (!search_box->get_text().is_empty() && - name.findn(search_box->get_text()) == -1 && - desc.findn(search_box->get_text()) == -1 && - desc_raw.findn(search_box->get_text()) == -1) { - continue; - } + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, desc); - item->set_icon(0, vbc->get_theme_icon(SNAME("MemberMethod"), SNAME("EditorIcons"))); - item->set_metadata(0, name); - item->set_selectable(0, true); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); - item->set_metadata(1, "method"); - item->set_collapsed(true); - item->set_selectable(1, false); + _update_results(); +} - item->set_selectable(2, false); - item->set_metadata(2, connecting); - } +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_base_script, bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from base type")); + base_type = p_base; + base_script = p_base_script.lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + connecting = p_connecting; - if (category && category->get_first_child() == nullptr) { - memdelete(category); //old category was unused + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("_"); + } else { + search_box->set_text(" "); } } - if (properties) { - if (!seq_connect && !visual_script_generic) { - get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); - get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); - get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); - get_visual_node_names("functions/deconstruct/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); - get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box); - if (type == Variant::INT) { - get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box); - } - if (type == Variant::BOOL) { - get_visual_node_names("operators/logic/", Set<String>(), found, root, search_box); - } - if (type == Variant::BOOL || type == Variant::INT || type == Variant::FLOAT || type == Variant::VECTOR2 || type == Variant::VECTOR3) { - get_visual_node_names("operators/math/", Set<String>(), found, root, search_box); - } - } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(false); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + // When class is Input only show inheritors + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from script")); + ERR_FAIL_COND(p_script.is_null()); + + base_type = p_script->get_instance_base_type(); + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + script = p_script->get_instance_id(); + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(""); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(true); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" - if (seq_connect && !visual_script_generic) { - String text = search_box->get_text(); - create_visualscript_item(String("VisualScriptCondition"), root, text, String("Condition")); - create_visualscript_item(String("VisualScriptSwitch"), root, text, String("Switch")); - create_visualscript_item(String("VisualScriptSequence"), root, text, String("Sequence")); - create_visualscript_item(String("VisualScriptIterator"), root, text, String("Iterator")); - create_visualscript_item(String("VisualScriptWhile"), root, text, String("While")); - create_visualscript_item(String("VisualScriptReturn"), root, text, String("Return")); - get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); - get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from basic type")); + ERR_FAIL_COND(p_type == Variant::NIL); + base_type = Variant::get_type_name(p_type); + base_script = ""; + type = p_type; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(false); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + + _update_results(); +} + +void VisualScriptPropertySelector::select_from_action(const String &p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from action")); + base_type = p_type; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; - if ((properties || seq_connect) && visual_script_generic) { - get_visual_node_names("", Set<String>(), found, root, search_box); + if (clear_text) { + search_box->set_text(""); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(false); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); + + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from instance")); + base_type = p_instance->get_class(); - TreeItem *selected_item = search_options->search_item_text(search_box->get_text()); - if (!found && selected_item != nullptr) { - selected_item->select(0); - found = true; + const Ref<Script> &p_script = p_instance->get_script(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" - get_ok_button()->set_disabled(root->get_first_child() == nullptr); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); } -void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) { - if (search_input.is_empty() || text.findn(search_input) != -1) { - TreeItem *item = search_options->create_item(root); - item->set_text(0, text); - item->set_icon(0, vbc->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); - item->set_metadata(0, name); - item->set_metadata(1, "action"); - item->set_selectable(0, true); - item->set_collapsed(true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); +void VisualScriptPropertySelector::select_from_visual_script(const Ref<Script> &p_script, bool clear_text) { + set_title(TTR("Select from visual script")); + base_type = p_script->get_instance_base_type(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + type = Variant::NIL; + connecting = false; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::show_window(float p_screen_ratio) { + popup_centered_ratio(p_screen_ratio); } -void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box) { - Map<String, TreeItem *> path_cache; +void VisualScriptPropertySelector::_bind_methods() { + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +} - List<String> fnodes; - VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); +VisualScriptPropertySelector::VisualScriptPropertySelector() { + vbox = memnew(VBoxContainer); + add_child(vbox); + + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->set_alignment(hbox->ALIGNMENT_CENTER); + vbox->add_child(hbox); + + case_sensitive_button = memnew(Button); + case_sensitive_button->set_flat(true); + case_sensitive_button->set_tooltip(TTR("Case Sensitive")); + case_sensitive_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + case_sensitive_button->set_toggle_mode(true); + case_sensitive_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(case_sensitive_button); + + hierarchy_button = memnew(Button); + hierarchy_button->set_flat(true); + hierarchy_button->set_tooltip(TTR("Show Hierarchy")); + hierarchy_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + hierarchy_button->set_toggle_mode(true); + hierarchy_button->set_pressed(true); + hierarchy_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(hierarchy_button); + + hbox->add_child(memnew(VSeparator)); + + search_visual_script_nodes = memnew(Button); + search_visual_script_nodes->set_flat(true); + search_visual_script_nodes->set_tooltip(TTR("Search Visual Script Nodes")); + search_visual_script_nodes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_visual_script_nodes->set_toggle_mode(true); + search_visual_script_nodes->set_pressed(true); + search_visual_script_nodes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_visual_script_nodes); + + search_classes = memnew(Button); + search_classes->set_flat(true); + search_classes->set_tooltip(TTR("Search Classes")); + search_classes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_classes->set_toggle_mode(true); + search_classes->set_pressed(true); + search_classes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_classes); + + search_operators = memnew(Button); + search_operators->set_flat(true); + search_operators->set_tooltip(TTR("Search Operators")); + search_operators->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_operators->set_toggle_mode(true); + search_operators->set_pressed(true); + search_operators->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_operators); + + hbox->add_child(memnew(VSeparator)); + + search_methods = memnew(Button); + search_methods->set_flat(true); + search_methods->set_tooltip(TTR("Search Methods")); + search_methods->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_methods->set_toggle_mode(true); + search_methods->set_pressed(true); + search_methods->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_methods); + + search_signals = memnew(Button); + search_signals->set_flat(true); + search_signals->set_tooltip(TTR("Search Signals")); + search_signals->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_signals->set_toggle_mode(true); + search_signals->set_pressed(true); + search_signals->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_signals); + + search_constants = memnew(Button); + search_constants->set_flat(true); + search_constants->set_tooltip(TTR("Search Constants")); + search_constants->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_constants->set_toggle_mode(true); + search_constants->set_pressed(true); + search_constants->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_constants); + + search_properties = memnew(Button); + search_properties->set_flat(true); + search_properties->set_tooltip(TTR("Search Properties")); + search_properties->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_properties->set_toggle_mode(true); + search_properties->set_pressed(true); + search_properties->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_properties); + + search_theme_items = memnew(Button); + search_theme_items->set_flat(true); + search_theme_items->set_tooltip(TTR("Search Theme Items")); + search_theme_items->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_theme_items->set_toggle_mode(true); + search_theme_items->set_pressed(true); + search_theme_items->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_theme_items); + + scope_combo = memnew(OptionButton); + scope_combo->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + scope_combo->set_tooltip(TTR("Select the search limits")); + scope_combo->set_stretch_ratio(0); // Fixed width. + scope_combo->add_item(TTR("Search Related"), SCOPE_RELATED); + scope_combo->add_separator(); + scope_combo->add_item(TTR("Search Base"), SCOPE_BASE); + scope_combo->add_item(TTR("Search Inheriters"), SCOPE_INHERITERS); + scope_combo->add_item(TTR("Search Unrelated"), SCOPE_UNRELATED); + scope_combo->add_item(TTR("Search All"), SCOPE_ALL); + scope_combo->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_update_results_i)); + hbox->add_child(scope_combo); - for (const String &E : fnodes) { - if (!E.begins_with(root_filter)) { - continue; - } - Vector<String> path = E.split("/"); - - // check if the name has the filter - bool in_filter = false; - Vector<String> tx_filters = search_box->get_text().split(" "); - for (int i = 0; i < tx_filters.size(); i++) { - if (tx_filters[i].is_empty()) { - in_filter = true; - } else { - in_filter = false; - } - if (E.findn(tx_filters[i]) != -1) { - in_filter = true; - break; - } - } - if (!in_filter) { - continue; - } + search_box = memnew(LineEdit); + search_box->set_tooltip(TTR("Enter \" \" to show all filterd options\nEnter \".\" to show all filterd methods, operators and constructors\nUse CTRL_KEY to drop property setters")); + search_box->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_update_results_s)); + search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); + register_text_enter(search_box); + vbox->add_child(search_box); + + results_tree = memnew(Tree); + results_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + results_tree->set_hide_root(true); + results_tree->set_hide_folding(false); + results_tree->set_columns(2); + results_tree->set_column_title(0, TTR("Name")); + results_tree->set_column_clip_content(0, true); + results_tree->set_column_title(1, TTR("Member Type")); + results_tree->set_column_expand(1, false); + results_tree->set_column_custom_minimum_width(1, 150 * EDSCALE); + results_tree->set_column_clip_content(1, true); + results_tree->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + results_tree->set_select_mode(Tree::SELECT_ROW); + results_tree->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + results_tree->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_item_selected)); + vbox->add_child(results_tree); - bool in_modifier = p_modifiers.is_empty(); - for (Set<String>::Element *F = p_modifiers.front(); F && in_modifier; F = F->next()) { - if (E.findn(F->get()) != -1) { - in_modifier = true; - } - } - if (!in_modifier) { - continue; - } + help_bit = memnew(EditorHelpBit); + vbox->add_child(help_bit); + help_bit->connect("request_hide", callable_mp(this, &VisualScriptPropertySelector::_hide_requested)); + get_ok_button()->set_text(TTR("Open")); + get_ok_button()->set_disabled(true); + set_hide_on_ok(false); +} - TreeItem *item = search_options->create_item(root); - Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(E); - Ref<VisualScriptOperator> vnode_operator = vnode; - String type_name; - if (vnode_operator.is_valid()) { - String type; - if (path.size() >= 2) { - type = path[1]; - } - type_name = type.capitalize() + " "; +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_feature_profile(const StringName &p_class) { + Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); + if (profile.is_null()) { + return false; + } + + StringName class_name = p_class; + while (class_name != StringName()) { + if (!ClassDB::class_exists(class_name)) { + return false; } - Ref<VisualScriptFunctionCall> vnode_function_call = vnode; - if (vnode_function_call.is_valid()) { - String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type()); - type_name = basic_type.capitalize() + " "; + + if (profile->is_class_disabled(class_name)) { + return true; } - Ref<VisualScriptConstructor> vnode_constructor = vnode; - if (vnode_constructor.is_valid()) { - type_name = "Construct "; + class_name = ClassDB::get_parent_class(class_name); + } + + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_scope(const StringName &p_class) { + bool is_base_script = false; + if (p_class == selector_ui->base_script) { + is_base_script = true; + } + bool is_base = false; + if (selector_ui->base_type == p_class) { + is_base = true; + } + bool is_parent = false; + if ((ClassDB::is_parent_class(selector_ui->base_type, p_class)) && !is_base) { + is_parent = true; + } + + bool is_inheriter = false; + List<StringName> inheriters; + ClassDB::get_inheriters_from_class(selector_ui->base_type, &inheriters); + if (inheriters.find(p_class)) { + is_inheriter = true; + } + + if (scope_flags & SCOPE_BASE) { + if (is_base_script || is_base || is_parent) { + return false; } - Ref<VisualScriptDeconstruct> vnode_deconstruct = vnode; - if (vnode_deconstruct.is_valid()) { - type_name = "Deconstruct "; + } + if (scope_flags & SCOPE_INHERITERS) { + if (is_base_script || is_base || is_inheriter) { + return false; } - Vector<String> desc = path[path.size() - 1].replace("(", " ").replace(")", " ").replace(",", " ").split(" "); - for (int i = 0; i < desc.size(); i++) { - desc.write[i] = desc[i].capitalize(); - if (desc[i].ends_with(",")) { - desc.write[i] = desc[i].replace(",", ", "); - } + } + // if (scope_flags & SCOPE_RELATED) { + // /* code */ + // } + if (scope_flags & SCOPE_UNRELATED) { + if (!is_base_script && !is_base && !is_inheriter) { + return false; } - - item->set_text(0, type_name + String("").join(desc)); - item->set_icon(0, vbc->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); - item->set_selectable(0, true); - item->set_metadata(0, E); - item->set_selectable(0, true); - item->set_metadata(1, "visualscript"); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); } + return true; } -void VisualScriptPropertySelector::_confirmed() { - TreeItem *ti = search_options->get_selected(); - if (!ti) { - return; +bool VisualScriptPropertySelector::SearchRunner::_slice() { + bool phase_done = false; + switch (phase) { + case PHASE_INIT: + phase_done = _phase_init(); + break; + case PHASE_MATCH_CLASSES_INIT: + phase_done = _phase_match_classes_init(); + break; + case PHASE_NODE_CLASSES_INIT: + phase_done = _phase_node_classes_init(); + break; + case PHASE_NODE_CLASSES_BUILD: + phase_done = _phase_node_classes_build(); + break; + case PHASE_MATCH_CLASSES: + phase_done = _phase_match_classes(); + break; + case PHASE_CLASS_ITEMS_INIT: + phase_done = _phase_class_items_init(); + break; + case PHASE_CLASS_ITEMS: + phase_done = _phase_class_items(); + break; + case PHASE_MEMBER_ITEMS_INIT: + phase_done = _phase_member_items_init(); + break; + case PHASE_MEMBER_ITEMS: + phase_done = _phase_member_items(); + break; + case PHASE_SELECT_MATCH: + phase_done = _phase_select_match(); + break; + case PHASE_MAX: + return true; + default: + WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search."); + return true; + }; + + if (phase_done) { + phase++; } - emit_signal(SNAME("selected"), ti->get_metadata(0), ti->get_metadata(1), ti->get_metadata(2)); - set_visible(false); + return false; } -void VisualScriptPropertySelector::_item_selected() { - help_bit->set_text(""); - - TreeItem *item = search_options->get_selected(); - if (!item) { - return; +bool VisualScriptPropertySelector::SearchRunner::_phase_init() { + search_flags = 0; // selector_ui->filter_combo->get_selected_id(); + if (selector_ui->search_visual_script_nodes->is_pressed()) { + search_flags |= SEARCH_VISUAL_SCRIPT_NODES; } - String name = item->get_metadata(0); - - String class_type; - if (type != Variant::NIL) { - class_type = Variant::get_type_name(type); - - } else { - class_type = base_type; + if (selector_ui->search_classes->is_pressed()) { + search_flags |= SEARCH_CLASSES; } + // if (selector_ui->search_constructors->is_pressed()) { + search_flags |= SEARCH_CONSTRUCTORS; + // } + if (selector_ui->search_methods->is_pressed()) { + search_flags |= SEARCH_METHODS; + } + if (selector_ui->search_operators->is_pressed()) { + search_flags |= SEARCH_OPERATORS; + } + if (selector_ui->search_signals->is_pressed()) { + search_flags |= SEARCH_SIGNALS; + } + if (selector_ui->search_constants->is_pressed()) { + search_flags |= SEARCH_CONSTANTS; + } + if (selector_ui->search_properties->is_pressed()) { + search_flags |= SEARCH_PROPERTIES; + } + if (selector_ui->search_theme_items->is_pressed()) { + search_flags |= SEARCH_THEME_ITEMS; + } + if (selector_ui->case_sensitive_button->is_pressed()) { + search_flags |= SEARCH_CASE_SENSITIVE; + } + if (selector_ui->hierarchy_button->is_pressed()) { + search_flags |= SEARCH_SHOW_HIERARCHY; + } + scope_flags = selector_ui->scope_combo->get_selected_id(); - DocTools *dd = EditorHelp::get_doc_data(); - String text; - - String at_class = class_type; + return true; +} - while (!at_class.is_empty()) { - Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); - if (E) { - for (int i = 0; i < E->get().properties.size(); i++) { - if (E->get().properties[i].name == name) { - text = DTR(E->get().properties[i].description); +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { + combined_docs = EditorHelp::get_doc_data()->class_list; + matches.clear(); + matched_item = nullptr; + match_highest_score = 0; + + if ( + (selector_ui->base_script.unquote() != "") && + (selector_ui->base_script.unquote() != ".") && + !combined_docs.has(selector_ui->base_script)) { + String file_path = "res://" + selector_ui->base_script.unquote(); // EditorHelp::get_doc_data().name to filepath + Ref<Script> script; + script = ResourceLoader::load(file_path); + if (!script.is_null()) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + + class_doc.name = selector_ui->base_script; + + class_doc.inherits = script->get_instance_base_type(); + class_doc.brief_description = ".vs files not suported by EditorHelp::get_doc_data()"; + class_doc.description = ""; + + Object *obj = ObjectDB::get_instance(script->get_instance_id()); + if (Object::cast_to<Script>(obj)) { + List<MethodInfo> methods; + Object::cast_to<Script>(obj)->get_script_method_list(&methods); + for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { + class_doc.methods.push_back(_get_method_doc(M->get())); } - } - } - at_class = ClassDB::get_parent_class_nocheck(at_class); - } - at_class = class_type; + List<MethodInfo> signals; + Object::cast_to<Script>(obj)->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *S = signals.front(); S; S = S->next()) { + class_doc.signals.push_back(_get_method_doc(S->get())); + } - while (!at_class.is_empty()) { - Map<String, DocData::ClassDoc>::Element *C = dd->class_list.find(at_class); - if (C) { - for (int i = 0; i < C->get().methods.size(); i++) { - if (C->get().methods[i].name == name) { - text = DTR(C->get().methods[i].description); + List<PropertyInfo> propertys; + Object::cast_to<Script>(obj)->get_script_property_list(&propertys); + for (List<PropertyInfo>::Element *P = propertys.front(); P; P = P->next()) { + DocData::PropertyDoc pd = DocData::PropertyDoc(); + pd.name = P->get().name; + pd.type = Variant::get_type_name(P->get().type); + class_doc.properties.push_back(pd); } } + combined_docs.insert(class_doc.name, class_doc); } + } + iterator_doc = combined_docs.front(); + return true; +} - at_class = ClassDB::get_parent_class_nocheck(at_class); +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_init() { + VisualScriptLanguage::singleton->get_registered_node_names(&vs_nodes); + _add_class_doc("functions", "", ""); + _add_class_doc("operators", "", ""); + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_build() { + if (vs_nodes.is_empty()) { + return true; } - Vector<String> functions = name.rsplit("/", false); - at_class = functions.size() > 3 ? functions[functions.size() - 2] : class_type; - Map<String, DocData::ClassDoc>::Element *T = dd->class_list.find(at_class); - if (T) { - for (int i = 0; i < T->get().methods.size(); i++) { - if (T->get().methods[i].name == functions[functions.size() - 1]) { - text = DTR(T->get().methods[i].description); + String registerd_node_name = vs_nodes[0]; + vs_nodes.pop_front(); + + Vector<String> path = registerd_node_name.split("/"); + if (path[0] == "constants") { + _add_class_doc(registerd_node_name, "", "constants"); + } else if (path[0] == "custom") { + _add_class_doc(registerd_node_name, "", "custom"); + } else if (path[0] == "data") { + _add_class_doc(registerd_node_name, "", "data"); + } else if (path[0] == "flow_control") { + _add_class_doc(registerd_node_name, "", "flow_control"); + } else if (path[0] == "functions") { + if (path[1] == "built_in") { + _add_class_doc(registerd_node_name, "functions", "built_in"); + } else if (path[1] == "by_type") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2], "by_type_class"); + } + } else if (path[1] == "constructors") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2].substr(0, path[2].find_char('(')), "constructors_class"); } + } else if (path[1] == "deconstruct") { + _add_class_doc(registerd_node_name, "", "deconstruct"); + } else if (path[1] == "wait") { + _add_class_doc(registerd_node_name, "functions", "yield"); + } else { + _add_class_doc(registerd_node_name, "functions", ""); + } + } else if (path[0] == "index") { + _add_class_doc(registerd_node_name, "", "index"); + } else if (path[0] == "operators") { + if (path[1] == "bitwise") { + _add_class_doc(registerd_node_name, "operators", "bitwise"); + } else if (path[1] == "compare") { + _add_class_doc(registerd_node_name, "operators", "compare"); + } else if (path[1] == "logic") { + _add_class_doc(registerd_node_name, "operators", "logic"); + } else if (path[1] == "math") { + _add_class_doc(registerd_node_name, "operators", "math"); + } else { + _add_class_doc(registerd_node_name, "operators", ""); } } + return false; +} - List<String> *names = memnew(List<String>); - VisualScriptLanguage::singleton->get_registered_node_names(names); - if (names->find(name) != nullptr) { - Ref<VisualScriptOperator> operator_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (operator_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(operator_node->get_class_name()); - if (F) { - text = Variant::get_operator_name(operator_node->get_operator()); +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes() { + DocData::ClassDoc &class_doc = iterator_doc->value(); + if ( + (!_is_class_disabled_by_feature_profile(class_doc.name) && !_is_class_disabled_by_scope(class_doc.name)) || + _match_visual_script(class_doc)) { + if (class_doc.inherits == "VisualScriptCustomNode") { + class_doc.script_path = "res://" + class_doc.name.unquote(); + Ref<Script> script = ResourceLoader::load(class_doc.script_path); + Ref<VisualScriptCustomNode> vsn; + vsn.instantiate(); + vsn->set_script(script); + class_doc.name = vsn->get_caption(); + if (combined_docs.has(vsn->get_category())) { + class_doc.inherits = vsn->get_category(); + } else if (combined_docs.has("VisualScriptNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptNode/" + vsn->get_category(); + } else if (combined_docs.has("VisualScriptCustomNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptCustomNode/" + vsn->get_category(); + } else { + class_doc.inherits = ""; } + class_doc.category = "VisualScriptCustomNode/" + vsn->get_category(); + class_doc.brief_description = ""; + class_doc.constructors.clear(); + class_doc.methods.clear(); + class_doc.operators.clear(); + class_doc.signals.clear(); + class_doc.constants.clear(); + class_doc.enums.clear(); + class_doc.properties.clear(); + class_doc.theme_properties.clear(); } - Ref<VisualScriptTypeCast> typecast_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (typecast_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(typecast_node->get_class_name()); - if (F) { - text = DTR(F->get().description); + + matches[class_doc.name] = ClassMatch(); + ClassMatch &match = matches[class_doc.name]; + + match.category = class_doc.category; + match.doc = &class_doc; + // Match class name. + if (search_flags & SEARCH_CLASSES || _match_visual_script(class_doc)) { + if (term == "") { + match.name = !_match_is_hidden(class_doc); + } else { + match.name = _match_string(term, class_doc.name); } + // match.name = term == "" || _match_string(term, class_doc.name); } - Ref<VisualScriptBuiltinFunc> builtin_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (builtin_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(builtin_node->get_class_name()); - if (F) { - for (int i = 0; i < F->get().constants.size(); i++) { - if (F->get().constants[i].value.to_int() == int(builtin_node->get_func())) { - text = DTR(F->get().constants[i].description); + // Match members if the term is long enough. + if (term.length() >= 0) { + if (search_flags & SEARCH_CONSTRUCTORS) { + for (int i = 0; i < class_doc.constructors.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.constructors[i].name : class_doc.constructors[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.constructors.push_back(const_cast<DocData::MethodDoc *>(&class_doc.constructors[i])); + } + } + } + if (search_flags & SEARCH_METHODS) { + for (int i = 0; i < class_doc.methods.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.methods[i].name : class_doc.methods[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.methods.push_back(const_cast<DocData::MethodDoc *>(&class_doc.methods[i])); + } + } + } + if (search_flags & SEARCH_OPERATORS) { + for (int i = 0; i < class_doc.operators.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.operators[i].name : class_doc.operators[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.operators.push_back(const_cast<DocData::MethodDoc *>(&class_doc.operators[i])); + } + } + } + if (search_flags & SEARCH_SIGNALS) { + for (int i = 0; i < class_doc.signals.size(); i++) { + if (_match_string(term, class_doc.signals[i].name) || + term == " ") { + match.signals.push_back(const_cast<DocData::MethodDoc *>(&class_doc.signals[i])); + } + } + } + if (search_flags & SEARCH_CONSTANTS) { + for (int i = 0; i < class_doc.constants.size(); i++) { + if (_match_string(term, class_doc.constants[i].name) || + term == " ") { + match.constants.push_back(const_cast<DocData::ConstantDoc *>(&class_doc.constants[i])); + } + } + } + if (search_flags & SEARCH_PROPERTIES) { + for (int i = 0; i < class_doc.properties.size(); i++) { + if (_match_string(term, class_doc.properties[i].name) || + term == " " || + _match_string(term, class_doc.properties[i].getter) || + _match_string(term, class_doc.properties[i].setter)) { + match.properties.push_back(const_cast<DocData::PropertyDoc *>(&class_doc.properties[i])); + } + } + } + if (search_flags & SEARCH_THEME_ITEMS) { + for (int i = 0; i < class_doc.theme_properties.size(); i++) { + if (_match_string(term, class_doc.theme_properties[i].name) || + term == " ") { + match.theme_properties.push_back(const_cast<DocData::ThemeItemDoc *>(&class_doc.theme_properties[i])); } } } } } - memdelete(names); + iterator_doc = iterator_doc->next(); + return !iterator_doc; +} - if (text.is_empty()) { - return; - } +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items_init() { + results_tree->clear(); + iterator_match = matches.front(); - help_bit->set_text(text); -} + root_item = results_tree->create_item(); + class_items.clear(); -void VisualScriptPropertySelector::_hide_requested() { - _cancel_pressed(); // From AcceptDialog. + return true; } -void VisualScriptPropertySelector::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items() { + if (!iterator_match) { + return true; } -} -void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = p_current; - type = Variant::NIL; - properties = false; - instance = nullptr; - virtuals_only = p_virtuals_only; + ClassMatch &match = iterator_match->value(); - show_window(.5f); - if (clear_text) { - search_box->set_text(""); + if (search_flags & SEARCH_SHOW_HIERARCHY) { + if (match.required()) { + _create_class_hierarchy(match); + } } else { - search_box->select_all(); + if (match.name) { + _create_class_item(root_item, match.doc, true); + } } - search_box->grab_focus(); - connecting = p_connecting; - _update_search(); + iterator_match = iterator_match->next(); + return !iterator_match; } -void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) { - type_filter = p_type_filter; +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items_init() { + iterator_match = matches.front(); + + return true; } -void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = p_current; - type = Variant::NIL; - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = p_virtuals_only; +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items() { + if (!iterator_match) { + return true; + } - show_window(.5f); - if (clear_text) { - search_box->set_text(""); + ClassMatch &match = iterator_match->value(); + + TreeItem *parent = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item; + bool constructor_created = false; + for (int i = 0; i < match.methods.size(); i++) { + String text = match.methods[i]->name; + if (!constructor_created) { + if (match.doc->name == match.methods[i]->name) { + text += " " + TTR("(constructors)"); + constructor_created = true; + } + } else { + if (match.doc->name == match.methods[i]->name) { + continue; + } + } + _create_method_item(parent, match.doc, text, match.methods[i]); + } + for (int i = 0; i < match.signals.size(); i++) { + _create_signal_item(parent, match.doc, match.signals[i]); + } + for (int i = 0; i < match.constants.size(); i++) { + _create_constant_item(parent, match.doc, match.constants[i]); + } + for (int i = 0; i < match.properties.size(); i++) { + _create_property_item(parent, match.doc, match.properties[i]); + } + for (int i = 0; i < match.theme_properties.size(); i++) { + _create_theme_property_item(parent, match.doc, match.theme_properties[i]); + } + + iterator_match = iterator_match->next(); + return !iterator_match; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_select_match() { + if (matched_item) { + matched_item->select(0); + } + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_match_string(const String &p_term, const String &p_string) const { + if (search_flags & SEARCH_CASE_SENSITIVE) { + return p_string.find(p_term) > -1; } else { - search_box->select_all(); + return p_string.findn(p_term) > -1; } - search_box->grab_focus(); - seq_connect = p_seq_connect; - connecting = p_connecting; +} - _update_search(); +bool VisualScriptPropertySelector::SearchRunner::_match_visual_script(DocData::ClassDoc &class_doc) { + if (class_doc.category.ends_with("_class")) { + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_CLASSES) { + if (matches.has(class_doc.inherits)) { + return true; + } + } + return false; + } + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_VISUAL_SCRIPT_NODES) { + return true; + } + if (class_doc.name.begins_with("operators") && search_flags & SEARCH_OPERATORS) { + return true; + } + if (class_doc.category.begins_with("VisualScriptNode/deconstruct")) { + if (class_doc.name.find(selector_ui->base_type, 0) > -1) { + return true; + } + } + + return false; } -void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting, bool clear_text) { - ERR_FAIL_COND(p_script.is_null()); +bool VisualScriptPropertySelector::SearchRunner::_match_is_hidden(DocData::ClassDoc &class_doc) { + if (class_doc.category.begins_with("VisualScript")) { + if (class_doc.name.begins_with("flow_control")) { + return false; + } else if (class_doc.name.begins_with("operators")) { + return !(search_flags & SEARCH_OPERATORS); + } else if (class_doc.name.begins_with("functions/built_in/print")) { + return false; + } + return true; + } + return false; +} - base_type = p_script->get_instance_base_type(); - selected = p_current; - type = Variant::NIL; - script = p_script->get_instance_id(); - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +void VisualScriptPropertySelector::SearchRunner::_match_item(TreeItem *p_item, const String &p_text) { + float inverse_length = 1.f / float(p_text.length()); - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); + // Favor types where search term is a substring close to the start of the type. + float w = 0.5f; + int pos = p_text.findn(term); + float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w); + + // Favor shorter items: they resemble the search term more. + w = 0.1f; + score *= (1 - w) + w * (term.length() * inverse_length); + + if (match_highest_score == 0 || score > match_highest_score) { + matched_item = p_item; + match_highest_score = score; } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; +} - _update_search(); +void VisualScriptPropertySelector::SearchRunner::_add_class_doc(String class_name, String inherits, String category) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + class_doc.name = class_name; + class_doc.inherits = inherits; + class_doc.category = "VisualScriptNode/" + category; + class_doc.brief_description = category; + combined_docs.insert(class_doc.name, class_doc); } -void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting, bool clear_text) { - ERR_FAIL_COND(p_type == Variant::NIL); - base_type = ""; - selected = p_current; - type = p_type; - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +DocData::MethodDoc VisualScriptPropertySelector::SearchRunner::_get_method_doc(MethodInfo method_info) { + DocData::MethodDoc method_doc = DocData::MethodDoc(); + method_doc.name = method_info.name; + method_doc.return_type = Variant::get_type_name(method_info.return_val.type); + method_doc.description = "No description available"; + for (List<PropertyInfo>::Element *P = method_info.arguments.front(); P; P = P->next()) { + DocData::ArgumentDoc argument_doc = DocData::ArgumentDoc(); + argument_doc.name = P->get().name; + argument_doc.type = Variant::get_type_name(P->get().type); + method_doc.arguments.push_back(argument_doc); + } + return method_doc; +} - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_hierarchy(const ClassMatch &p_match) { + if (class_items.has(p_match.doc->name)) { + return class_items[p_match.doc->name]; } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; - _update_search(); + // Ensure parent nodes are created first. + TreeItem *parent = root_item; + if (p_match.doc->inherits != "") { + if (class_items.has(p_match.doc->inherits)) { + parent = class_items[p_match.doc->inherits]; + } else if (matches.has(p_match.doc->inherits)) { + ClassMatch &base_match = matches[p_match.doc->inherits]; + parent = _create_class_hierarchy(base_match); + } + } + + TreeItem *class_item = _create_class_item(parent, p_match.doc, !p_match.name); + class_items[p_match.doc->name] = class_item; + return class_item; } -void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting, bool clear_text) { - base_type = p_type; - selected = p_current; - type = Variant::NIL; - properties = false; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray) { + Ref<Texture2D> icon = empty_icon; + String text_0 = p_doc->name; + String text_1 = "Class"; + + String what = "Class"; + String details = p_doc->name; + if (p_doc->category.begins_with("VisualScriptCustomNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon("VisualScript", "EditorIcons"); + text_0 = path[path.size() - 1]; + text_1 = "VisualScriptCustomNode"; + what = "VisualScriptCustomNode"; + details = "CustomNode"; + } else if (p_doc->category.begins_with("VisualScriptNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon("VisualScript", "EditorIcons"); + text_0 = path[path.size() - 1]; + if (p_doc->category.begins_with("VisualScriptNode/deconstruct")) { + text_0 = "deconstruct " + text_0; + } + text_1 = "VisualScriptNode"; + what = "VisualScriptNode"; + details = p_doc->name; + + if (path.size() == 1) { + if (path[0] == "functions" || path[0] == "operators") { + text_1 = "VisualScript"; + p_gray = true; + what = "no_result"; + details = ""; + } + } - show_window(.5f); - if (clear_text) { - search_box->set_text(""); } else { - search_box->select_all(); + if (p_doc->name.is_quoted()) { + text_0 = p_doc->name.unquote().get_file(); + if (ui_service->has_theme_icon(p_doc->inherits, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->inherits, "EditorIcons"); + } + } else if (ui_service->has_theme_icon(p_doc->name, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->name, "EditorIcons"); + } else if (ClassDB::class_exists(p_doc->name) && ClassDB::is_parent_class(p_doc->name, "Object")) { + icon = ui_service->get_theme_icon(SNAME("Object"), SNAME("EditorIcons")); + } + } + String tooltip = p_doc->brief_description.strip_edges(); + + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text_0); + item->set_text(1, TTR(text_1)); + item->set_tooltip(0, tooltip); + item->set_tooltip(1, tooltip); + item->set_metadata(0, details); + item->set_metadata(1, what); + if (p_gray) { + item->set_custom_color(0, disabled_color); + item->set_custom_color(1, disabled_color); } - search_box->grab_focus(); - seq_connect = true; - connecting = p_connecting; - _update_search(); + _match_item(item, p_doc->name); + + return item; } -void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting, const String &p_basetype, bool clear_text) { - base_type = p_basetype; - selected = p_current; - type = Variant::NIL; - properties = true; - visual_script_generic = false; - instance = p_instance; - virtuals_only = false; +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } + } + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_doc->name, p_text, TTRC("Method"), "method", tooltip, p_doc->description); +} - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_doc->name, p_doc->name, TTRC("Signal"), "signal", tooltip, p_doc->description); +} - _update_search(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc) { + String tooltip = p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_doc->name, p_doc->name, TTRC("Constant"), "constant", tooltip, p_doc->description); } -void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = ""; - type = Variant::NIL; - properties = true; - visual_script_generic = true; - instance = nullptr; - virtuals_only = false; - show_window(.5f); - if (clear_text) { - search_box->set_text(""); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + tooltip += "\n " + p_class_doc->name + "." + p_doc->setter + "(value) setter"; + tooltip += "\n " + p_class_doc->name + "." + p_doc->getter + "() getter"; + return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_doc->name, p_doc->name, TTRC("Property"), "property", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_doc->name, p_doc->name, TTRC("Theme Property"), "theme_item", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description) { + Ref<Texture2D> icon; + String text; + if (search_flags & SEARCH_SHOW_HIERARCHY) { + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_text; } else { - search_box->select_all(); + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_class_name + "." + p_text; } - search_box->grab_focus(); - connecting = p_connecting; - _update_search(); -} + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text); + item->set_text(1, TTRGET(p_type)); + item->set_tooltip(0, p_tooltip); + item->set_tooltip(1, p_tooltip); + item->set_metadata(0, p_class_name + ":" + p_name); + item->set_metadata(1, "class_" + p_metatype); + item->set_meta("description", p_description); -void VisualScriptPropertySelector::show_window(float p_screen_ratio) { - popup_centered_ratio(p_screen_ratio); + _match_item(item, p_name); + + return item; } -void VisualScriptPropertySelector::_bind_methods() { - ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +bool VisualScriptPropertySelector::SearchRunner::work(uint64_t slot) { + // Return true when the search has been completed, otherwise false. + const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot; + while (!_slice()) { + if (OS::get_singleton()->get_ticks_usec() > until) { + return false; + } + } + return true; } -VisualScriptPropertySelector::VisualScriptPropertySelector() { - vbc = memnew(VBoxContainer); - add_child(vbc); - //set_child_rect(vbc); - search_box = memnew(LineEdit); - vbc->add_margin_child(TTR("Search:"), search_box); - search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_text_changed)); - search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); - search_options = memnew(Tree); - vbc->add_margin_child(TTR("Matches:"), search_options, true); - get_ok_button()->set_text(TTR("Open")); - get_ok_button()->set_disabled(true); - register_text_enter(search_box); - set_hide_on_ok(false); - search_options->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); - search_options->connect("cell_selected", callable_mp(this, &VisualScriptPropertySelector::_item_selected)); - search_options->set_hide_root(true); - search_options->set_hide_folding(true); - virtuals_only = false; - seq_connect = false; - help_bit = memnew(EditorHelpBit); - vbc->add_margin_child(TTR("Description:"), help_bit); - help_bit->connect("request_hide", callable_mp(this, &VisualScriptPropertySelector::_hide_requested)); - search_options->set_columns(3); - search_options->set_column_expand(1, false); - search_options->set_column_expand(2, false); +VisualScriptPropertySelector::SearchRunner::SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree) : + selector_ui(p_selector_ui), + ui_service(p_selector_ui->vbox), + results_tree(p_results_tree), + term(p_selector_ui->search_box->get_text()), + empty_icon(ui_service->get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))), + disabled_color(ui_service->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))) { } diff --git a/modules/visual_script/editor/visual_script_property_selector.h b/modules/visual_script/editor/visual_script_property_selector.h index 9e065548a0..6b5112f1af 100644 --- a/modules/visual_script/editor/visual_script_property_selector.h +++ b/modules/visual_script/editor/visual_script_property_selector.h @@ -31,6 +31,7 @@ #ifndef VISUALSCRIPT_PROPERTYSELECTOR_H #define VISUALSCRIPT_PROPERTYSELECTOR_H +#include "../visual_script.h" #include "editor/editor_help.h" #include "editor/property_editor.h" #include "scene/gui/rich_text_label.h" @@ -38,15 +39,56 @@ class VisualScriptPropertySelector : public ConfirmationDialog { GDCLASS(VisualScriptPropertySelector, ConfirmationDialog); + enum SearchFlags { + SEARCH_CLASSES = 1 << 0, + SEARCH_CONSTRUCTORS = 1 << 1, + SEARCH_METHODS = 1 << 2, + SEARCH_OPERATORS = 1 << 3, + SEARCH_SIGNALS = 1 << 4, + SEARCH_CONSTANTS = 1 << 5, + SEARCH_PROPERTIES = 1 << 6, + SEARCH_THEME_ITEMS = 1 << 7, + SEARCH_VISUAL_SCRIPT_NODES = 1 << 8, + SEARCH_ALL = SEARCH_CLASSES | SEARCH_CONSTRUCTORS | SEARCH_METHODS | SEARCH_OPERATORS | SEARCH_SIGNALS | SEARCH_CONSTANTS | SEARCH_PROPERTIES | SEARCH_THEME_ITEMS, + SEARCH_CASE_SENSITIVE = 1 << 29, + SEARCH_SHOW_HIERARCHY = 1 << 30, + }; + + enum ScopeFlags { + SCOPE_BASE = 1 << 0, + SCOPE_INHERITERS = 1 << 1, + SCOPE_UNRELATED = 1 << 2, + SCOPE_RELATED = SCOPE_BASE | SCOPE_INHERITERS, + SCOPE_ALL = SCOPE_BASE | SCOPE_INHERITERS | SCOPE_UNRELATED + }; + LineEdit *search_box; - Tree *search_options; - void _text_changed(const String &p_newtext); - void _sbox_input(const Ref<InputEvent> &p_ie); - void _update_search(); + Button *case_sensitive_button; + Button *hierarchy_button; + + Button *search_visual_script_nodes; + Button *search_classes; + Button *search_operators; + + Button *search_methods; + Button *search_signals; + Button *search_constants; + Button *search_properties; + Button *search_theme_items; - void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); - void get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box); + OptionButton *scope_combo; + Tree *results_tree; + + class SearchRunner; + Ref<SearchRunner> search_runner; + + void _update_icons(); + + void _sbox_input(const Ref<InputEvent> &p_ie); + void _update_results_i(int p_int); + void _update_results_s(String p_string); + void _update_results(); void _confirmed(); void _item_selected(); @@ -54,38 +96,124 @@ class VisualScriptPropertySelector : public ConfirmationDialog { EditorHelpBit *help_bit; - bool properties; - bool visual_script_generic; - bool connecting; + bool properties = false; + bool visual_script_generic = false; + bool connecting = false; String selected; Variant::Type type; String base_type; + String base_script; ObjectID script; Object *instance; - bool virtuals_only; - bool seq_connect; - VBoxContainer *vbc; - - Vector<Variant::Type> type_filter; + bool virtuals_only = false; + VBoxContainer *vbox; protected: void _notification(int p_what); static void _bind_methods(); public: - void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); - void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true, bool clear_text = true); - void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true, const String &p_basetype = "", bool clear_text = true); - void select_from_visual_script(const String &p_base, const bool p_connecting = true, bool clear_text = true); + void select_method_from_base_type(const String &p_base, const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_base_type(const String &p_base, const String &p_base_script = "", bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_script(const Ref<Script> &p_script, const bool p_connecting = true, bool clear_text = true); + void select_from_basic_type(Variant::Type p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_action(const String &p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_instance(Object *p_instance, const bool p_connecting = true, bool clear_text = true); + void select_from_visual_script(const Ref<Script> &p_script, bool clear_text = true); void show_window(float p_screen_ratio); - void set_type_filter(const Vector<Variant::Type> &p_type_filter); - VisualScriptPropertySelector(); }; +class VisualScriptPropertySelector::SearchRunner : public RefCounted { + enum Phase { + PHASE_INIT, + PHASE_MATCH_CLASSES_INIT, + PHASE_NODE_CLASSES_INIT, + PHASE_NODE_CLASSES_BUILD, + PHASE_MATCH_CLASSES, + PHASE_CLASS_ITEMS_INIT, + PHASE_CLASS_ITEMS, + PHASE_MEMBER_ITEMS_INIT, + PHASE_MEMBER_ITEMS, + PHASE_SELECT_MATCH, + PHASE_MAX + }; + int phase = 0; + + struct ClassMatch { + DocData::ClassDoc *doc; + bool name = false; + String category = ""; + Vector<DocData::MethodDoc *> constructors; + Vector<DocData::MethodDoc *> methods; + Vector<DocData::MethodDoc *> operators; + Vector<DocData::MethodDoc *> signals; + Vector<DocData::ConstantDoc *> constants; + Vector<DocData::PropertyDoc *> properties; + Vector<DocData::ThemeItemDoc *> theme_properties; + + bool required() { + return name || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size(); + } + }; + + VisualScriptPropertySelector *selector_ui; + Control *ui_service; + Tree *results_tree; + String term; + int search_flags; + int scope_flags; + + Ref<Texture2D> empty_icon; + Color disabled_color; + + Map<String, DocData::ClassDoc>::Element *iterator_doc = nullptr; + Map<String, ClassMatch> matches; + Map<String, ClassMatch>::Element *iterator_match = nullptr; + TreeItem *root_item = nullptr; + Map<String, TreeItem *> class_items; + TreeItem *matched_item = nullptr; + float match_highest_score = 0; + + Map<String, DocData::ClassDoc> combined_docs; + List<String> vs_nodes; + + bool _is_class_disabled_by_feature_profile(const StringName &p_class); + bool _is_class_disabled_by_scope(const StringName &p_class); + + bool _slice(); + bool _phase_init(); + bool _phase_match_classes_init(); + bool _phase_node_classes_init(); + bool _phase_node_classes_build(); + bool _phase_match_classes(); + bool _phase_class_items_init(); + bool _phase_class_items(); + bool _phase_member_items_init(); + bool _phase_member_items(); + bool _phase_select_match(); + + bool _match_string(const String &p_term, const String &p_string) const; + bool _match_visual_script(DocData::ClassDoc &class_doc); + bool _match_is_hidden(DocData::ClassDoc &class_doc); + void _match_item(TreeItem *p_item, const String &p_text); + void _add_class_doc(String class_name, String inherits, String category); + DocData::MethodDoc _get_method_doc(MethodInfo method_info); + TreeItem *_create_class_hierarchy(const ClassMatch &p_match); + TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray); + TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc); + TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc); + TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc); + TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc); + TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc); + TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description); + +public: + bool work(uint64_t slot = 100000); + + SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree); +}; + #endif // VISUALSCRIPT_PROPERTYSELECTOR_H diff --git a/modules/visual_script/icons/VisualScript.svg b/modules/visual_script/icons/VisualScript.svg index 2352ba5d87..bc698247c9 100644 --- a/modules/visual_script/icons/VisualScript.svg +++ b/modules/visual_script/icons/VisualScript.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><ellipse cx="3" cy="1039.4" fill="#6e6e6e"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm-4 9v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4zm8 0a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><ellipse cx="3" cy="1039.4" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm-4 9v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4zm8 0a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg> diff --git a/modules/visual_script/icons/VisualScriptInternal.svg b/modules/visual_script/icons/VisualScriptInternal.svg index ea83047a29..8ab39ad929 100644 --- a/modules/visual_script/icons/VisualScriptInternal.svg +++ b/modules/visual_script/icons/VisualScriptInternal.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="3" cy="3.000024" fill="#6e6e6e" r="0"/><path d="m11 10a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0"/><path d="m3 10v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4z" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2z" fill="none" stroke="#e0e0e0"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="3" cy="3.000024" fill="#e0e0e0" r="0"/><path d="m11 10a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0"/><path d="m3 10v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4z" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2z" fill="none" stroke="#e0e0e0"/></svg> diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 99e75f9289..fd55796a66 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -964,7 +964,7 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in return; } - if (p_inputs[0]->is_ref()) { + if (p_inputs[0]->is_ref_counted()) { REF r = *p_inputs[0]; if (!r.is_valid()) { return; diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 2abbd19e12..e8942b9788 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -328,6 +328,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { }; case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -364,9 +365,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -374,13 +377,13 @@ Error VisualScriptExpression::_get_token(Token &r_token) { r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { _set_error("Malformed hex constant in string"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } char32_t v; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -403,12 +406,46 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; @@ -420,7 +457,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { break; } - if (cchar >= '0' && cchar <= '9') { + if (is_digit(cchar)) { //a number String num; @@ -439,7 +476,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { while (true) { switch (reading) { case READING_INT: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { //pass } else if (c == '.') { reading = READING_DEC; @@ -452,7 +489,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_DEC: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; @@ -462,7 +499,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_EXP: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { exp_beg = true; } else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) { @@ -495,11 +532,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } return OK; - } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') { + } else if (is_ascii_char(cchar) || cchar == '_') { String id; bool first = true; - while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) { + while (is_ascii_char(cchar) || cchar == '_' || (!first && is_digit(cchar))) { id += String::chr(cchar); cchar = GET_CHAR(); first = false; diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index cc18d48dd8..ef6c1ecdb9 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -912,7 +912,7 @@ int VisualScriptPropertySet::get_output_sequence_port_count() const { } bool VisualScriptPropertySet::has_input_sequence_port() const { - return 1; + return true; } Node *VisualScriptPropertySet::_get_base_node() const { diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 618fe14137..416a674435 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -33,8 +33,7 @@ <method name="create_data_channel"> <return type="WebRTCDataChannel" /> <argument index="0" name="label" type="String" /> - <argument index="1" name="options" type="Dictionary" default="{ -}" /> + <argument index="1" name="options" type="Dictionary" default="{}" /> <description> Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW]. There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code]. @@ -70,8 +69,7 @@ </method> <method name="initialize"> <return type="int" enum="Error" /> - <argument index="0" name="configuration" type="Dictionary" default="{ -}" /> + <argument index="0" name="configuration" type="Dictionary" default="{}" /> <description> Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection. Valid [code]options[/code] are: @@ -119,7 +117,7 @@ </methods> <signals> <signal name="data_channel_received"> - <argument index="0" name="channel" type="Object" /> + <argument index="0" name="channel" type="WebRTCDataChannel" /> <description> Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel]. diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 54d4f57eef..e6a8dcc31a 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -38,11 +38,11 @@ #include "webrtc_peer_connection_extension.h" void register_webrtc_types() { -#define _SET_HINT(NAME, _VAL_, _MAX_) \ - GLOBAL_DEF(NAME, _VAL_); \ +#define SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); - _SET_HINT(WRTC_IN_BUF, 64, 4096); + SET_HINT(WRTC_IN_BUF, 64, 4096); ClassDB::register_custom_instance_class<WebRTCPeerConnection>(); GDREGISTER_CLASS(WebRTCPeerConnectionExtension); @@ -51,6 +51,8 @@ void register_webrtc_types() { GDREGISTER_CLASS(WebRTCDataChannelExtension); GDREGISTER_CLASS(WebRTCMultiplayerPeer); + +#undef SET_HINT } void unregister_webrtc_types() {} diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 96cf518c06..7fdf26d3cd 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -69,7 +69,7 @@ void WebRTCPeerConnection::_bind_methods() { ADD_SIGNAL(MethodInfo("session_description_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp"))); ADD_SIGNAL(MethodInfo("ice_candidate_created", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name"))); - ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel"))); + ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel", PROPERTY_HINT_RESOURCE_TYPE, "WebRTCDataChannel"))); BIND_ENUM_CONSTANT(STATE_NEW); BIND_ENUM_CONSTANT(STATE_CONNECTING); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index bccbf88417..bebb198126 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -124,17 +124,17 @@ bool WSLClient::_verify_headers(String &r_protocol) { } } -#define _WSL_CHECK(NAME, VALUE) \ +#define WSL_CHECK(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define _WSL_CHECK_NC(NAME, VALUE) \ +#define WSL_CHECK_NC(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); - _WSL_CHECK("connection", "upgrade"); - _WSL_CHECK("upgrade", "websocket"); - _WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); -#undef _WSL_CHECK_NC -#undef _WSL_CHECK + WSL_CHECK("connection", "upgrade"); + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); +#undef WSL_CHECK_NC +#undef WSL_CHECK if (_protocols.size() == 0) { // We didn't request a custom protocol ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); @@ -163,22 +163,24 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _peer = Ref<WSLPeer>(memnew(WSLPeer)); if (p_host.is_valid_ip_address()) { - ip_candidates.clear(); - ip_candidates.push_back(IPAddress(p_host)); + _ip_candidates.push_back(IPAddress(p_host)); } else { - ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host); - } - - ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER); - - String port = ""; - if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { - port = ":" + itos(p_port); + // Queue hostname for resolution. + _resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host); + ERR_FAIL_COND_V(_resolver_id == IP::RESOLVER_INVALID_ID, ERR_INVALID_PARAMETER); + // Check if it was found in cache. + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } } - Error err = ERR_BUG; // Should be at least one entry. - while (ip_candidates.size() > 0) { - err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port); + // We assume OK while hostname resultion is pending. + Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED; + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port); if (err == OK) { break; } @@ -200,8 +202,11 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } _key = WSLPeer::generate_key(); - // TODO custom extra headers (allow overriding this too?) String request = "GET " + p_path + " HTTP/1.1\r\n"; + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } request += "Host: " + p_host + port + "\r\n"; request += "Upgrade: websocket\r\n"; request += "Connection: Upgrade\r\n"; @@ -231,6 +236,30 @@ int WSLClient::get_max_packet_size() const { } void WSLClient::poll() { + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_WAITING) { + return; + } + // Anything else is either a candidate or a failure. + Error err = FAILED; + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port); + if (err == OK) { + break; + } + } + } + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + } if (_peer->is_connected_to_host()) { _peer->poll(); if (!_peer->is_connected_to_host()) { @@ -251,7 +280,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { - ip_candidates.clear(); + _ip_candidates.clear(); Ref<StreamPeerSSL> ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -282,9 +311,9 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: - while (ip_candidates.size() > 0) { + while (_ip_candidates.size() > 0) { _tcp->disconnect_from_host(); - if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) { + if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) { return; } } @@ -307,7 +336,7 @@ MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { return CONNECTION_CONNECTED; } - if (_tcp->is_connected_to_host()) { + if (_tcp->is_connected_to_host() || _resolver_id != IP::RESOLVER_INVALID_ID) { return CONNECTION_CONNECTING; } @@ -330,7 +359,12 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { memset(_resp_buf, 0, sizeof(_resp_buf)); _resp_pos = 0; - ip_candidates.clear(); + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } + + _ip_candidates.clear(); } IPAddress WSLClient::get_connected_host() const { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index 4839d7ab9b..d846e6be00 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -63,10 +63,11 @@ private: String _key; String _host; - int _port; - Array ip_candidates; + uint16_t _port; + Array _ip_candidates; Vector<String> _protocols; bool _use_ssl = false; + IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID; void _do_handshake(); bool _verify_headers(String &r_protocol); diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 31175c5779..eadd7ef7ac 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -58,17 +58,17 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols, St headers[name] = value; } } -#define _WSL_CHECK(NAME, VALUE) \ +#define WSL_CHECK(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define _WSL_CHECK_EX(NAME) \ +#define WSL_CHECK_EX(NAME) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); - _WSL_CHECK("upgrade", "websocket"); - _WSL_CHECK("sec-websocket-version", "13"); - _WSL_CHECK_EX("sec-websocket-key"); - _WSL_CHECK_EX("connection"); -#undef _WSL_CHECK_EX -#undef _WSL_CHECK + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK("sec-websocket-version", "13"); + WSL_CHECK_EX("sec-websocket-key"); + WSL_CHECK_EX("connection"); +#undef WSL_CHECK_EX +#undef WSL_CHECK key = headers["sec-websocket-key"]; if (headers.has("sec-websocket-protocol")) { Vector<String> protos = headers["sec-websocket-protocol"].split(","); diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 8eb0d8ff90..86b857f72c 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -385,7 +385,7 @@ CameraMatrix WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p return eye; } -Vector<BlitToScreen> WebXRInterfaceJS::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { +Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; if (!initialized) { diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 8eddfbe484..31858194f6 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -88,7 +88,7 @@ public: virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; - virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; |