diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/gdscript/gdscript.cpp | 26 | ||||
| -rw-r--r-- | modules/gdscript/gdscript.h | 2 | ||||
| -rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 44 | ||||
| -rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 5 | ||||
| -rw-r--r-- | modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd | 3 | ||||
| -rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs | 167 | ||||
| -rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs | 2 | ||||
| -rw-r--r-- | modules/mono/glue/runtime_interop.cpp | 8 | ||||
| -rw-r--r-- | modules/openxr/editor/openxr_action_map_editor.cpp | 10 | ||||
| -rw-r--r-- | modules/openxr/editor/openxr_interaction_profile_editor.cpp | 2 | ||||
| -rw-r--r-- | modules/openxr/editor/openxr_select_action_dialog.cpp | 4 | ||||
| -rw-r--r-- | modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp | 4 | ||||
| -rw-r--r-- | modules/text_server_adv/text_server_adv.cpp | 94 | ||||
| -rw-r--r-- | modules/text_server_adv/text_server_adv.h | 2 | ||||
| -rw-r--r-- | modules/text_server_fb/text_server_fb.cpp | 71 | ||||
| -rw-r--r-- | modules/text_server_fb/text_server_fb.h | 2 |
16 files changed, 344 insertions, 102 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 258f1a80f7..57a29cc81e 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1111,21 +1111,27 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { GDScript *GDScript::find_class(const String &p_qualified_name) { String first = p_qualified_name.get_slice("::", 0); + Vector<String> class_names; GDScript *result = nullptr; + // Empty initial name means start here. if (first.is_empty() || first == name) { + class_names = p_qualified_name.split("::"); result = this; - } else if (first == get_root_script()->path) { + } else if (p_qualified_name.begins_with(get_root_script()->path)) { + // Script path could have a class path separator("::") in it. + class_names = p_qualified_name.trim_prefix(get_root_script()->path).split("::"); result = get_root_script(); } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { + class_names = p_qualified_name.split("::"); result = E->value.ptr(); } else if (_owner != nullptr) { // Check parent scope. return _owner->find_class(p_qualified_name); } - int name_count = p_qualified_name.get_slice_count("::"); - for (int i = 1; result != nullptr && i < name_count; i++) { - String current_name = p_qualified_name.get_slice("::", i); + // Starts at index 1 because index 0 was handled above. + for (int i = 1; result != nullptr && i < class_names.size(); i++) { + String current_name = class_names[i]; if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { result = E->value.ptr(); } else { @@ -1137,11 +1143,12 @@ GDScript *GDScript::find_class(const String &p_qualified_name) { return result; } -bool GDScript::is_subclass(const GDScript *p_script) { +bool GDScript::has_class(const GDScript *p_script) { String fqn = p_script->fully_qualified_name; - if (!fqn.is_empty() && fqn != fully_qualified_name && fqn.begins_with(fully_qualified_name)) { - String fqn_rest = fqn.substr(fully_qualified_name.length()); - return find_class(fqn_rest) == p_script; + if (fully_qualified_name.is_empty() && fqn.get_slice("::", 0).is_empty()) { + return p_script == this; + } else if (fqn.begins_with(fully_qualified_name)) { + return p_script == find_class(fqn.trim_prefix(fully_qualified_name)); } return false; } @@ -2592,8 +2599,7 @@ Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String SelfList<GDScript> *elem = script_list.first(); while (elem) { GDScript *scr = elem->self(); - scr = scr->find_class(p_name); - if (scr != nullptr) { + if (scr->fully_qualified_name == p_name) { return scr; } elem = elem->next(); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 7911ea47ec..332d18f720 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -187,7 +187,7 @@ public: bool inherits_script(const Ref<Script> &p_script) const override; GDScript *find_class(const String &p_qualified_name); - bool is_subclass(const GDScript *p_script); + bool has_class(const GDScript *p_script); GDScript *get_root_script(); bool is_root_script() const { return _owner == nullptr; } String get_fully_qualified_name() const { return fully_qualified_name; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 30ac1f29b1..7d14bcf3c4 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -996,7 +996,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum); if (member.enum_value.index > 0) { - member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; + const GDScriptParser::EnumNode::Value &prev_value = member.enum_value.parent_enum->values[member.enum_value.index - 1]; + resolve_class_member(p_class, prev_value.identifier->name, member.enum_value.identifier); + member.enum_value.value = prev_value.value + 1; } else { member.enum_value.value = 0; } @@ -3994,37 +3996,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.kind = GDScriptParser::DataType::CLASS; // This might be an inner class, so we want to get the parser for the root. // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } - - Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + String script_path = gds->get_script_path(); + Ref<GDScriptParserRef> ref = get_parser_for(script_path); if (ref.is_null()) { - push_error("Could not find script in path.", p_source); + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); GDScriptParser::DataType error_type; error_type.kind = GDScriptParser::DataType::VARIANT; return error_type; } - ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); - - GDScriptParser::ClassNode *found = ref->get_parser()->head; - - for (const StringName &E : class_chain) { - if (!found->has_member(E)) { - return GDScriptParser::DataType(); - } - - if (found->get_member(E).type != GDScriptParser::ClassNode::Member::CLASS) { - return GDScriptParser::DataType(); + Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + GDScriptParser::ClassNode *found = nullptr; + if (err == OK) { + found = ref->get_parser()->find_class(gds->fully_qualified_name); + if (found != nullptr) { + err = resolve_class_inheritance(found, p_source); } - - resolve_class_member(found, E, p_source); - - found = found->get_member(E).m_class; + } + if (err || found == nullptr) { + push_error(vformat(R"(Could not resolve script "%s".)", script_path), p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; } result.class_type = found; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 4740b9b5a9..50588110c4 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -117,8 +117,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = p_datatype.builtin_type; result.native_type = p_datatype.native_type; - String root_name = p_datatype.class_type->fqcn.get_slice("::", 0); - bool is_local_class = !root_name.is_empty() && root_name == main_script->fully_qualified_name; + bool is_local_class = parser->has_class(p_datatype.class_type); Ref<GDScript> script; if (is_local_class) { @@ -2300,7 +2299,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri return ERR_COMPILATION_FAILED; } - if (base.ptr() == main_script || main_script->is_subclass(base.ptr())) { + if (main_script->has_class(base.ptr())) { Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); if (err) { return err; diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd index 069e54c528..11349cc916 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd @@ -42,7 +42,8 @@ const c2 := EV2 enum { EV1 = 42, - EV2 = EV3 + 1 + UNUSED = EV3, + EV2 } enum { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index f49023555b..4075a878d2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -187,6 +188,19 @@ namespace Godot } /// <summary> + /// Returns the light intensity of the color, as a value between 0.0 and 1.0 (inclusive). + /// This is useful when determining light or dark color. Colors with a luminance smaller + /// than 0.5 can be generally considered dark. + /// Note: <see cref="Luminance"/> relies on the color being in the linear color space to + /// return an accurate relative luminance value. If the color is in the sRGB color space + /// use <see cref="SrgbToLinear"/> to convert it to the linear color space first. + /// </summary> + public readonly float Luminance + { + get { return 0.2126f * r + 0.7152f * g + 0.0722f * b; } + } + + /// <summary> /// Access color components using their index. /// </summary> /// <value> @@ -363,6 +377,35 @@ namespace Godot } /// <summary> + /// Returns the color converted to the sRGB color space. + /// This method assumes the original color is in the linear color space. + /// See also <see cref="SrgbToLinear"/> which performs the opposite operation. + /// </summary> + /// <returns>The sRGB color.</returns> + public readonly Color LinearToSrgb() + { + return new Color( + r < 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * (float)Mathf.Pow(r, 1.0f / 2.4f) - 0.055f, + g < 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * (float)Mathf.Pow(g, 1.0f / 2.4f) - 0.055f, + b < 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * (float)Mathf.Pow(b, 1.0f / 2.4f) - 0.055f, a); + } + + /// <summary> + /// Returns the color converted to linear color space. + /// This method assumes the original color already is in sRGB color space. + /// See also <see cref="LinearToSrgb"/> which performs the opposite operation. + /// </summary> + /// <returns>The color in linear color space.</returns> + public readonly Color SrgbToLinear() + { + return new Color( + r < 0.04045f ? r * (1.0f / 12.92f) : (float)Mathf.Pow((r + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + g < 0.04045f ? g * (1.0f / 12.92f) : (float)Mathf.Pow((g + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + b < 0.04045f ? b * (1.0f / 12.92f) : (float)Mathf.Pow((b + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + a); + } + + /// <summary> /// Returns the color converted to an unsigned 32-bit integer in ABGR /// format (each byte represents a color channel). /// ABGR is the reversed version of the default format. @@ -565,6 +608,10 @@ namespace Godot /// <see cref="Colors"/> constants. /// </summary> /// <param name="code">The HTML color code or color name to construct from.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// A color cannot be inferred from the given <paramref name="code"/>. + /// It was invalid HTML and a color with that name was not found. + /// </exception> public Color(string code) { if (HtmlIsValid(code)) @@ -597,7 +644,7 @@ namespace Godot /// <exception name="ArgumentOutOfRangeException"> /// <paramref name="rgba"/> color code is invalid. /// </exception> - private static Color FromHTML(ReadOnlySpan<char> rgba) + public static Color FromHTML(ReadOnlySpan<char> rgba) { Color c; if (rgba.Length == 0) @@ -705,28 +752,59 @@ namespace Godot /// the constants defined in <see cref="Colors"/>. /// </summary> /// <param name="name">The name of the color.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// A color with the given name is not found. + /// </exception> /// <returns>The constructed color.</returns> private static Color Named(string name) { + if (!FindNamedColor(name, out Color color)) + { + throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); + } + + return color; + } + + /// <summary> + /// Returns a color according to the standardized name, with the + /// specified alpha value. Supported color names are the same as + /// the constants defined in <see cref="Colors"/>. + /// If a color with the given name is not found, it returns + /// <paramref name="default"/>. + /// </summary> + /// <param name="name">The name of the color.</param> + /// <param name="default"> + /// The default color to return when a color with the given name + /// is not found. + /// </param> + /// <returns>The constructed color.</returns> + private static Color Named(string name, Color @default) + { + if (!FindNamedColor(name, out Color color)) + { + return @default; + } + + return color; + } + + private static bool FindNamedColor(string name, out Color color) + { name = name.Replace(" ", string.Empty); name = name.Replace("-", string.Empty); name = name.Replace("_", string.Empty); name = name.Replace("'", string.Empty); name = name.Replace(".", string.Empty); - name = name.ToUpper(); - - if (!Colors.namedColors.ContainsKey(name)) - { - throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); - } + name = name.ToUpperInvariant(); - return Colors.namedColors[name]; + return Colors.namedColors.TryGetValue(name, out color); } /// <summary> - /// Constructs a color from an HSV profile, with values on the - /// range of 0 to 1. This is equivalent to using each of - /// the <c>h</c>/<c>s</c>/<c>v</c> properties, but much more efficient. + /// Constructs a color from an HSV profile. The <paramref name="hue"/>, + /// <paramref name="saturation"/>, and <paramref name="value"/> are typically + /// between 0.0 and 1.0. /// </summary> /// <param name="hue">The HSV hue, typically on the range of 0 to 1.</param> /// <param name="saturation">The HSV saturation, typically on the range of 0 to 1.</param> @@ -841,13 +919,78 @@ namespace Godot return ParseCol4(str, index) * 16 + ParseCol4(str, index + 1); } + /// <summary> + /// Constructs a color from an OK HSL profile. The <paramref name="hue"/>, + /// <paramref name="saturation"/>, and <paramref name="lightness"/> are typically + /// between 0.0 and 1.0. + /// </summary> + /// <param name="hue">The OK HSL hue, typically on the range of 0 to 1.</param> + /// <param name="saturation">The OK HSL saturation, typically on the range of 0 to 1.</param> + /// <param name="lightness">The OK HSL lightness, typically on the range of 0 to 1.</param> + /// <param name="alpha">The alpha (transparency) value, typically on the range of 0 to 1.</param> + /// <returns>The constructed color.</returns> + public static Color FromOkHsl(float hue, float saturation, float lightness, float alpha = 1.0f) + { + return NativeFuncs.godotsharp_color_from_ok_hsl(hue, saturation, lightness, alpha); + } + + /// <summary> + /// Encodes a <see cref="Color"/> from a RGBE9995 format integer. + /// See <see cref="Image.Format.Rgbe9995"/>. + /// </summary> + /// <param name="rgbe">The RGBE9995 encoded color.</param> + /// <returns>The constructed color.</returns> + public static Color FromRgbe9995(uint rgbe) + { + float r = rgbe & 0x1ff; + float g = (rgbe >> 9) & 0x1ff; + float b = (rgbe >> 18) & 0x1ff; + float e = rgbe >> 27; + float m = (float)Mathf.Pow(2.0f, e - 15.0f - 9.0f); + + float rd = r * m; + float gd = g * m; + float bd = b * m; + + return new Color(rd, gd, bd, 1.0f); + } + + /// <summary> + /// Constructs a color from the given string, which can be either an HTML color + /// code or a named color. Returns <paramref name="default"/> if the color cannot + /// be inferred from the string. Supported color names are the same as the + /// <see cref="Colors"/> constants. + /// </summary> + /// <param name="str">The HTML color code or color name.</param> + /// <param name="default">The fallback color to return if the color cannot be inferred.</param> + /// <returns>The constructed color.</returns> + public static Color FromString(string str, Color @default) + { + if (HtmlIsValid(str)) + { + return FromHTML(str); + } + else + { + return Named(str, @default); + } + } + private static string ToHex32(float val) { byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255)); return b.HexEncode(); } - internal static bool HtmlIsValid(ReadOnlySpan<char> color) + /// <summary> + /// Returns <see langword="true"/> if <paramref name="color"/> is a valid HTML hexadecimal + /// color string. The string must be a hexadecimal value (case-insensitive) of either 3, + /// 4, 6 or 8 digits, and may be prefixed by a hash sign (<c>#</c>). This method is + /// identical to <see cref="StringExtensions.IsValidHtmlColor(string)"/>. + /// </summary> + /// <param name="color">The HTML hexadecimal color string.</param> + /// <returns>Whether or not the string was a valid HTML hexadecimal color string.</returns> + public static bool HtmlIsValid(ReadOnlySpan<char> color) { if (color.IsEmpty) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index c7deb6423b..57488bd586 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -153,6 +153,8 @@ namespace Godot.NativeInterop internal static partial void godotsharp_callable_call_deferred(in godot_callable p_callable, godot_variant** p_args, int p_arg_count); + internal static partial Color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha); + // GDNative functions // gdnative.h diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 338e5a0147..a3280a0739 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -514,6 +514,13 @@ void godotsharp_callable_call_deferred(Callable *p_callable, const Variant **p_a p_callable->call_deferredp(p_args, p_arg_count); } +godot_color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { + godot_color ret; + Color *dest = (Color *)&ret; + memnew_placement(dest, Color(Color::from_ok_hsl(p_h, p_s, p_l, p_alpha))); + return ret; +} + // GDNative functions // gdnative.h @@ -1345,6 +1352,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_callable_get_data_for_marshalling, (void *)godotsharp_callable_call, (void *)godotsharp_callable_call_deferred, + (void *)godotsharp_color_from_ok_hsl, (void *)godotsharp_method_bind_ptrcall, (void *)godotsharp_method_bind_call, (void *)godotsharp_variant_new_string_name, diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index a2c33a91c4..844423afc0 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -57,7 +57,7 @@ void OpenXRActionMapEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { for (int i = 0; i < tabs->get_child_count(); i++) { - Control *tab = static_cast<Control *>(tabs->get_child(i)); + Control *tab = Object::cast_to<Control>(tabs->get_child(i)); if (tab) { tab->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } @@ -208,7 +208,7 @@ void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { void OpenXRActionMapEditor::_on_action_removed() { for (int i = 0; i < tabs->get_tab_count(); i++) { // First tab won't be an interaction profile editor, but being thorough.. - OpenXRInteractionProfileEditorBase *interaction_profile_editor = static_cast<OpenXRInteractionProfileEditorBase *>(tabs->get_tab_control(i)); + OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(i)); if (interaction_profile_editor) { } } @@ -309,7 +309,7 @@ void OpenXRActionMapEditor::_on_tabs_tab_changed(int p_tab) { } void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) { - OpenXRInteractionProfileEditorBase *interaction_profile_editor = static_cast<OpenXRInteractionProfileEditorBase *>(tabs->get_tab_control(p_tab)); + OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(p_tab)); ERR_FAIL_NULL(interaction_profile_editor); undo_redo->create_action(TTR("Remove interaction profile")); @@ -375,9 +375,9 @@ void OpenXRActionMapEditor::_clear_action_map() { child->queue_free(); } - for (int i = 0; i < tabs->get_tab_count(); i++) { + for (int i = tabs->get_tab_count() - 1; i >= 0; --i) { // First tab won't be an interaction profile editor, but being thorough.. - OpenXRInteractionProfileEditorBase *interaction_profile_editor = static_cast<OpenXRInteractionProfileEditorBase *>(tabs->get_tab_control(i)); + OpenXRInteractionProfileEditorBase *interaction_profile_editor = Object::cast_to<OpenXRInteractionProfileEditorBase>(tabs->get_tab_control(i)); if (interaction_profile_editor) { tabs->remove_child(interaction_profile_editor); interaction_profile_editor->queue_free(); diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp index 3eca1967f5..ee73f6a5cd 100644 --- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -308,7 +308,7 @@ void OpenXRInteractionProfileEditor::_update_interaction_profile() { void OpenXRInteractionProfileEditor::_theme_changed() { for (int i = 0; i < main_hb->get_child_count(); i++) { - Control *panel = static_cast<Control *>(main_hb->get_child(i)); + Control *panel = Object::cast_to<Control>(main_hb->get_child(i)); if (panel) { panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); } diff --git a/modules/openxr/editor/openxr_select_action_dialog.cpp b/modules/openxr/editor/openxr_select_action_dialog.cpp index 5f018291d5..c4b2ef95c5 100644 --- a/modules/openxr/editor/openxr_select_action_dialog.cpp +++ b/modules/openxr/editor/openxr_select_action_dialog.cpp @@ -47,7 +47,7 @@ void OpenXRSelectActionDialog::_notification(int p_what) { void OpenXRSelectActionDialog::_on_select_action(const String p_action) { if (selected_action != "") { NodePath button_path = action_buttons[selected_action]; - Button *button = static_cast<Button *>(get_node(button_path)); + Button *button = Object::cast_to<Button>(get_node(button_path)); if (button != nullptr) { button->set_flat(true); } @@ -57,7 +57,7 @@ void OpenXRSelectActionDialog::_on_select_action(const String p_action) { if (selected_action != "") { NodePath button_path = action_buttons[selected_action]; - Button *button = static_cast<Button *>(get_node(button_path)); + Button *button = Object::cast_to<Button>(get_node(button_path)); if (button != nullptr) { button->set_flat(false); } diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp index 6ce26a62db..b871840cb2 100644 --- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp @@ -46,7 +46,7 @@ void OpenXRSelectInteractionProfileDialog::_notification(int p_what) { void OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile(const String p_interaction_profile) { if (selected_interaction_profile != "") { NodePath button_path = ip_buttons[selected_interaction_profile]; - Button *button = static_cast<Button *>(get_node(button_path)); + Button *button = Object::cast_to<Button>(get_node(button_path)); if (button != nullptr) { button->set_flat(true); } @@ -56,7 +56,7 @@ void OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile(const if (selected_interaction_profile != "") { NodePath button_path = ip_buttons[selected_interaction_profile]; - Button *button = static_cast<Button *>(get_node(button_path)); + Button *button = Object::cast_to<Button>(get_node(button_path)); if (button != nullptr) { button->set_flat(false); } diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index b4f3389c36..046973d193 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -6246,7 +6246,7 @@ String TextServerAdvanced::_string_to_lower(const String &p_string, const String return String::utf16(lower.ptr(), len); } -PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_string, const String &p_language) const { +PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_string, const String &p_language, int p_chars_per_line) const { const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; // Convert to UTF-16. Char16String utf16 = p_string.utf16(); @@ -6254,15 +6254,7 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str HashSet<int> breaks; UErrorCode err = U_ZERO_ERROR; UBreakIterator *bi = ubrk_open(UBRK_LINE, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err); - if (U_FAILURE(err)) { - // No data loaded - use fallback. - for (int i = 0; i < p_string.length(); i++) { - char32_t c = p_string[i]; - if (is_whitespace(c) || is_linebreak(c)) { - breaks.insert(i); - } - } - } else { + if (U_SUCCESS(err)) { while (ubrk_next(bi) != UBRK_DONE) { int pos = _convert_pos(p_string, utf16, ubrk_current(bi)) - 1; if (pos != p_string.length() - 1) { @@ -6273,24 +6265,80 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str ubrk_close(bi); PackedInt32Array ret; + + int line_start = 0; + int line_end = 0; // End of last word on current line. + int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. + int word_length = 0; + for (int i = 0; i < p_string.length(); i++) { - char32_t c = p_string[i]; - if (c == 0xfffc) { - continue; - } - if (u_ispunct(c) && c != 0x005F) { - ret.push_back(i); - continue; - } - if (is_underscore(c)) { - ret.push_back(i); - continue; - } - if (breaks.has(i)) { + const char32_t c = p_string[i]; + + if (is_linebreak(c)) { + // Force newline. + ret.push_back(line_start); ret.push_back(i); + line_start = i + 1; + line_end = line_start; + word_start = line_start; + word_length = 0; + } else if (c == 0xfffc) { continue; + } else if ((u_ispunct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || is_whitespace(c)) { + // A whitespace ends current word. + if (word_length > 0) { + line_end = i - 1; + word_start = -1; + word_length = 0; + } + } else if (breaks.has(i)) { + // End current word, no space. + if (word_length > 0) { + line_end = i; + word_start = i + 1; + word_length = 0; + } + if (p_chars_per_line <= 0) { + ret.push_back(line_start); + ret.push_back(line_end + 1); + line_start = word_start; + line_end = line_start; + } + } else { + if (word_start == -1) { + word_start = i; + if (p_chars_per_line <= 0) { + ret.push_back(line_start); + ret.push_back(line_end + 1); + line_start = word_start; + line_end = line_start; + } + } + word_length += 1; + + if (p_chars_per_line > 0) { + if (word_length > p_chars_per_line) { + // Word too long: wrap before current character. + ret.push_back(line_start); + ret.push_back(i); + line_start = i; + line_end = i; + word_start = i; + word_length = 1; + } else if (i - line_start + 1 > p_chars_per_line) { + // Line too long: wrap after the last word. + ret.push_back(line_start); + ret.push_back(line_end + 1); + line_start = word_start; + line_end = line_start; + } + } } } + if (line_start < p_string.length()) { + ret.push_back(line_start); + ret.push_back(p_string.length()); + } return ret; } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 8a9aa4356b..59f44cf142 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -915,7 +915,7 @@ public: MODBIND2RC(String, parse_number, const String &, const String &); MODBIND1RC(String, percent_sign, const String &); - MODBIND2RC(PackedInt32Array, string_get_word_breaks, const String &, const String &); + MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int); MODBIND2RC(int64_t, is_confusable, const String &, const PackedStringArray &); MODBIND1RC(bool, spoof_check, const String &); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 19abcde1fd..2cee360f42 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -4099,26 +4099,69 @@ String TextServerFallback::_string_to_lower(const String &p_string, const String return lower; } -PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_string, const String &p_language) const { +PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_string, const String &p_language, int p_chars_per_line) const { PackedInt32Array ret; + + int line_start = 0; + int line_end = 0; // End of last word on current line. + int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. + int word_length = 0; + for (int i = 0; i < p_string.length(); i++) { - char32_t c = p_string[i]; - if (c == 0xfffc) { - continue; - } - if (is_punct(c) && c != 0x005F) { - ret.push_back(i); - continue; - } - if (is_underscore(c)) { - ret.push_back(i); - continue; - } - if (is_whitespace(c) || is_linebreak(c)) { + const char32_t c = p_string[i]; + + if (is_linebreak(c)) { + // Force newline. + ret.push_back(line_start); ret.push_back(i); + line_start = i + 1; + line_end = line_start; + word_start = line_start; + word_length = 0; + } else if (c == 0xfffc) { continue; + } else if ((is_punct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || is_whitespace(c)) { + // A whitespace ends current word. + if (word_length > 0) { + line_end = i - 1; + word_start = -1; + word_length = 0; + } + } else { + if (word_start == -1) { + word_start = i; + if (p_chars_per_line <= 0) { + ret.push_back(line_start); + ret.push_back(line_end + 1); + line_start = word_start; + line_end = line_start; + } + } + word_length += 1; + + if (p_chars_per_line > 0) { + if (word_length > p_chars_per_line) { + // Word too long: wrap before current character. + ret.push_back(line_start); + ret.push_back(i); + line_start = i; + line_end = i; + word_start = i; + word_length = 1; + } else if (i - line_start + 1 > p_chars_per_line) { + // Line too long: wrap after the last word. + ret.push_back(line_start); + ret.push_back(line_end + 1); + line_start = word_start; + line_end = line_start; + } + } } } + if (line_start < p_string.length()) { + ret.push_back(line_start); + ret.push_back(p_string.length()); + } return ret; } diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 11f37ab6d1..49e89214ec 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -786,7 +786,7 @@ public: MODBIND1RC(double, shaped_text_get_underline_position, const RID &); MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &); - MODBIND2RC(PackedInt32Array, string_get_word_breaks, const String &, const String &); + MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int); MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); |