diff options
Diffstat (limited to 'modules')
56 files changed, 733 insertions, 457 deletions
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 5b451fbf6b..5605255367 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -32,6 +32,16 @@ #include "core/io/file_access_memory.h" +static uint8_t get_mask_width(uint16_t mask) { + // Returns number of ones in the binary value of the parameter: mask. + // Uses a Simple pop_count. + uint8_t c = 0u; + for (; mask != 0u; mask &= mask - 1u) { + c++; + } + return c; +} + Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, const uint8_t *p_buffer, const uint8_t *p_color_buffer, @@ -71,9 +81,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width))); ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE, vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height))); - - } else if (bits_per_pixel == 16) { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported."); } // Image data (might be indexed) @@ -96,7 +103,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, // The actual data traversal is determined by // the data width in case of 8/4/2/1 bit images - const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes; + const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes; const uint8_t *line = p_buffer + (line_width * (height - 1)); const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset; @@ -149,6 +156,34 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, index += 1; line_ptr += 1; } break; + case 16: { + uint16_t rgb = (static_cast<uint16_t>(line_ptr[1]) << 8) | line_ptr[0]; + // A1R5G5B5/X1R5G5B5 => uint16_t + // [A/X]1R5G2 | G3B5 => uint8_t | uint8_t + uint8_t ba = (rgb & p_header.bmp_bitfield.alpha_mask) >> p_header.bmp_bitfield.alpha_offset; // Alpha 0b 1000 ... + uint8_t b0 = (rgb & p_header.bmp_bitfield.red_mask) >> p_header.bmp_bitfield.red_offset; // Red 0b 0111 1100 ... + uint8_t b1 = (rgb & p_header.bmp_bitfield.green_mask) >> p_header.bmp_bitfield.green_offset; // Green 0b 0000 0011 1110 ... + uint8_t b2 = (rgb & p_header.bmp_bitfield.blue_mask); // >> p_header.bmp_bitfield.blue_offset; // Blue 0b ... 0001 1111 + + // Next we apply some color scaling going from a variable value space to a 256 value space. + // This may be simplified some but left as is for legibility. + // float scaled_value = unscaled_value * byte_max_value / color_channel_maxium_value + rounding_offset; + float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f; + float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f; + float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f; + write_buffer[index + 0] = static_cast<uint8_t>(f0); // R + write_buffer[index + 1] = static_cast<uint8_t>(f1); // G + write_buffer[index + 2] = static_cast<uint8_t>(f2); // B + + if (p_header.bmp_bitfield.alpha_mask_width > 0) { + write_buffer[index + 3] = ba * 0xFF; // Alpha value(Always true or false so no scaling) + } else { + write_buffer[index + 3] = 0xFF; // No Alpha channel, Show everything. + } + + index += 4; + line_ptr += 2; + } break; case 24: { write_buffer[index + 2] = line_ptr[0]; write_buffer[index + 1] = line_ptr[1]; @@ -253,13 +288,32 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField bmp_header.bmp_info_header.bmp_important_colors = f->get_32(); switch (bmp_header.bmp_info_header.bmp_compression) { + case BI_BITFIELDS: { + bmp_header.bmp_bitfield.red_mask = f->get_32(); + bmp_header.bmp_bitfield.green_mask = f->get_32(); + bmp_header.bmp_bitfield.blue_mask = f->get_32(); + bmp_header.bmp_bitfield.alpha_mask = f->get_32(); + + bmp_header.bmp_bitfield.red_mask_width = get_mask_width(bmp_header.bmp_bitfield.red_mask); + bmp_header.bmp_bitfield.green_mask_width = get_mask_width(bmp_header.bmp_bitfield.green_mask); + bmp_header.bmp_bitfield.blue_mask_width = get_mask_width(bmp_header.bmp_bitfield.blue_mask); + bmp_header.bmp_bitfield.alpha_mask_width = get_mask_width(bmp_header.bmp_bitfield.alpha_mask); + + bmp_header.bmp_bitfield.alpha_offset = bmp_header.bmp_bitfield.red_mask_width + bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width; + bmp_header.bmp_bitfield.red_offset = bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width; + bmp_header.bmp_bitfield.green_offset = bmp_header.bmp_bitfield.blue_mask_width; + + bmp_header.bmp_bitfield.red_max = (1 << bmp_header.bmp_bitfield.red_mask_width) - 1; + bmp_header.bmp_bitfield.green_max = (1 << bmp_header.bmp_bitfield.green_mask_width) - 1; + bmp_header.bmp_bitfield.blue_max = (1 << bmp_header.bmp_bitfield.blue_mask_width) - 1; + } break; case BI_RLE8: case BI_RLE4: case BI_CMYKRLE8: case BI_CMYKRLE4: { // Stop parsing. ERR_FAIL_V_MSG(ERR_UNAVAILABLE, - vformat("Compressed BMP files are not supported: %s", f->get_path())); + vformat("RLE compressed BMP files are not yet supported: %s", f->get_path())); } break; } // Don't rely on sizeof(bmp_file_header) as structure padding diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h index 08b9115c87..cb51d7f0de 100644 --- a/modules/bmp/image_loader_bmp.h +++ b/modules/bmp/image_loader_bmp.h @@ -74,6 +74,25 @@ protected: uint32_t bmp_colors_used = 0; uint32_t bmp_important_colors = 0; } bmp_info_header; + + struct bmp_bitfield_s { + uint16_t alpha_mask = 0x8000; + uint16_t red_mask = 0x7C00; + uint16_t green_mask = 0x03E0; + uint16_t blue_mask = 0x001F; + uint16_t alpha_mask_width = 1u; + uint16_t red_mask_width = 5u; + uint16_t green_mask_width = 5u; + uint16_t blue_mask_width = 5u; + uint8_t alpha_offset = 15u; // Used for bit shifting. + uint8_t red_offset = 10u; // Used for bit shifting. + uint8_t green_offset = 5u; // Used for bit shifting. + //uint8_t blue_offset = 0u; // Always LSB aligned no shifting needed. + //uint8_t alpha_max = 1u; // Always boolean or on, so no scaling needed. + uint8_t red_max = 32u; // Used for color space scaling. + uint8_t green_max = 32u; // Used for color space scaling. + uint8_t blue_max = 32u; // Used for color space scaling. + } bmp_bitfield; }; static Error convert_to_image(Ref<Image> p_image, diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd new file mode 100644 index 0000000000..c79eeb91ec --- /dev/null +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -0,0 +1,17 @@ +# meta-description: Base template for rich text effects + +@tool +class_name _CLASS_ +extends _BASE_ + + +# To use this effect: +# - Enable BBCode on a RichTextLabel. +# - Register this effect on the label. +# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. +var bbcode := "_CLASS_" + + +func _process_custom_fx(char_fx: CharFXTransform) -> bool: + var param: float = char_fx.env.get("param", 1.0) + return true diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a2cab25ce8..aa91f8fe8d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -479,7 +479,24 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } if (look_class->has_member(name)) { resolve_class_member(look_class, name, id); - base = look_class->get_member(name).get_datatype(); + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + GDScriptParser::DataType member_datatype = member.get_datatype(); + + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: + break; // OK. + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Constant "%s" is not a preloaded script or class.)", name), id); + return ERR_PARSE_ERROR; + } + break; + default: + push_error(vformat(R"(Cannot use %s "%s" in extends chain.)", member.get_type_name(), name), id); + return ERR_PARSE_ERROR; + } + + base = member_datatype; found = true; break; } @@ -506,6 +523,9 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!id_type.is_set()) { push_error(vformat(R"(Could not find nested type "%s".)", id->name), id); return ERR_PARSE_ERROR; + } else if (id_type.kind != GDScriptParser::DataType::SCRIPT && id_type.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Identifier "%s" is not a preloaded script or class.)", id->name), id); + return ERR_PARSE_ERROR; } base = id_type; @@ -4546,7 +4566,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.set_container_element_type(elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. - if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) { + if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { if (CoreConstants::is_global_enum(p_property.class_name)) { result = make_global_enum_type(p_property.class_name, StringName(), false); result.is_constant = false; @@ -4558,6 +4578,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } } } + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. } } return result; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f3a86522ae..be33c7c591 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,6 +54,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); + p_delimiters->push_back("''' '''"); } bool GDScriptLanguage::is_using_templates() { @@ -73,9 +74,11 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri .replace(": String", "") .replace(": Array[String]", "") .replace(": float", "") + .replace(": CharFXTransform", "") .replace(":=", "=") .replace(" -> String", "") .replace(" -> int", "") + .replace(" -> bool", "") .replace(" -> void", ""); } @@ -578,29 +581,34 @@ static int _get_enum_constant_location(StringName p_class, StringName p_enum_con // END LOCATION METHODS -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { - if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { - String enum_name = p_info.class_name; - if (!enum_name.contains(".")) { - return enum_name; +static String _trim_parent_class(const String &p_class, const String &p_base_class) { + if (p_base_class.is_empty()) { + return p_class; + } + Vector<String> names = p_class.split(".", false, 1); + if (names.size() == 2) { + String first = names[0]; + String rest = names[1]; + if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { + return rest; } - return enum_name.get_slice(".", 1); } + return p_class; +} - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); - } +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "") { + String class_name = p_info.class_name; + bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM; + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. - if (p_info.type == Variant::OBJECT) { - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - return p_info.hint_string; - } else { - return p_info.class_name.operator String(); + if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) { + if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) { + return class_name; } - } - if (p_info.type == Variant::NIL) { + return _trim_parent_class(class_name, p_base_class); + } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { + return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { @@ -3001,26 +3009,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c arg = arg.substr(0, arg.find(":")); } method_hint += arg; - if (use_type_hint && mi.arguments[i].type != Variant::NIL) { - method_hint += ": "; - if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { - method_hint += mi.arguments[i].class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.arguments[i].type); - } + if (use_type_hint) { + method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name); } } } method_hint += ")"; - if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { - method_hint += " -> "; - if (mi.return_val.type == Variant::NIL) { - method_hint += "void"; - } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { - method_hint += mi.return_val.class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.return_val.type); - } + if (use_type_hint) { + method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name); } method_hint += ":"; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e2a37ab6e9..8a49398f1a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1343,7 +1343,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod default_used = true; } else { if (default_used) { - push_error("Cannot have a mandatory parameters after optional parameters."); + push_error("Cannot have mandatory parameters after optional parameters."); continue; } } @@ -1439,27 +1439,32 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali valid = false; } - if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + push_multiline(true); + advance(); // Arguments. push_completion_call(annotation); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); - if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { - push_multiline(true); - int argument_index = 0; - do { - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); - set_last_completion_call_arg(argument_index++); - ExpressionNode *argument = parse_expression(false); - if (argument == nullptr) { - valid = false; - continue; - } - annotation->arguments.push_back(argument); - } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); + int argument_index = 0; + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } - consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); - } + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + set_last_completion_call_arg(argument_index++); + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + push_error("Expected expression as the annotation argument."); + valid = false; + continue; + } + annotation->arguments.push_back(argument); + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); pop_completion_call(); } complete_extents(annotation); @@ -1475,7 +1480,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali void GDScriptParser::clear_unused_annotations() { for (const AnnotationNode *annotation : annotation_stack) { - push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); + push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation); } annotation_stack.clear(); @@ -1817,7 +1822,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { n_for->list = parse_expression(false); if (!n_for->list) { - push_error(R"(Expected a list or range after "in".)"); + push_error(R"(Expected iterable after "in".)"); } consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); @@ -2820,6 +2825,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * attribute->base = p_previous_operand; + if (current.is_node_name()) { + current.type = GDScriptTokenizer::Token::IDENTIFIER; + } if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { complete_extents(attribute); return attribute; @@ -3856,7 +3864,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; variable->export_info.hint_string = export_type.to_string(); } else { - push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable); + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); return false; } @@ -3901,8 +3909,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = enum_hint_string; } break; default: - // TODO: Allow custom user resources. - push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); break; } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ef59a07f1a..0cb8e3a2af 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -183,7 +183,7 @@ String GDScriptWarning::get_message() const { return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]); } case ONREADY_WITH_EXPORT: { - return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)"; + return R"("@onready" will set the default value after "@export" takes effect and will override it.)"; } case WARNING_MAX: break; // Can't happen, but silences warning diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 2ed444c7ad..07f9465516 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -337,7 +337,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN symbol.kind = lsp::SymbolKind::Variable; symbol.name = parameter->identifier->name; symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column); symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); symbol.uri = uri; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index acd75f039a..112db4df3a 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -46,7 +46,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { while (true) { if (req_pos >= LSP_MAX_BUFFER_SIZE) { req_pos = 0; - ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Response header too big"); } Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) { @@ -237,6 +237,7 @@ void GDScriptLanguageProtocol::poll() { HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin(); while (E != clients.end()) { Ref<LSPeer> peer = E->value; + peer->connection->poll(); StreamPeerTCP::Status status = peer->connection->get_status(); if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { on_client_disconnected(E->key); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd new file mode 100644 index 0000000000..72af099158 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd @@ -0,0 +1,9 @@ +# GH-75870 + +const A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out new file mode 100644 index 0000000000..65d629a35b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Constant "A" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd new file mode 100644 index 0000000000..fe334f8cb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd @@ -0,0 +1,12 @@ +# GH-75870 + +class A: + const X = 1 + +const Y = A.X # A.X is now resolved. + +class B extends A.X: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out new file mode 100644 index 0000000000..951cfb1ea4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "X" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd new file mode 100644 index 0000000000..6574d4cf31 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd @@ -0,0 +1,9 @@ +# GH-75870 + +var A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out new file mode 100644 index 0000000000..7b39af6979 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot use variable "A" in extends chain. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out index ff184f9f04..f861d52f2b 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 3 >> ONREADY_WITH_EXPORT ->> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it. +>> "@onready" will set the default value after "@export" takes effect and will override it. warn diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd new file mode 100644 index 0000000000..271a831732 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd @@ -0,0 +1,4 @@ +@export_enum("A",, "B", "C") var a + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out new file mode 100644 index 0000000000..70eee5b39f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as the annotation argument. diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd new file mode 100644 index 0000000000..13c89a0a09 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -0,0 +1,48 @@ +extends Node + +@export_enum("A", "B", "C") var a0 +@export_enum("A", "B", "C",) var a1 + +@export_enum( + "A", + "B", + "C" +) var a2 + +@export_enum( + "A", + "B", + "C", +) var a3 + +@export +var a4: int + +@export() +var a5: int + +@export() var a6: int +@warning_ignore("onready_with_export") @onready @export var a7: int +@warning_ignore("onready_with_export") @onready() @export() var a8: int + +@warning_ignore("onready_with_export") +@onready +@export +var a9: int + +@warning_ignore("onready_with_export") +@onready() +@export() +var a10: int + +@warning_ignore("onready_with_export") +@onready() +@export() + +var a11: int + + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(property) diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out new file mode 100644 index 0000000000..3af0436c53 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.out @@ -0,0 +1,13 @@ +GDTEST_OK +{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd new file mode 100644 index 0000000000..87f9479812 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd @@ -0,0 +1,36 @@ +var dict = {} + +func test(): + dict.if = 1 + dict.elif = 1 + dict.else = 1 + dict.for = 1 + dict.while = 1 + dict.match = 1 + dict.break = 1 + dict.continue = 1 + dict.pass = 1 + dict.return = 1 + dict.class = 1 + dict.class_name = 1 + dict.extends = 1 + dict.is = 1 + dict.in = 1 + dict.as = 1 + dict.self = 1 + dict.signal = 1 + dict.func = 1 + dict.static = 1 + dict.const = 1 + dict.enum = 1 + dict.var = 1 + dict.breakpoint = 1 + dict.preload = 1 + dict.await = 1 + dict.yield = 1 + dict.assert = 1 + dict.void = 1 + dict.PI = 1 + dict.TAU = 1 + dict.INF = 1 + dict.NAN = 1 diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 3b8d2cc701..5f6ec5904c 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -34,9 +34,9 @@ #include "../gltf_document.h" -#include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" +#include "editor/gui/editor_file_dialog.h" String SceneExporterGLTFPlugin::get_name() const { return "ConvertGLTF2"; diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index a736e36c6a..2efaaa7d4d 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -37,10 +37,10 @@ #include "editor_import_blend_runner.h" #include "core/config/project_settings.h" -#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/gui/editor_file_dialog.h" #include "main/main.h" #include "scene/gui/line_edit.h" diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index d93485a800..7087c30688 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -2820,7 +2820,13 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { if (j == 0) { const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); for (int k = 0; k < targets.size(); k++) { - import_mesh->add_blend_shape(k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k)); + String bs_name; + if (k < target_names.size() && ((String)target_names[k]).size() != 0) { + bs_name = (String)target_names[k]; + } else { + bs_name = String("morph_") + itos(k); + } + import_mesh->add_blend_shape(bs_name); } } diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index bb5eb8e643..20f1aa357b 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -40,6 +40,8 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" #include "scene/main/window.h" diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 6c5c61acb9..b7e5b058c6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -317,16 +317,6 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build - try - { - // Make sure our packages are added to the fallback folder - NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); - } - catch (Exception e) - { - GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); - } - if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) return true; // Requested play from an external editor/IDE which already built the project diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index cf1b84e37f..8fe7d3c2d7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -34,16 +34,6 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build - try - { - // Make sure our packages are added to the fallback folder - NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); - } - catch (Exception e) - { - GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); - } - if (!BuildManager.BuildProjectBlocking("Debug")) return; // Build failed @@ -62,16 +52,6 @@ namespace GodotTools.Build if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build - try - { - // Make sure our packages are added to the fallback folder - NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); - } - catch (Exception e) - { - GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); - } - if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true)) return; // Build failed diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs deleted file mode 100644 index fe309b8102..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Xml; -using Godot; -using GodotTools.Internals; -using GodotTools.Shared; -using Directory = GodotTools.Utils.Directory; -using Environment = System.Environment; -using File = GodotTools.Utils.File; - -namespace GodotTools.Build -{ - public static class NuGetUtils - { - public const string GodotFallbackFolderName = "Godot Offline Packages"; - - public static string GodotFallbackFolderPath - => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder"); - - /// <summary> - /// Returns all the paths where the Godot.Offline.Config files can be found. - /// Does not determine whether the returned files exist or not. - /// </summary> - private static string[] GetAllGodotNuGetConfigFilePaths() - { - // Where to find 'NuGet/config/Godot.Offline.Config': - // - // - Mono/.NETFramework (standalone NuGet): - // Uses Environment.SpecialFolder.ApplicationData - // - Windows: '%APPDATA%' - // - Linux/macOS: '$HOME/.config' - // - CoreCLR (dotnet CLI NuGet): - // - Windows: '%APPDATA%' - // - Linux/macOS: '$DOTNET_CLI_HOME/.nuget' otherwise '$HOME/.nuget' - - string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - - const string configFileName = "Godot.Offline.Config"; - - if (Utils.OS.IsWindows) - { - // %APPDATA% for both - return new[] { Path.Combine(applicationData, "NuGet", "config", configFileName) }; - } - - var paths = new string[2]; - - // CoreCLR (dotnet CLI NuGet) - - string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); - if (!string.IsNullOrEmpty(dotnetCliHome)) - { - paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "config", configFileName); - } - else - { - string home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - throw new InvalidOperationException("Required environment variable 'HOME' is not set."); - paths[0] = Path.Combine(home, ".nuget", "NuGet", "config", configFileName); - } - - // Mono/.NETFramework (standalone NuGet) - - // ApplicationData is $HOME/.config on Linux/macOS - paths[1] = Path.Combine(applicationData, "NuGet", "config", configFileName); - - return paths; - } - - // nupkg extraction - // - // Exclude: (NuGet.Client -> NuGet.Packaging.PackageHelper.ExcludePaths) - // package/ - // _rels/ - // [Content_Types].xml - // - // Don't ignore files that begin with a dot (.) - // - // The nuspec is not lower case inside the nupkg but must be made lower case when extracted. - - /// <summary> - /// Adds the specified fallback folder to the Godot.Offline.Config files, - /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. - /// </summary> - public static void AddFallbackFolderToGodotNuGetConfigs(string name, string path) - { - // Make sure the fallback folder exists to avoid error: - // MSB4018: The "ResolvePackageAssets" task failed unexpectedly. - System.IO.Directory.CreateDirectory(path); - - foreach (string nuGetConfigPath in GetAllGodotNuGetConfigFilePaths()) - { - string defaultConfig = @$"<?xml version=""1.0"" encoding=""utf-8""?> -<configuration> - <fallbackPackageFolders> - <add key=""{name}"" value=""{path}"" /> - </fallbackPackageFolders> -</configuration> -"; - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); - System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM - } - } - - private static void AddPackageToFallbackFolder(string fallbackFolder, - string nupkgPath, string packageId, string packageVersion) - { - // dotnet CLI provides no command for this, but we can do it manually. - // - // - The expected structure is as follows: - // fallback_folder/ - // <package.name>/<version>/ - // <package.name>.<version>.nupkg - // <package.name>.<version>.nupkg.sha512 - // <package.name>.nuspec - // ... extracted nupkg files (check code for excluded files) ... - // - // - <package.name> and <version> must be in lower case. - // - The sha512 of the nupkg is base64 encoded. - // - We can get the nuspec from the nupkg which is a Zip file. - - string packageIdLower = packageId.ToLowerInvariant(); - string packageVersionLower = packageVersion.ToLowerInvariant(); - - string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); - string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); - string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); - string nupkgMetadataDestPath = Path.Combine(destDir, ".nupkg.metadata"); - - if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) - return; // Already added (for speed we don't check if every file is properly extracted) - - Directory.CreateDirectory(destDir); - - // Generate .nupkg.sha512 file - - byte[] hash = SHA512.HashData(File.ReadAllBytes(nupkgPath)); - string base64Hash = Convert.ToBase64String(hash); - File.WriteAllText(nupkgSha512DestPath, base64Hash); - - // Generate .nupkg.metadata file - // Spec: https://github.com/NuGet/Home/wiki/Nupkg-Metadata-File - - File.WriteAllText(nupkgMetadataDestPath, @$"{{ - ""version"": 2, - ""contentHash"": ""{base64Hash}"", - ""source"": null -}}"); - - // Extract nupkg - ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); - - // Copy .nupkg - File.Copy(nupkgPath, nupkgDestPath); - } - - private static readonly string[] NupkgExcludePaths = - { - "_rels/", - "package/", - "[Content_Types].xml" - }; - - private static void ExtractNupkg(string destDir, string nupkgPath, string packageId, string packageVersion) - { - // NOTE: Must use SimplifyGodotPath to make sure we don't extract files outside the destination directory. - - using (var archive = ZipFile.OpenRead(nupkgPath)) - { - // Extract .nuspec manually as it needs to be in lower case - - var nuspecEntry = archive.GetEntry(packageId + ".nuspec"); - - if (nuspecEntry == null) - throw new InvalidOperationException( - $"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); - - nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name - .ToLowerInvariant().SimplifyGodotPath())); - - // Extract the other package files - - foreach (var entry in archive.Entries) - { - // NOTE: SimplifyGodotPath() removes trailing slash and backslash, - // so we can't use the result to check if the entry is a directory. - - string entryFullName = entry.FullName.Replace('\\', '/'); - - // Check if the file must be ignored - if ( // Excluded files. - NupkgExcludePaths.Any(e => entryFullName.StartsWith(e, StringComparison.OrdinalIgnoreCase)) || - // Nupkg hash files and nupkg metadata files on all directory. - entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) || - entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) || - // Nuspec at root level. We already extracted it previously but in lower case. - !entryFullName.Contains('/') && entryFullName.EndsWith(".nuspec")) - { - continue; - } - - string entryFullNameSimplified = entryFullName.SimplifyGodotPath(); - string destFilePath = Path.Combine(destDir, entryFullNameSimplified); - bool isDir = entryFullName.EndsWith("/"); - - if (isDir) - { - Directory.CreateDirectory(destFilePath); - } - else - { - Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); - entry.ExtractToFile(destFilePath, overwrite: true); - } - } - } - } - - /// <summary> - /// Copies and extracts all the Godot bundled packages to the Godot NuGet fallback folder. - /// Does nothing if the packages were already copied. - /// </summary> - public static void AddBundledPackagesToFallbackFolder(string fallbackFolder) - { - GD.Print("Copying Godot Offline Packages..."); - - string nupkgsLocation = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "nupkgs"); - - void AddPackage(string packageId, string packageVersion) - { - string nupkgPath = Path.Combine(nupkgsLocation, $"{packageId}.{packageVersion}.nupkg"); - AddPackageToFallbackFolder(fallbackFolder, nupkgPath, packageId, packageVersion); - } - - foreach (var (packageId, packageVersion) in PackagesToAdd) - AddPackage(packageId, packageVersion); - } - - private static readonly (string packageId, string packageVersion)[] PackagesToAdd = - { - ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), - ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), - ("GodotSharp", GeneratedGodotNupkgsVersions.GodotSharp), - ("GodotSharpEditor", GeneratedGodotNupkgsVersions.GodotSharp), - }; - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index a284451a35..6837b617e0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -21,10 +21,22 @@ namespace GodotTools.Export private List<string> _tempFolders = new List<string>(); - public void RegisterExportSettings() + public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetExportOptions(EditorExportPlatform platform) { - // TODO: These would be better as export preset options, but that doesn't seem to be supported yet - GlobalDef("dotnet/export/include_scripts_content", false); + return new Godot.Collections.Array<Godot.Collections.Dictionary>() + { + new Godot.Collections.Dictionary() + { + { + "option", new Godot.Collections.Dictionary() + { + { "name", "dotnet/include_scripts_content" }, + { "type", (int)Variant.Type.Bool } + } + }, + { "default_value", false } + } + }; } private string _maybeLastExportError; @@ -44,7 +56,7 @@ namespace GodotTools.Export // TODO What if the source file is not part of the game's C# project - bool includeScriptsContent = (bool)ProjectSettings.GetSetting("dotnet/export/include_scripts_content"); + bool includeScriptsContent = (bool)GetOption("dotnet/include_scripts_content"); if (!includeScriptsContent) { diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs index 90d6eb960e..049cc58512 100644 --- a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -7,6 +7,7 @@ namespace GodotTools VisualStudioForMac, // Mac-only MonoDevelop, VsCode, - Rider + Rider, + CustomEditor } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index f50803af95..4e33b38ac2 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -25,6 +25,8 @@ namespace GodotTools public static class Settings { public const string ExternalEditor = "dotnet/editor/external_editor"; + public const string CustomExecPath = "dotnet/editor/custom_exec_path"; + public const string CustomExecPathArgs = "dotnet/editor/custom_exec_path_args"; public const string VerbosityLevel = "dotnet/build/verbosity_level"; public const string NoConsoleLogging = "dotnet/build/no_console_logging"; public const string CreateBinaryLog = "dotnet/build/create_binary_log"; @@ -133,22 +135,6 @@ namespace GodotTools } break; } - case MenuOptions.SetupGodotNugetFallbackFolder: - { - try - { - string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; - NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, - fallbackFolder); - NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); - } - catch (Exception e) - { - ShowErrorDialog("Failed to setup Godot NuGet Offline Packages: " + e.Message); - } - - break; - } default: throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); } @@ -168,7 +154,6 @@ namespace GodotTools private enum MenuOptions { CreateSln, - SetupGodotNugetFallbackFolder, } public void ShowErrorDialog(string message, string title = "Error") @@ -202,6 +187,66 @@ namespace GodotTools case ExternalEditorId.None: // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor. return Error.Unavailable; + case ExternalEditorId.CustomEditor: + { + string file = ProjectSettings.GlobalizePath(script.ResourcePath); + var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>(); + var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>(); + var args = new List<string>(); + var from = 0; + var numChars = 0; + var insideQuotes = false; + var hasFileFlag = false; + + execArgs = execArgs.ReplaceN("{line}", line.ToString()); + execArgs = execArgs.ReplaceN("{col}", col.ToString()); + execArgs = execArgs.StripEdges(true, true); + execArgs = execArgs.Replace("\\\\", "\\"); + + for (int i = 0; i < execArgs.Length; ++i) + { + if ((execArgs[i] == '"' && (i == 0 || execArgs[i - 1] != '\\')) && i != execArgs.Length - 1) + { + if (!insideQuotes) + { + from++; + } + insideQuotes = !insideQuotes; + } + else if ((execArgs[i] == ' ' && !insideQuotes) || i == execArgs.Length - 1) + { + if (i == execArgs.Length - 1 && !insideQuotes) + { + numChars++; + } + + var arg = execArgs.Substr(from, numChars); + if (arg.Contains("{file}")) + { + hasFileFlag = true; + } + + arg = arg.ReplaceN("{file}", file); + args.Add(arg); + + from = i + 1; + numChars = 0; + } + else + { + numChars++; + } + } + + if (!hasFileFlag) + { + args.Add(file); + } + + OS.RunProcess(execCommand, args); + + break; + } case ExternalEditorId.VisualStudio: { string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -480,6 +525,8 @@ namespace GodotTools // External editor settings EditorDef(Settings.ExternalEditor, Variant.From(ExternalEditorId.None)); + EditorDef(Settings.CustomExecPath, ""); + EditorDef(Settings.CustomExecPathArgs, ""); EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal)); EditorDef(Settings.NoConsoleLogging, false); EditorDef(Settings.CreateBinaryLog, false); @@ -491,20 +538,23 @@ namespace GodotTools settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + - $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; + $",JetBrains Rider:{(int)ExternalEditorId.Rider}" + + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } else if (OS.IsMacOS) { settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + - $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; + $",JetBrains Rider:{(int)ExternalEditorId.Rider}" + + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } else if (OS.IsUnixLike) { settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + - $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; + $",JetBrains Rider:{(int)ExternalEditorId.Rider}" + + $",Custom:{(int)ExternalEditorId.CustomEditor}"; } _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary @@ -515,6 +565,20 @@ namespace GodotTools ["hint_string"] = settingsHintStr }); + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = (int)Variant.Type.String, + ["name"] = Settings.CustomExecPath, + ["hint"] = (int)PropertyHint.GlobalFile, + }); + + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = (int)Variant.Type.String, + ["name"] = Settings.CustomExecPathArgs, + }); + _editorSettings.SetInitialValue(Settings.CustomExecPathArgs, "{file}", false); + var verbosityLevels = Enum.GetValues<VerbosityLevelId>().Select(level => $"{Enum.GetName(level)}:{(int)level}"); _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { @@ -530,20 +594,8 @@ namespace GodotTools // Export plugin var exportPlugin = new ExportPlugin(); AddExportPlugin(exportPlugin); - exportPlugin.RegisterExportSettings(); _exportPluginWeak = WeakRef(exportPlugin); - try - { - // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included - NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, - NuGetUtils.GodotFallbackFolderPath); - } - catch (Exception e) - { - GD.PushError("Failed to add Godot NuGet Offline Packages to NuGet.Config: " + e.Message); - } - BuildManager.Initialize(); RiderPathManager.Initialize(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 83621ce5af..5d1a2277f9 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -70,6 +70,8 @@ namespace GodotTools.Ides return "VisualStudioForMac"; case ExternalEditorId.MonoDevelop: return "MonoDevelop"; + case ExternalEditorId.CustomEditor: + return "CustomEditor"; default: throw new NotImplementedException(); } @@ -105,6 +107,7 @@ namespace GodotTools.Ides case ExternalEditorId.VisualStudio: case ExternalEditorId.VsCode: case ExternalEditorId.Rider: + case ExternalEditorId.CustomEditor: throw new NotSupportedException(); case ExternalEditorId.VisualStudioForMac: goto case ExternalEditorId.MonoDevelop; diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index 9e542828ee..1f707f1192 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -36,9 +36,9 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/scene_tree_editor.h" #include "editor/inspector_dock.h" #include "editor/property_selector.h" -#include "editor/scene_tree_editor.h" #include "scene/gui/dialogs.h" #include "scene/gui/separator.h" #include "scene/gui/tree.h" diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index 557d45b386..dd2c539c95 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -38,6 +38,9 @@ #include "editor/editor_node.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" void NavigationMeshEditor::_node_removed(Node *p_node) { if (p_node == node) { diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h index 010be411d6..b73d8d2e69 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.h +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h @@ -36,7 +36,9 @@ #include "editor/editor_plugin.h" class AcceptDialog; +class Button; class HBoxContainer; +class Label; class NavigationRegion3D; class NavigationMeshEditor : public Control { diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 16a16a7f24..b6d52464c0 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -43,6 +43,9 @@ <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized. </member> + <member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0"> + The render size multiplier for the current HMD. Must be set before the interface has been initialized. + </member> </members> <signals> <signal name="pose_recentered"> diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index ad5a515a01..64e07eff21 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -31,10 +31,10 @@ #include "openxr_action_map_editor.h" #include "core/config/project_settings.h" -#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/gui/editor_file_dialog.h" // TODO implement redo/undo system diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 4b39a6295c..5879fbd460 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1282,7 +1282,7 @@ XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunctio if (result != XR_SUCCESS) { String error_message = String("Symbol ") + p_name + " not found in OpenXR instance."; - ERR_FAIL_COND_V_MSG(true, result, error_message.utf8().get_data()); + ERR_FAIL_V_MSG(result, error_message.utf8().get_data()); } return result; @@ -1423,8 +1423,8 @@ Size2 OpenXRAPI::get_recommended_target_size() { Size2 target_size; - target_size.width = view_configuration_views[0].recommendedImageRectWidth; - target_size.height = view_configuration_views[0].recommendedImageRectHeight; + target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier; + target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier; return target_size; } @@ -1964,6 +1964,14 @@ Array OpenXRAPI::get_available_display_refresh_rates() const { return Array(); } +double OpenXRAPI::get_render_target_size_multiplier() const { + return render_target_size_multiplier; +} + +void OpenXRAPI::set_render_target_size_multiplier(double multiplier) { + render_target_size_multiplier = multiplier; +} + OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. singleton = this; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index c8bef5d420..292e54228d 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -120,6 +120,7 @@ private: XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; bool running = false; XrFrameState frame_state = { XR_TYPE_FRAME_STATE, NULL, 0, 0, false }; + double render_target_size_multiplier = 1.0; OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; XrSystemGraphicsProperties graphics_properties; @@ -368,6 +369,10 @@ public: void set_display_refresh_rate(float p_refresh_rate); Array get_available_display_refresh_rates() const; + // Render Target size multiplier + double get_render_target_size_multiplier() const; + void set_render_target_size_multiplier(double multiplier); + // action map String get_default_action_map_resource_name(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 27344c9da7..933148da87 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -47,6 +47,11 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + // Render Target size multiplier + ClassDB::bind_method(D_METHOD("get_render_target_size_multiplier"), &OpenXRInterface::get_render_target_size_multiplier); + ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier", "multiplier"), &OpenXRInterface::set_render_target_size_multiplier); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier"), "set_render_target_size_multiplier", "get_render_target_size_multiplier"); + ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active); ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active); ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets); @@ -668,6 +673,22 @@ Array OpenXRInterface::get_action_sets() const { return arr; } +double OpenXRInterface::get_render_target_size_multiplier() const { + if (openxr_api == nullptr) { + return 1.0; + } else { + return openxr_api->get_render_target_size_multiplier(); + } +} + +void OpenXRInterface::set_render_target_size_multiplier(double multiplier) { + if (openxr_api == nullptr) { + return; + } else { + openxr_api->set_render_target_size_multiplier(multiplier); + } +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index de758a8c2d..f36318530c 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -128,6 +128,9 @@ public: void set_action_set_active(const String &p_action_set, bool p_active); Array get_action_sets() const; + double get_render_target_size_multiplier() const; + void set_render_target_size_multiplier(double multiplier); + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp index 01be72a27e..5b35a2643a 100644 --- a/modules/squish/image_decompress_squish.cpp +++ b/modules/squish/image_decompress_squish.cpp @@ -58,7 +58,6 @@ void image_decompress_squish(Image *p_image) { squish_flags = squish::kBc5; } else { ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + "."); - return; } for (int i = 0; i <= mm_count; i++) { diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index c6a21aeefe..dfc820050f 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start new_sd->direction = sd->direction; new_sd->custom_punct = sd->custom_punct; new_sd->para_direction = sd->para_direction; + new_sd->base_para_direction = sd->base_para_direction; for (int i = 0; i < TextServer::SPACING_MAX; i++) { new_sd->extra_spacing[i] = sd->extra_spacing[i]; } @@ -4182,22 +4183,61 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S ERR_FAIL_COND_V_MSG((start < 0 || end - start > p_new_sd->utf16.length()), false, "Invalid BiDi override range."); // Create temporary line bidi & shape. - UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err); - ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); - ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err); - if (U_FAILURE(err)) { - ubidi_close(bidi_iter); - ERR_FAIL_V_MSG(false, u_errorName(err)); + UBiDi *bidi_iter = nullptr; + if (p_sd->bidi_iter[ov]) { + bidi_iter = ubidi_openSized(end - start, 0, &err); + if (U_SUCCESS(err)) { + ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err); + if (U_FAILURE(err)) { + // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead. + err = U_ZERO_ERROR; + const UChar *data = p_sd->utf16.get_data(); + switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) { + case DIRECTION_LTR: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); + } break; + case DIRECTION_RTL: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); + } break; + case DIRECTION_INHERITED: { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } break; + case DIRECTION_AUTO: { + UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); + if (direction != UBIDI_NEUTRAL) { + ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); + } else { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } + } break; + } + if (U_FAILURE(err)) { + ubidi_close(bidi_iter); + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err))); + } + } + } else { + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi iterator allocation for the line failed: %s", u_errorName(err))); + } } p_new_sd->bidi_iter.push_back(bidi_iter); err = U_ZERO_ERROR; - int bidi_run_count = ubidi_countRuns(bidi_iter, &err); - ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); + int bidi_run_count = 1; + if (bidi_iter) { + bidi_run_count = ubidi_countRuns(bidi_iter, &err); + if (U_FAILURE(err)) { + ERR_PRINT(u_errorName(err)); + } + } for (int i = 0; i < bidi_run_count; i++) { int32_t _bidi_run_start = 0; - int32_t _bidi_run_length = 0; - ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length); + int32_t _bidi_run_length = end - start; + if (bidi_iter) { + ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length); + } int32_t bidi_run_start = _convert_pos(p_sd, ov_start + start + _bidi_run_start); int32_t bidi_run_end = _convert_pos(p_sd, ov_start + start + _bidi_run_start + _bidi_run_length); @@ -5604,25 +5644,25 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length())); } - int base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; switch (sd->direction) { case DIRECTION_LTR: { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_LTR; + sd->base_para_direction = UBIDI_LTR; } break; case DIRECTION_RTL: { sd->para_direction = DIRECTION_RTL; - base_para_direction = UBIDI_RTL; + sd->base_para_direction = UBIDI_RTL; } break; case DIRECTION_INHERITED: case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length()); if (direction != UBIDI_NEUTRAL) { sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR; - base_para_direction = direction; + sd->base_para_direction = direction; } else { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; } } break; } @@ -5642,38 +5682,53 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { UErrorCode err = U_ZERO_ERROR; UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err); - ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); - - switch (static_cast<TextServer::Direction>(sd->bidi_override[ov].z)) { - case DIRECTION_LTR: { - ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); - } break; - case DIRECTION_RTL: { - ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); - } break; - case DIRECTION_INHERITED: { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); - } break; - case DIRECTION_AUTO: { - UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); - if (direction != UBIDI_NEUTRAL) { - ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); - } else { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); - } - } break; + if (U_SUCCESS(err)) { + switch (static_cast<TextServer::Direction>(sd->bidi_override[ov].z)) { + case DIRECTION_LTR: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); + } break; + case DIRECTION_RTL: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); + } break; + case DIRECTION_INHERITED: { + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); + } break; + case DIRECTION_AUTO: { + UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); + if (direction != UBIDI_NEUTRAL) { + ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); + } else { + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); + } + } break; + } + if (U_FAILURE(err)) { + ubidi_close(bidi_iter); + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi reordering for the paragraph failed: %s", u_errorName(err))); + } + } else { + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi iterator allocation for the paragraph failed: %s", u_errorName(err))); } - ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); sd->bidi_iter.push_back(bidi_iter); err = U_ZERO_ERROR; - int bidi_run_count = ubidi_countRuns(bidi_iter, &err); - ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); + int bidi_run_count = 1; + if (bidi_iter) { + bidi_run_count = ubidi_countRuns(bidi_iter, &err); + if (U_FAILURE(err)) { + ERR_PRINT(u_errorName(err)); + } + } for (int i = 0; i < bidi_run_count; i++) { int32_t _bidi_run_start = 0; - int32_t _bidi_run_length = 0; + int32_t _bidi_run_length = end - start; + bool is_rtl = false; hb_direction_t bidi_run_direction = HB_DIRECTION_INVALID; - bool is_rtl = (ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length) == UBIDI_LTR); + if (bidi_iter) { + is_rtl = (ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length) == UBIDI_LTR); + } switch (sd->orientation) { case ORIENTATION_HORIZONTAL: { if (is_rtl) { @@ -5730,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { gl.start = span.start; gl.end = span.end; gl.count = 1; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index b959dd8544..4b8c3f7cd3 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. + int base_para_direction = UBIDI_DEFAULT_LTR; bool valid = false; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. @@ -515,7 +516,9 @@ class TextServerAdvanced : public TextServerExtension { ~ShapedTextDataAdvanced() { for (int i = 0; i < bidi_iter.size(); i++) { - ubidi_close(bidi_iter[i]); + if (bidi_iter[i]) { + ubidi_close(bidi_iter[i]); + } } if (script_iter) { memdelete(script_iter); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 9095be46af..8b210bdc38 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { gl.end = span.end; gl.count = 1; gl.index = 0; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/modules/vhacd/register_types.cpp b/modules/vhacd/register_types.cpp index 6310becf1b..088f03ebe8 100644 --- a/modules/vhacd/register_types.cpp +++ b/modules/vhacd/register_types.cpp @@ -32,22 +32,22 @@ #include "scene/resources/mesh.h" #include "thirdparty/vhacd/public/VHACD.h" -static Vector<Vector<Vector3>> convex_decompose(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const Mesh::ConvexDecompositionSettings &p_settings, Vector<Vector<uint32_t>> *r_convex_indices) { +static Vector<Vector<Vector3>> convex_decompose(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const Ref<MeshConvexDecompositionSettings> &p_settings, Vector<Vector<uint32_t>> *r_convex_indices) { VHACD::IVHACD::Parameters params; - params.m_concavity = p_settings.max_concavity; - params.m_alpha = p_settings.symmetry_planes_clipping_bias; - params.m_beta = p_settings.revolution_axes_clipping_bias; - params.m_minVolumePerCH = p_settings.min_volume_per_convex_hull; - params.m_resolution = p_settings.resolution; - params.m_maxNumVerticesPerCH = p_settings.max_num_vertices_per_convex_hull; - params.m_planeDownsampling = p_settings.plane_downsampling; - params.m_convexhullDownsampling = p_settings.convexhull_downsampling; - params.m_pca = p_settings.normalize_mesh; - params.m_mode = p_settings.mode; - params.m_convexhullApproximation = p_settings.convexhull_approximation; + params.m_concavity = p_settings->get_max_concavity(); + params.m_alpha = p_settings->get_symmetry_planes_clipping_bias(); + params.m_beta = p_settings->get_revolution_axes_clipping_bias(); + params.m_minVolumePerCH = p_settings->get_min_volume_per_convex_hull(); + params.m_resolution = p_settings->get_resolution(); + params.m_maxNumVerticesPerCH = p_settings->get_max_num_vertices_per_convex_hull(); + params.m_planeDownsampling = p_settings->get_plane_downsampling(); + params.m_convexhullDownsampling = p_settings->get_convex_hull_downsampling(); + params.m_pca = p_settings->get_normalize_mesh(); + params.m_mode = p_settings->get_mode(); + params.m_convexhullApproximation = p_settings->get_convex_hull_approximation(); params.m_oclAcceleration = true; - params.m_maxConvexHulls = p_settings.max_convex_hulls; - params.m_projectHullVertices = p_settings.project_hull_vertices; + params.m_maxConvexHulls = p_settings->get_max_convex_hulls(); + params.m_projectHullVertices = p_settings->get_project_hull_vertices(); VHACD::IVHACD *decomposer = VHACD::CreateVHACD(); decomposer->Compute(p_vertices, p_vertex_count, p_triangles, p_triangle_count, params); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 8a150c8561..816d5276b9 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -379,7 +379,6 @@ void WSLPeer::_do_client_handshake() { // Header is too big close(-1); ERR_FAIL_MSG("Response headers too big."); - return; } uint8_t byte; @@ -402,7 +401,6 @@ void WSLPeer::_do_client_handshake() { if (!_verify_server_response()) { close(-1); ERR_FAIL_MSG("Invalid response headers."); - return; } wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this); wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size); @@ -469,7 +467,6 @@ bool WSLPeer::_verify_server_response() { } if (!valid) { ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + selected_protocol); - return false; } } return true; diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 758c37e2ad..ed162b1da2 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -93,6 +93,18 @@ <link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link> </tutorials> <methods> + <method name="get_available_display_refresh_rates" qualifiers="const"> + <return type="Array" /> + <description> + Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the web browser and after the interface has been initialized. + </description> + </method> + <method name="get_display_refresh_rate" qualifiers="const"> + <return type="float" /> + <description> + Returns the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It may not report an accurate value until after using [method set_display_refresh_rate]. + </description> + </method> <method name="get_input_source_target_ray_mode" qualifiers="const"> <return type="int" enum="WebXRInterface.TargetRayMode" /> <param index="0" name="input_source_id" type="int" /> @@ -132,6 +144,13 @@ This method returns nothing, instead it emits the [signal session_supported] signal with the result. </description> </method> + <method name="set_display_refresh_rate"> + <return type="void" /> + <param index="0" name="refresh_rate" type="float" /> + <description> + Sets the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It won't take effect right away until after [signal display_refresh_rate_changed] is emitted. + </description> + </method> </methods> <members> <member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features"> @@ -167,6 +186,11 @@ </member> </members> <signals> + <signal name="display_refresh_rate_changed"> + <description> + Emitted after the display's refresh rate has changed. + </description> + </signal> <signal name="reference_space_reset"> <description> Emitted to indicate that the reference space has been reset or reconfigured. diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index c636be1576..554ca0398b 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -90,6 +90,10 @@ extern bool godot_webxr_update_input_source( extern char *godot_webxr_get_visibility_state(); extern int godot_webxr_get_bounds_geometry(float **r_points); +extern float godot_webxr_get_frame_rate(); +extern void godot_webxr_update_target_frame_rate(float p_frame_rate); +extern int godot_webxr_get_supported_frame_rates(float **r_frame_rates); + #ifdef __cplusplus } #endif diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 5c01d88a30..fe47de02b0 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -42,6 +42,7 @@ const GodotWebXR = { view_count: 1, input_sources: new Array(16), touches: new Array(5), + onsimpleevent: null, // Monkey-patch the requestAnimationFrame() used by Emscripten for the main // loop, so that we can swap it out for XRSession.requestAnimationFrame() @@ -283,6 +284,9 @@ const GodotWebXR = { GodotRuntime.free(c_str); }); + // Store onsimpleevent so we can use it later. + GodotWebXR.onsimpleevent = onsimpleevent; + const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef const gl = GL.getContext(gl_context_handle).GLctx; GodotWebXR.gl = gl; @@ -368,6 +372,7 @@ const GodotWebXR = { GodotWebXR.view_count = 1; GodotWebXR.input_sources = new Array(16); GodotWebXR.touches = new Array(5); + GodotWebXR.onsimpleevent = null; // Disable the monkey-patched window.requestAnimationFrame() and // pause/restart the main loop to activate it on all platforms. @@ -594,6 +599,51 @@ const GodotWebXR = { return point_count; }, + + godot_webxr_get_frame_rate__proxy: 'sync', + godot_webxr_get_frame_rate__sig: 'i', + godot_webxr_get_frame_rate: function () { + if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) { + return 0; + } + return GodotWebXR.session.frameRate; + }, + + godot_webxr_update_target_frame_rate__proxy: 'sync', + godot_webxr_update_target_frame_rate__sig: 'vi', + godot_webxr_update_target_frame_rate: function (p_frame_rate) { + if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) { + return; + } + + GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => { + const c_str = GodotRuntime.allocString('display_refresh_rate_changed'); + GodotWebXR.onsimpleevent(c_str); + GodotRuntime.free(c_str); + }); + }, + + godot_webxr_get_supported_frame_rates__proxy: 'sync', + godot_webxr_get_supported_frame_rates__sig: 'ii', + godot_webxr_get_supported_frame_rates: function (r_frame_rates) { + if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) { + return 0; + } + + const frame_rate_count = GodotWebXR.session.supportedFrameRates.length; + if (frame_rate_count === 0) { + return 0; + } + + const buf = GodotRuntime.malloc(frame_rate_count * 4); + for (let i = 0; i < frame_rate_count; i++) { + GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float'); + } + GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32'); + + return frame_rate_count; + }, + }; autoAddDeps(GodotWebXR, '$GodotWebXR'); diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 4b88820b19..7f7c297acc 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -68,6 +68,16 @@ XRSession.prototype.inputSources; XRSession.prototype.visibilityState; /** + * @type {?number} + */ +XRSession.prototype.frameRate; + +/** + * @type {?Float32Array} + */ +XRSession.prototype.supportedFrameRates; + +/** * @type {?function (Event)} */ XRSession.prototype.onend; @@ -142,6 +152,12 @@ XRSession.prototype.end = function () {}; XRSession.prototype.requestReferenceSpace = function (referenceSpaceType) {}; /** + * @param {number} rate + * @return {Promise<undefined>} + */ +XRSession.prototype.updateTargetFrameRate = function (rate) {}; + +/** * @typedef {function(number, XRFrame): undefined} */ var XRFrameRequestCallback; diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index d7f4247768..5e30d0c996 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -46,6 +46,9 @@ void WebXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker); ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode); ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state); + ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &WebXRInterface::get_display_refresh_rate); + ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &WebXRInterface::set_display_refresh_rate); + ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &WebXRInterface::get_available_display_refresh_rates); ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features"); @@ -68,6 +71,7 @@ void WebXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("visibility_state_changed")); ADD_SIGNAL(MethodInfo("reference_space_reset")); + ADD_SIGNAL(MethodInfo("display_refresh_rate_changed")); BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN); BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE); diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index f18b4b40e6..abaa8c01f8 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -66,6 +66,9 @@ public: virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0; virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0; virtual String get_visibility_state() const = 0; + virtual float get_display_refresh_rate() const = 0; + virtual void set_display_refresh_rate(float p_refresh_rate) = 0; + virtual Array get_available_display_refresh_rates() const = 0; }; VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode); diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index d3710bd0df..e1df2ea94e 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -202,6 +202,30 @@ PackedVector3Array WebXRInterfaceJS::get_play_area() const { return ret; } +float WebXRInterfaceJS::get_display_refresh_rate() const { + return godot_webxr_get_frame_rate(); +} + +void WebXRInterfaceJS::set_display_refresh_rate(float p_refresh_rate) { + godot_webxr_update_target_frame_rate(p_refresh_rate); +} + +Array WebXRInterfaceJS::get_available_display_refresh_rates() const { + Array ret; + + float *rates; + int rate_count = godot_webxr_get_supported_frame_rates(&rates); + if (rate_count > 0) { + ret.resize(rate_count); + for (int i = 0; i < rate_count; i++) { + ret[i] = rates[i]; + } + free(rates); + } + + return ret; +} + StringName WebXRInterfaceJS::get_name() const { return "WebXR"; }; diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 20516e89e2..d85eb8cad7 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -102,6 +102,10 @@ public: virtual String get_visibility_state() const override; virtual PackedVector3Array get_play_area() const override; + virtual float get_display_refresh_rate() const override; + virtual void set_display_refresh_rate(float p_refresh_rate) override; + virtual Array get_available_display_refresh_rates() const override; + virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; |
