diff options
27 files changed, 815 insertions, 511 deletions
diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml index 984ad85ff1..6b379e0509 100644 --- a/doc/classes/Camera3D.xml +++ b/doc/classes/Camera3D.xml @@ -194,7 +194,7 @@ The camera's projection mode. In [constant PROJECTION_PERSPECTIVE] mode, objects' Z distance from the camera's local space scales their perceived size. </member> <member name="size" type="float" setter="set_size" getter="get_size" default="1.0"> - The camera's size measured as 1/2 the width or height. Only applicable in orthogonal and frustum modes. Since [member keep_aspect] locks on axis, [code]size[/code] sets the other axis' size length. + The camera's size in meters measured as the diameter of the width or height, depending on [member keep_aspect]. Only applicable in orthogonal and frustum modes. </member> <member name="v_offset" type="float" setter="set_v_offset" getter="get_v_offset" default="0.0"> The vertical (Y) offset of the camera viewport. diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index 01a011021f..f15c0d74ca 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -15,10 +15,10 @@ <param index="0" name="position" type="Vector2" /> <param index="1" name="in" type="Vector2" default="Vector2(0, 0)" /> <param index="2" name="out" type="Vector2" default="Vector2(0, 0)" /> - <param index="3" name="at_position" type="int" default="-1" /> + <param index="3" name="index" type="int" default="-1" /> <description> - Adds a point to a curve at [param position] relative to the [Curve2D]'s position, with control points [param in] and [param out]. - If [param at_position] is given, the point is inserted before the point number [param at_position], moving that point (and every point after) after the inserted point. If [param at_position] is not given, or is an illegal value ([code]at_position <0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list. + Adds a point with the specified [param position] relative to the curve's own position, with control points [param in] and [param out]. Appends the new point at the end of the point list. + If [param index] is given, the new point is inserted before the existing point identified by index [param index]. Every existing point starting from [param index] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [member point_count]. </description> </method> <method name="clear_points"> diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml index ca3ce1cb64..0843453820 100644 --- a/doc/classes/Curve3D.xml +++ b/doc/classes/Curve3D.xml @@ -15,10 +15,10 @@ <param index="0" name="position" type="Vector3" /> <param index="1" name="in" type="Vector3" default="Vector3(0, 0, 0)" /> <param index="2" name="out" type="Vector3" default="Vector3(0, 0, 0)" /> - <param index="3" name="at_position" type="int" default="-1" /> + <param index="3" name="index" type="int" default="-1" /> <description> - Adds a point to a curve at [param position] relative to the [Curve3D]'s position, with control points [param in] and [param out]. - If [param at_position] is given, the point is inserted before the point number [param at_position], moving that point (and every point after) after the inserted point. If [param at_position] is not given, or is an illegal value ([code]at_position <0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list. + Adds a point with the specified [param position] relative to the curve's own position, with control points [param in] and [param out]. Appends the new point at the end of the point list. + If [param index] is given, the new point is inserted before the existing point identified by index [param index]. Every existing point starting from [param index] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [member point_count]. </description> </method> <method name="clear_points"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 1a811011eb..cb2474384a 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -692,7 +692,7 @@ <return type="int" /> <param index="0" name="screen" type="int" default="-1" /> <description> - Returns the dots per inch density of the specified screen. If [param screen] is [/code]SCREEN_OF_MAIN_WINDOW[/code] (the default value), a screen with the main window will be used. + Returns the dots per inch density of the specified screen. If [param screen] is [constant SCREEN_OF_MAIN_WINDOW] (the default value), a screen with the main window will be used. [b]Note:[/b] On macOS, returned value is inaccurate if fractional display scaling mode is used. [b]Note:[/b] On Android devices, the actual screen densities are grouped into six generalized densities: [codeblock] diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index f6aea83b7f..f4bf803554 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -14,10 +14,10 @@ <method name="add_point"> <return type="void" /> <param index="0" name="position" type="Vector2" /> - <param index="1" name="at_position" type="int" default="-1" /> + <param index="1" name="index" type="int" default="-1" /> <description> - Adds a point at the [param position]. Appends the point at the end of the line. - If [param at_position] is given, the point is inserted before the point number [param at_position], moving that point (and every point after) after the inserted point. If [param at_position] is not given, or is an illegal value ([code]at_position < 0[/code] or [code]at_position >= [method get_point_count][/code]), the point will be appended at the end of the point list. + Adds a point with the specified [param position] relative to the line's own position. Appends the new point at the end of the point list. + If [param index] is given, the new point is inserted before the existing point identified by index [param index]. Every existing point starting from [param index] is shifted further down the list of points. The index must be greater than or equal to [code]0[/code] and must not exceed the number of existing points in the line. See [method get_point_count]. </description> </method> <method name="clear_points"> @@ -29,29 +29,29 @@ <method name="get_point_count" qualifiers="const"> <return type="int" /> <description> - Returns the Line2D's amount of points. + Returns the amount of points in the line. </description> </method> <method name="get_point_position" qualifiers="const"> <return type="Vector2" /> - <param index="0" name="i" type="int" /> + <param index="0" name="index" type="int" /> <description> - Returns point [param i]'s position. + Returns the position of the point at index [param index]. </description> </method> <method name="remove_point"> <return type="void" /> - <param index="0" name="i" type="int" /> + <param index="0" name="index" type="int" /> <description> - Removes the point at index [param i] from the line. + Removes the point at index [param index] from the line. </description> </method> <method name="set_point_position"> <return type="void" /> - <param index="0" name="i" type="int" /> + <param index="0" name="index" type="int" /> <param index="1" name="position" type="Vector2" /> <description> - Overwrites the position in point [param i] with the supplied [param position]. + Overwrites the position of the point at index [param index] with the supplied [param position]. </description> </method> </methods> diff --git a/doc/classes/PopupPanel.xml b/doc/classes/PopupPanel.xml index d850cf20b8..0c6c342f5b 100644 --- a/doc/classes/PopupPanel.xml +++ b/doc/classes/PopupPanel.xml @@ -5,6 +5,7 @@ </brief_description> <description> Class for displaying popups with a panel background. In some cases it might be simpler to use than [Popup], since it provides a configurable background. If you are making windows, better check [Window]. + If any [Control] node is added as a child of this [PopupPanel], it will be stretched to fit the panel's size (similar to how [PanelContainer] works). </description> <tutorials> </tutorials> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 0496b8f34b..0b358bd06f 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -215,12 +215,15 @@ </member> <member name="debug_collisions_hint" type="bool" setter="set_debug_collisions_hint" getter="is_debugging_collisions_hint" default="false"> If [code]true[/code], collision shapes will be visible when running the game from the editor for debugging purposes. + [b]Note:[/b] This property is not designed to be changed at run-time. Changing the value of [member debug_collisions_hint] while the project is running will not have the desired effect. </member> <member name="debug_navigation_hint" type="bool" setter="set_debug_navigation_hint" getter="is_debugging_navigation_hint" default="false"> If [code]true[/code], navigation polygons will be visible when running the game from the editor for debugging purposes. + [b]Note:[/b] This property is not designed to be changed at run-time. Changing the value of [member debug_navigation_hint] while the project is running will not have the desired effect. </member> <member name="debug_paths_hint" type="bool" setter="set_debug_paths_hint" getter="is_debugging_paths_hint" default="false"> If [code]true[/code], curves from [Path2D] and [Path3D] nodes will be visible when running the game from the editor for debugging purposes. + [b]Note:[/b] This property is not designed to be changed at run-time. Changing the value of [member debug_paths_hint] while the project is running will not have the desired effect. </member> <member name="edited_scene_root" type="Node" setter="set_edited_scene_root" getter="get_edited_scene_root"> The root of the edited scene. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index c18291914f..ee3c87b8e6 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -942,7 +942,7 @@ <param index="0" name="string" type="String" /> <param index="1" name="dict" type="PackedStringArray" /> <description> - Returns index of the first string in [code]dict[/dict] which is visually confusable with the [code]string[/string], or [code]-1[/code] if none is found. + Returns index of the first string in [param dict] which is visually confusable with the [param string], or [code]-1[/code] if none is found. [b]Note:[/b] This method doesn't detect invisible characters, for spoof detection use it in combination with [method spoof_check]. [b]Note:[/b] Always returns [code]-1[/code] if the server does not support the [constant FEATURE_UNICODE_SECURITY] feature. </description> @@ -1707,10 +1707,10 @@ Glyph horizontal position is rounded to one quarter of the pixel size, each glyph is rasterized up to four times. </constant> <constant name="SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE" value="20" enum="SubpixelPositioning"> - Maximum font size which will use one half of the pixel subpixel positioning in [constants SUBPIXEL_POSITIONING_AUTO] mode. + Maximum font size which will use one half of the pixel subpixel positioning in [constant SUBPIXEL_POSITIONING_AUTO] mode. </constant> <constant name="SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE" value="16" enum="SubpixelPositioning"> - Maximum font size which will use one quarter of the pixel subpixel positioning in [constants SUBPIXEL_POSITIONING_AUTO] mode. + Maximum font size which will use one quarter of the pixel subpixel positioning in [constant SUBPIXEL_POSITIONING_AUTO] mode. </constant> <constant name="FEATURE_SIMPLE_LAYOUT" value="1" enum="Feature"> TextServer supports simple text layouts. diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index b288dc5416..219052d3d5 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -939,7 +939,7 @@ <param index="0" name="string" type="String" /> <param index="1" name="dict" type="PackedStringArray" /> <description> - Returns index of the first string in [code]dict[/dict] which is visually confusable with the [code]string[/string], or [code]-1[/code] if none is found. + Returns index of the first string in [param dict] which is visually confusable with the [param string], or [code]-1[/code] if none is found. </description> </method> <method name="is_locale_right_to_left" qualifiers="virtual const"> diff --git a/doc/classes/VisualShaderNodeUVFunc.xml b/doc/classes/VisualShaderNodeUVFunc.xml index 37a9769a10..541991b790 100644 --- a/doc/classes/VisualShaderNodeUVFunc.xml +++ b/doc/classes/VisualShaderNodeUVFunc.xml @@ -17,7 +17,7 @@ Translates [code]uv[/code] by using [code]scale[/code] and [code]offset[/code] values using the following formula: [code]uv = uv + offset * scale[/code]. [code]uv[/code] port is connected to [code]UV[/code] built-in by default. </constant> <constant name="FUNC_SCALING" value="1" enum="Function"> - Scales [code]uv[/uv] by using [code]scale[/code] and [code]pivot[/code] values using the following formula: [code]uv = (uv - pivot) * scale + pivot[/code]. [code]uv[/code] port is connected to [code]UV[/code] built-in by default. + Scales [code]uv[/code] by using [code]scale[/code] and [code]pivot[/code] values using the following formula: [code]uv = (uv - pivot) * scale + pivot[/code]. [code]uv[/code] port is connected to [code]UV[/code] built-in by default. </constant> <constant name="FUNC_MAX" value="2" enum="Function"> Represents the size of the [enum Function] enum. diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 207eb7fabd..519554e026 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -67,6 +67,7 @@ STYLES: Dict[str, str] = {} class State: def __init__(self) -> None: self.num_errors = 0 + self.num_warnings = 0 self.classes: OrderedDict[str, ClassDef] = OrderedDict() self.current_class: str = "" @@ -353,7 +354,17 @@ class TypeName: return cls(element.attrib["type"], element.get("enum")) -class PropertyDef: +class DefinitionBase: + def __init__( + self, + definition_name: str, + name: str, + ) -> None: + self.definition_name = definition_name + self.name = name + + +class PropertyDef(DefinitionBase): def __init__( self, name: str, @@ -364,9 +375,8 @@ class PropertyDef: default_value: Optional[str], overrides: Optional[str], ) -> None: - self.definition_name = "property" + super().__init__("property", name) - self.name = name self.type_name = type_name self.setter = setter self.getter = getter @@ -375,25 +385,23 @@ class PropertyDef: self.overrides = overrides -class ParameterDef: +class ParameterDef(DefinitionBase): def __init__(self, name: str, type_name: TypeName, default_value: Optional[str]) -> None: - self.definition_name = "parameter" + super().__init__("parameter", name) - self.name = name self.type_name = type_name self.default_value = default_value -class SignalDef: +class SignalDef(DefinitionBase): def __init__(self, name: str, parameters: List[ParameterDef], description: Optional[str]) -> None: - self.definition_name = "signal" + super().__init__("signal", name) - self.name = name self.parameters = parameters self.description = description -class AnnotationDef: +class AnnotationDef(DefinitionBase): def __init__( self, name: str, @@ -401,15 +409,14 @@ class AnnotationDef: description: Optional[str], qualifiers: Optional[str], ) -> None: - self.definition_name = "annotation" + super().__init__("annotation", name) - self.name = name self.parameters = parameters self.description = description self.qualifiers = qualifiers -class MethodDef: +class MethodDef(DefinitionBase): def __init__( self, name: str, @@ -418,52 +425,47 @@ class MethodDef: description: Optional[str], qualifiers: Optional[str], ) -> None: - self.definition_name = "method" + super().__init__("method", name) - self.name = name self.return_type = return_type self.parameters = parameters self.description = description self.qualifiers = qualifiers -class ConstantDef: +class ConstantDef(DefinitionBase): def __init__(self, name: str, value: str, text: Optional[str], bitfield: bool) -> None: - self.definition_name = "constant" + super().__init__("constant", name) - self.name = name self.value = value self.text = text self.is_bitfield = bitfield -class EnumDef: +class EnumDef(DefinitionBase): def __init__(self, name: str, bitfield: bool) -> None: - self.definition_name = "enum" + super().__init__("enum", name) - self.name = name self.values: OrderedDict[str, ConstantDef] = OrderedDict() self.is_bitfield = bitfield -class ThemeItemDef: +class ThemeItemDef(DefinitionBase): def __init__( self, name: str, type_name: TypeName, data_name: str, text: Optional[str], default_value: Optional[str] ) -> None: - self.definition_name = "theme item" + super().__init__("theme item", name) - self.name = name self.type_name = type_name self.data_name = data_name self.text = text self.default_value = default_value -class ClassDef: +class ClassDef(DefinitionBase): def __init__(self, name: str) -> None: - self.definition_name = "class" + super().__init__("class", name) - self.name = name self.constants: OrderedDict[str, ConstantDef] = OrderedDict() self.enums: OrderedDict[str, EnumDef] = OrderedDict() self.properties: OrderedDict[str, PropertyDef] = OrderedDict() @@ -482,11 +484,7 @@ class ClassDef: self.filepath: str = "" -def print_error(error: str, state: State) -> None: - print("{}{}ERROR:{} {}{}".format(STYLES["red"], STYLES["bold"], STYLES["regular"], error, STYLES["reset"])) - state.num_errors += 1 - - +# Entry point for the RST generator. def main() -> None: # Enable ANSI escape code support on Windows 10 and later (for colored console output). # <https://bugs.python.org/issue29059> @@ -520,6 +518,7 @@ def main() -> None: should_color = args.color or (hasattr(sys.stdout, "isatty") and sys.stdout.isatty()) STYLES["red"] = "\x1b[91m" if should_color else "" STYLES["green"] = "\x1b[92m" if should_color else "" + STYLES["yellow"] = "\x1b[93m" if should_color else "" STYLES["bold"] = "\x1b[1m" if should_color else "" STYLES["regular"] = "\x1b[22m" if should_color else "" STYLES["reset"] = "\x1b[0m" if should_color else "" @@ -604,12 +603,29 @@ def main() -> None: # Create the output folder recursively if it doesn't already exist. os.makedirs(args.output, exist_ok=True) + print("Generating the RST class reference...") + for class_name, class_def in state.classes.items(): if args.filter and not pattern.search(class_def.filepath): continue state.current_class = class_name make_rst_class(class_def, state, args.dry_run, args.output) + print("") + + if state.num_warnings >= 2: + print( + "{}{} warnings were found in the class reference XML. Please check the messages above.{}".format( + STYLES["yellow"], state.num_warnings, STYLES["reset"] + ) + ) + elif state.num_warnings == 1: + print( + "{}1 warning was found in the class reference XML. Please check the messages above.{}".format( + STYLES["yellow"], STYLES["reset"] + ) + ) + if state.num_errors == 0: print("{}No errors found in the class reference XML.{}".format(STYLES["green"], STYLES["reset"])) if not args.dry_run: @@ -630,6 +646,19 @@ def main() -> None: exit(1) +# Common helpers. + + +def print_error(error: str, state: State) -> None: + print("{}{}ERROR:{} {}{}".format(STYLES["red"], STYLES["bold"], STYLES["regular"], error, STYLES["reset"])) + state.num_errors += 1 + + +def print_warning(error: str, state: State) -> None: + print("{}{}WARNING:{} {}{}".format(STYLES["yellow"], STYLES["bold"], STYLES["regular"], error, STYLES["reset"])) + state.num_warnings += 1 + + def translate(string: str) -> str: """Translate a string based on translations sourced from `doc/translations/*.po` for a language if defined via the --lang command line argument. @@ -638,6 +667,9 @@ def translate(string: str) -> str: return strings_l10n.get(string, string) +# Generator methods. + + def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: str) -> None: class_name = class_def.name @@ -705,12 +737,12 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Brief description if class_def.brief_description is not None: - f.write(rstize_text(class_def.brief_description.strip(), class_def, state) + "\n\n") + f.write(format_text_block(class_def.brief_description.strip(), class_def, state) + "\n\n") # Class description if class_def.description is not None and class_def.description.strip() != "": f.write(make_heading("Description", "-")) - f.write(rstize_text(class_def.description.strip(), class_def, state) + "\n\n") + f.write(format_text_block(class_def.description.strip(), class_def, state) + "\n\n") # Online tutorials if len(class_def.tutorials) > 0: @@ -784,7 +816,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- {}\n\n".format(signature)) if signal.description is not None and signal.description.strip() != "": - f.write(rstize_text(signal.description.strip(), signal, state) + "\n\n") + f.write(format_text_block(signal.description.strip(), signal, state) + "\n\n") index += 1 @@ -814,7 +846,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- **{}** = **{}**".format(value.name, value.value)) if value.text is not None and value.text.strip() != "": # If value.text contains a bullet point list, each entry needs additional indentation - f.write(" --- " + indent_bullets(rstize_text(value.text.strip(), value, state))) + f.write(" --- " + indent_bullets(format_text_block(value.text.strip(), value, state))) f.write("\n\n") @@ -831,7 +863,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: for constant in class_def.constants.values(): f.write("- **{}** = **{}**".format(constant.name, constant.value)) if constant.text is not None and constant.text.strip() != "": - f.write(" --- " + rstize_text(constant.text.strip(), constant, state)) + f.write(" --- " + format_text_block(constant.text.strip(), constant, state)) f.write("\n\n") @@ -852,7 +884,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- {}\n\n".format(signature)) if m.description is not None and m.description.strip() != "": - f.write(rstize_text(m.description.strip(), m, state) + "\n\n") + f.write(format_text_block(m.description.strip(), m, state) + "\n\n") index += 1 @@ -884,7 +916,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: format_table(f, info) if property_def.text is not None and property_def.text.strip() != "": - f.write(rstize_text(property_def.text.strip(), property_def, state) + "\n\n") + f.write(format_text_block(property_def.text.strip(), property_def, state) + "\n\n") index += 1 @@ -905,7 +937,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- {} {}\n\n".format(ret_type, signature)) if m.description is not None and m.description.strip() != "": - f.write(rstize_text(m.description.strip(), m, state) + "\n\n") + f.write(format_text_block(m.description.strip(), m, state) + "\n\n") index += 1 @@ -925,7 +957,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- {} {}\n\n".format(ret_type, signature)) if m.description is not None and m.description.strip() != "": - f.write(rstize_text(m.description.strip(), m, state) + "\n\n") + f.write(format_text_block(m.description.strip(), m, state) + "\n\n") index += 1 @@ -949,7 +981,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("- {} {}\n\n".format(ret_type, signature)) if m.description is not None and m.description.strip() != "": - f.write(rstize_text(m.description.strip(), m, state) + "\n\n") + f.write(format_text_block(m.description.strip(), m, state) + "\n\n") index += 1 @@ -974,87 +1006,183 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: format_table(f, info) if theme_item_def.text is not None and theme_item_def.text.strip() != "": - f.write(rstize_text(theme_item_def.text.strip(), theme_item_def, state) + "\n\n") + f.write(format_text_block(theme_item_def.text.strip(), theme_item_def, state) + "\n\n") index += 1 f.write(make_footer()) -def escape_rst(text: str, until_pos: int = -1) -> str: - # Escape \ character, otherwise it ends up as an escape character in rst - pos = 0 - while True: - pos = text.find("\\", pos, until_pos) - if pos == -1: - break - text = text[:pos] + "\\\\" + text[pos + 1 :] - pos += 2 +def make_type(klass: str, state: State) -> str: + if klass.find("*") != -1: # Pointer, ignore + return klass + link_type = klass + if link_type.endswith("[]"): # Typed array, strip [] to link to contained type. + link_type = link_type[:-2] + if link_type in state.classes: + return ":ref:`{}<class_{}>`".format(klass, link_type) + print_error('{}.xml: Unresolved type "{}".'.format(state.current_class, klass), state) + return klass - # Escape * character to avoid interpreting it as emphasis - pos = 0 - while True: - pos = text.find("*", pos, until_pos) - if pos == -1: - break - text = text[:pos] + "\*" + text[pos + 1 :] - pos += 2 - # Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink - pos = 0 - while True: - pos = text.find("_", pos, until_pos) - if pos == -1: - break - if not text[pos + 1].isalnum(): # don't escape within a snake_case word - text = text[:pos] + "\_" + text[pos + 1 :] - pos += 2 - else: - pos += 1 +def make_enum(t: str, state: State) -> str: + p = t.find(".") + if p >= 0: + c = t[0:p] + e = t[p + 1 :] + # Variant enums live in GlobalScope but still use periods. + if c == "Variant": + c = "@GlobalScope" + e = "Variant." + e + else: + c = state.current_class + e = t + if c in state.classes and e not in state.classes[c].enums: + c = "@GlobalScope" - return text + if c in state.classes and e in state.classes[c].enums: + return ":ref:`{0}<enum_{1}_{0}>`".format(e, c) + # Don't fail for `Vector3.Axis`, as this enum is a special case which is expected not to be resolved. + if "{}.{}".format(c, e) != "Vector3.Axis": + print_error('{}.xml: Unresolved enum "{}".'.format(state.current_class, t), state) -def format_codeblock(code_type: str, post_text: str, indent_level: int, state: State) -> Union[Tuple[str, int], None]: - end_pos = post_text.find("[/" + code_type + "]") - if end_pos == -1: - print_error("{}.xml: [" + code_type + "] without a closing tag.".format(state.current_class), state) - return None + return t - code_text = post_text[len("[" + code_type + "]") : end_pos] - post_text = post_text[end_pos:] - # Remove extraneous tabs - code_pos = 0 - while True: - code_pos = code_text.find("\n", code_pos) - if code_pos == -1: - break +def make_method_signature( + class_def: ClassDef, definition: Union[AnnotationDef, MethodDef, SignalDef], ref_type: str, state: State +) -> Tuple[str, str]: + ret_type = "" - to_skip = 0 - while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == "\t": - to_skip += 1 + is_method_def = isinstance(definition, MethodDef) + if is_method_def: + ret_type = definition.return_type.to_rst(state) - if to_skip > indent_level: - print_error( - "{}.xml: Four spaces should be used for indentation within [{}].".format( - state.current_class, code_type - ), - state, + qualifiers = None + if is_method_def or isinstance(definition, AnnotationDef): + qualifiers = definition.qualifiers + + out = "" + + if is_method_def and ref_type != "": + if ref_type == "operator": + out += ":ref:`{0}<class_{1}_{2}_{3}_{4}>` ".format( + definition.name.replace("<", "\\<"), # So operator "<" gets correctly displayed. + class_def.name, + ref_type, + sanitize_operator_name(definition.name, state), + definition.return_type.type_name, ) + else: + out += ":ref:`{0}<class_{1}_{2}_{0}>` ".format(definition.name, class_def.name, ref_type) + else: + out += "**{}** ".format(definition.name) - if len(code_text[code_pos + to_skip + 1 :]) == 0: - code_text = code_text[:code_pos] + "\n" - code_pos += 1 + out += "**(**" + for i, arg in enumerate(definition.parameters): + if i > 0: + out += ", " else: - code_text = code_text[:code_pos] + "\n " + code_text[code_pos + to_skip + 1 :] - code_pos += 5 - to_skip - return ("\n[" + code_type + "]" + code_text + post_text, len("\n[" + code_type + "]" + code_text)) + out += " " + + out += "{} {}".format(arg.type_name.to_rst(state), arg.name) + + if arg.default_value is not None: + out += "=" + arg.default_value + + if qualifiers is not None and "vararg" in qualifiers: + if len(definition.parameters) > 0: + out += ", ..." + else: + out += " ..." + + out += " **)**" + + if qualifiers is not None: + # Use substitutions for abbreviations. This is used to display tooltips on hover. + # See `make_footer()` for descriptions. + for qualifier in qualifiers.split(): + out += " |" + qualifier + "|" + + return ret_type, out + + +def make_heading(title: str, underline: str, l10n: bool = True) -> str: + if l10n: + new_title = translate(title) + if new_title != title: + title = new_title + underline *= 2 # Double length to handle wide chars. + return title + "\n" + (underline * len(title)) + "\n\n" + + +def make_footer() -> str: + # Generate reusable abbreviation substitutions. + # This way, we avoid bloating the generated rST with duplicate abbreviations. + # fmt: off + return ( + ".. |virtual| replace:: :abbr:`virtual (" + translate("This method should typically be overridden by the user to have any effect.") + ")`\n" + ".. |const| replace:: :abbr:`const (" + translate("This method has no side effects. It doesn't modify any of the instance's member variables.") + ")`\n" + ".. |vararg| replace:: :abbr:`vararg (" + translate("This method accepts any number of arguments after the ones described here.") + ")`\n" + ".. |constructor| replace:: :abbr:`constructor (" + translate("This method is used to construct a type.") + ")`\n" + ".. |static| replace:: :abbr:`static (" + translate("This method doesn't need an instance to be called, so it can be called directly using the class name.") + ")`\n" + ".. |operator| replace:: :abbr:`operator (" + translate("This method describes a valid operator to use with this type as left-hand operand.") + ")`\n" + ) + # fmt: on + + +def make_link(url: str, title: str) -> str: + match = GODOT_DOCS_PATTERN.search(url) + if match: + groups = match.groups() + if match.lastindex == 2: + # Doc reference with fragment identifier: emit direct link to section with reference to page, for example: + # `#calling-javascript-from-script in Exporting For Web` + # Or use the title if provided. + if title != "": + return "`" + title + " <../" + groups[0] + ".html" + groups[1] + ">`__" + return "`" + groups[1] + " <../" + groups[0] + ".html" + groups[1] + ">`__ in :doc:`../" + groups[0] + "`" + elif match.lastindex == 1: + # Doc reference, for example: + # `Math` + if title != "": + return ":doc:`" + title + " <../" + groups[0] + ">`" + return ":doc:`../" + groups[0] + "`" + + # External link, for example: + # `http://enet.bespin.org/usergroup0.html` + if title != "": + return "`" + title + " <" + url + ">`__" + return "`" + url + " <" + url + ">`__" + + +# Formatting helpers. + + +RESERVED_FORMATTING_TAGS = ["i", "b", "u", "code", "kbd", "center", "url", "br"] +RESERVED_CODEBLOCK_TAGS = ["codeblocks", "codeblock", "gdscript", "csharp"] +RESERVED_CROSSLINK_TAGS = ["method", "member", "signal", "constant", "enum", "annotation", "theme_item", "param"] -def rstize_text( +def is_in_tagset(tag_text: str, tagset: List[str]) -> bool: + for tag in tagset: + # Complete match. + if tag_text == tag: + return True + # Tag with arguments. + if tag_text.startswith(tag + " "): + return True + # Tag with arguments, special case for [url]. + if tag_text.startswith(tag + "="): + return True + + return False + + +def format_text_block( text: str, - context: Union[ClassDef, SignalDef, ConstantDef, AnnotationDef, PropertyDef, MethodDef, ThemeItemDef, None], + context: Union[DefinitionBase, None], state: State, ) -> str: # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock" @@ -1092,8 +1220,12 @@ def rstize_text( next_brac_pos = text.find("[") text = escape_rst(text, next_brac_pos) + context_name = format_context_name(context) + # Handle [tags] inside_code = False + inside_code_tag = "" + inside_code_tabs = False pos = 0 tag_depth = 0 while True: @@ -1112,240 +1244,337 @@ def rstize_text( escape_pre = False escape_post = False + # Tag is a reference to a class. if tag_text in state.classes: if tag_text == state.current_class: - # We don't want references to the same class + # Don't create a link to the same class, format it as inline code. tag_text = "``{}``".format(tag_text) else: tag_text = make_type(tag_text, state) escape_pre = True escape_post = True - else: # command + + # Tag is a cross-reference or a formating directive. + else: cmd = tag_text space_pos = tag_text.find(" ") - if cmd == "/codeblock" or cmd == "/gdscript" or cmd == "/csharp": - tag_text = "" - tag_depth -= 1 - inside_code = False - # Strip newline if the tag was alone on one - if pre_text[-1] == "\n": - pre_text = pre_text[:-1] - elif cmd == "/code": - tag_text = "``" - tag_depth -= 1 - inside_code = False - escape_post = True - elif inside_code: - tag_text = "[" + tag_text + "]" - elif cmd.find("html") == 0: - param = tag_text[space_pos + 1 :] - tag_text = param - elif ( - cmd.startswith("method") - or cmd.startswith("member") - or cmd.startswith("signal") - or cmd.startswith("constant") - or cmd.startswith("theme_item") - ): - param = tag_text[space_pos + 1 :] - - if param.find(".") != -1: - ss = param.split(".") - if len(ss) > 2: - print_error('{}.xml: Bad reference: "{}".'.format(state.current_class, param), state) - class_param, method_param = ss + # Anything identified as a tag inside of a code block is valid, + # unless it's a matching closing tag. + if inside_code: + # Exiting codeblocks and inline code tags. + + if inside_code_tag == cmd[1:]: + if cmd == "/codeblock" or cmd == "/gdscript" or cmd == "/csharp": + tag_text = "" + tag_depth -= 1 + inside_code = False + # Strip newline if the tag was alone on one + if pre_text[-1] == "\n": + pre_text = pre_text[:-1] + + elif cmd == "/code": + tag_text = "``" + tag_depth -= 1 + inside_code = False + escape_post = True else: - class_param = state.current_class - method_param = param - - ref_type = "" - if class_param in state.classes: - class_def = state.classes[class_param] - if cmd.startswith("constructor"): - if method_param not in class_def.constructors: - print_error( - '{}.xml: Unresolved constructor "{}".'.format(state.current_class, param), state - ) - ref_type = "_constructor" - - elif cmd.startswith("method"): - if method_param not in class_def.methods: - print_error('{}.xml: Unresolved method "{}".'.format(state.current_class, param), state) - ref_type = "_method" - - elif cmd.startswith("operator"): - if method_param not in class_def.operators: - print_error('{}.xml: Unresolved operator "{}".'.format(state.current_class, param), state) - ref_type = "_operator" - - elif cmd.startswith("member"): - if method_param not in class_def.properties: - print_error('{}.xml: Unresolved member "{}".'.format(state.current_class, param), state) - ref_type = "_property" - - elif cmd.startswith("theme_item"): - if method_param not in class_def.theme_items: - print_error('{}.xml: Unresolved theme item "{}".'.format(state.current_class, param), state) - ref_type = "_theme_{}".format(class_def.theme_items[method_param].data_name) - - elif cmd.startswith("signal"): - if method_param not in class_def.signals: - print_error('{}.xml: Unresolved signal "{}".'.format(state.current_class, param), state) - ref_type = "_signal" - - elif cmd.startswith("annotation"): - if method_param not in class_def.annotations: - print_error('{}.xml: Unresolved annotation "{}".'.format(state.current_class, param), state) - ref_type = "_annotation" - - elif cmd.startswith("constant"): - found = False - - # Search in the current class - search_class_defs = [class_def] - - if param.find(".") == -1: - # Also search in @GlobalScope as a last resort if no class was specified - search_class_defs.append(state.classes["@GlobalScope"]) - - for search_class_def in search_class_defs: - if method_param in search_class_def.constants: - class_param = search_class_def.name - found = True - - else: - for enum in search_class_def.enums.values(): - if method_param in enum.values: - class_param = search_class_def.name - found = True - break + if cmd.startswith("/"): + print_warning( + '{}.xml: Potential error inside of a code tag, found a string that looks like a closing tag "[{}]" in {}.'.format( + state.current_class, cmd, context_name + ), + state, + ) + + tag_text = "[" + tag_text + "]" + + # Entering codeblocks and inline code tags. + + elif cmd == "codeblocks": + tag_depth += 1 + tag_text = "\n.. tabs::" + inside_code_tabs = True + elif cmd == "/codeblocks": + tag_depth -= 1 + tag_text = "" + inside_code_tabs = False - if not found: - print_error('{}.xml: Unresolved constant "{}".'.format(state.current_class, param), state) - ref_type = "_constant" + elif cmd == "codeblock" or cmd == "gdscript" or cmd == "csharp": + tag_depth += 1 + if cmd == "gdscript": + if not inside_code_tabs: + print_error( + "{}.xml: GDScript code block is used outside of [codeblocks] in {}.".format( + state.current_class, cmd, context_name + ), + state, + ) + tag_text = "\n .. code-tab:: gdscript\n" + elif cmd == "csharp": + if not inside_code_tabs: + print_error( + "{}.xml: C# code block is used outside of [codeblocks] in {}.".format( + state.current_class, cmd, context_name + ), + state, + ) + tag_text = "\n .. code-tab:: csharp\n" else: - print_error( - '{}.xml: Unresolved type reference "{}" in method reference "{}".'.format( - state.current_class, class_param, param - ), - state, - ) + tag_text = "\n::\n" + + inside_code = True + inside_code_tag = cmd - repl_text = method_param - if class_param != state.current_class: - repl_text = "{}.{}".format(class_param, method_param) - tag_text = ":ref:`{}<class_{}{}_{}>`".format(repl_text, class_param, ref_type, method_param) + elif cmd == "code": + tag_text = "``" + tag_depth += 1 + inside_code = True + inside_code_tag = cmd escape_pre = True - escape_post = True - elif cmd.startswith("param"): - param_name: str = "" - if space_pos >= 0: - param_name = tag_text[space_pos + 1 :].strip() - if param_name == "": - context_name: str = "unknown context" - if context is not None: - context_name = '{} "{}" description'.format(context.definition_name, context.name) + # Cross-references to items in this or other class documentation pages. + elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS): + link_target: str = "" + if space_pos >= 0: + link_target = tag_text[space_pos + 1 :].strip() + if link_target == "": print_error( - "{}.xml: Empty argument reference in {}.".format(state.current_class, context_name), + '{}.xml: Empty cross-reference link "{}" in {}.'.format(state.current_class, cmd, context_name), state, ) + tag_text = "" else: - valid_context = ( - isinstance(context, MethodDef) - or isinstance(context, SignalDef) - or isinstance(context, AnnotationDef) - ) - if not valid_context: - context_name: str = "unknown context" - if context is not None: - context_name = '{} "{}" description'.format(context.definition_name, context.name) + if ( + cmd.startswith("method") + or cmd.startswith("member") + or cmd.startswith("signal") + or cmd.startswith("constant") + or cmd.startswith("annotation") + or cmd.startswith("theme_item") + ): + if link_target.find(".") != -1: + ss = link_target.split(".") + if len(ss) > 2: + print_error( + '{}.xml: Bad reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + class_param, method_param = ss + + else: + class_param = state.current_class + method_param = link_target + + ref_type = "" + if class_param in state.classes: + class_def = state.classes[class_param] + if cmd.startswith("constructor"): + if method_param not in class_def.constructors: + print_error( + '{}.xml: Unresolved constructor reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_constructor" + + elif cmd.startswith("method"): + if method_param not in class_def.methods: + print_error( + '{}.xml: Unresolved method reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_method" + + elif cmd.startswith("operator"): + if method_param not in class_def.operators: + print_error( + '{}.xml: Unresolved operator reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_operator" + + elif cmd.startswith("member"): + if method_param not in class_def.properties: + print_error( + '{}.xml: Unresolved member reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_property" + + elif cmd.startswith("theme_item"): + if method_param not in class_def.theme_items: + print_error( + '{}.xml: Unresolved theme item reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_theme_{}".format(class_def.theme_items[method_param].data_name) + + elif cmd.startswith("signal"): + if method_param not in class_def.signals: + print_error( + '{}.xml: Unresolved signal reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_signal" + + elif cmd.startswith("annotation"): + if method_param not in class_def.annotations: + print_error( + '{}.xml: Unresolved annotation reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_annotation" + + elif cmd.startswith("constant"): + found = False + + # Search in the current class + search_class_defs = [class_def] + + if link_target.find(".") == -1: + # Also search in @GlobalScope as a last resort if no class was specified + search_class_defs.append(state.classes["@GlobalScope"]) + + for search_class_def in search_class_defs: + if method_param in search_class_def.constants: + class_param = search_class_def.name + found = True - print_error( - '{}.xml: Argument reference "{}" used outside of method, signal, or annotation context in {}.'.format( - state.current_class, param_name, context_name - ), - state, + else: + for enum in search_class_def.enums.values(): + if method_param in enum.values: + class_param = search_class_def.name + found = True + break + + if not found: + print_error( + '{}.xml: Unresolved constant reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + ref_type = "_constant" + + else: + print_error( + '{}.xml: Unresolved type reference "{}" in method reference "{}" in {}.'.format( + state.current_class, class_param, link_target, context_name + ), + state, + ) + + repl_text = method_param + if class_param != state.current_class: + repl_text = "{}.{}".format(class_param, method_param) + tag_text = ":ref:`{}<class_{}{}_{}>`".format(repl_text, class_param, ref_type, method_param) + escape_pre = True + escape_post = True + + elif cmd.startswith("enum"): + tag_text = make_enum(link_target, state) + escape_pre = True + escape_post = True + + elif cmd.startswith("param"): + valid_context = ( + isinstance(context, MethodDef) + or isinstance(context, SignalDef) + or isinstance(context, AnnotationDef) ) - else: - context_params: List[ParameterDef] = context.parameters - found = False - for param_def in context_params: - if param_def.name == param_name: - found = True - break - if not found: + if not valid_context: print_error( - '{}.xml: Unresolved argument reference "{}" in {} "{}" description.'.format( - state.current_class, param_name, context.definition_name, context.name + '{}.xml: Argument reference "{}" used outside of method, signal, or annotation context in {}.'.format( + state.current_class, link_target, context_name ), state, ) + else: + context_params: List[ParameterDef] = context.parameters + found = False + for param_def in context_params: + if param_def.name == link_target: + found = True + break + if not found: + print_error( + '{}.xml: Unresolved argument reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + + tag_text = "``{}``".format(link_target) + + # Formatting directives. + + elif is_in_tagset(cmd, ["url"]): + if cmd.startswith("url="): + # URLs are handled in full here as we need to extract the optional link + # title to use `make_link`. + link_url = cmd[4:] + endurl_pos = text.find("[/url]", endq_pos + 1) + if endurl_pos == -1: + print_error( + "{}.xml: Tag depth mismatch for [url]: no closing [/url] in {}.".format( + state.current_class, context_name + ), + state, + ) + break + link_title = text[endq_pos + 1 : endurl_pos] + tag_text = make_link(link_url, link_title) - if param_name == "": - tag_text = "" + pre_text = text[:pos] + post_text = text[endurl_pos + 6 :] + + if pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT: + pre_text += "\ " + if post_text and post_text[0] not in MARKUP_ALLOWED_SUBSEQUENT: + post_text = "\ " + post_text + + text = pre_text + tag_text + post_text + pos = len(pre_text) + len(tag_text) + continue else: - tag_text = "``{}``".format(param_name) - elif cmd.find("image=") == 0: - tag_text = "" # '![](' + cmd[6:] + ')' - elif cmd.find("url=") == 0: - # URLs are handled in full here as we need to extract the optional link - # title to use `make_link`. - link_url = cmd[4:] - endurl_pos = text.find("[/url]", endq_pos + 1) - if endurl_pos == -1: print_error( - "{}.xml: Tag depth mismatch for [url]: no closing [/url]".format(state.current_class), state + '{}.xml: Misformatted [url] tag "{}" in {}.'.format(state.current_class, cmd, context_name), + state, ) - break - link_title = text[endq_pos + 1 : endurl_pos] - tag_text = make_link(link_url, link_title) - pre_text = text[:pos] - post_text = text[endurl_pos + 6 :] - - if pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT: - pre_text += "\ " - if post_text and post_text[0] not in MARKUP_ALLOWED_SUBSEQUENT: - post_text = "\ " + post_text - - text = pre_text + tag_text + post_text - pos = len(pre_text) + len(tag_text) - continue - elif cmd == "center": - tag_depth += 1 - tag_text = "" - elif cmd == "/center": - tag_depth -= 1 - tag_text = "" - elif cmd == "codeblock": - tag_depth += 1 - tag_text = "\n::\n" - inside_code = True - elif cmd == "gdscript": - tag_depth += 1 - tag_text = "\n .. code-tab:: gdscript\n" - inside_code = True - elif cmd == "csharp": - tag_depth += 1 - tag_text = "\n .. code-tab:: csharp\n" - inside_code = True - elif cmd == "codeblocks": - tag_depth += 1 - tag_text = "\n.. tabs::" - elif cmd == "/codeblocks": - tag_depth -= 1 - tag_text = "" elif cmd == "br": # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly tag_text = "\n\n" # Strip potential leading spaces while post_text[0] == " ": post_text = post_text[1:] + + elif cmd == "center" or cmd == "/center": + if cmd == "/center": + tag_depth -= 1 + else: + tag_depth += 1 + tag_text = "" + elif cmd == "i" or cmd == "/i": if cmd == "/i": tag_depth -= 1 @@ -1354,6 +1583,7 @@ def rstize_text( tag_depth += 1 escape_pre = True tag_text = "*" + elif cmd == "b" or cmd == "/b": if cmd == "/b": tag_depth -= 1 @@ -1362,6 +1592,7 @@ def rstize_text( tag_depth += 1 escape_pre = True tag_text = "**" + elif cmd == "u" or cmd == "/u": if cmd == "/u": tag_depth -= 1 @@ -1370,25 +1601,31 @@ def rstize_text( tag_depth += 1 escape_pre = True tag_text = "" - elif cmd == "code": - tag_text = "``" - tag_depth += 1 - inside_code = True - escape_pre = True - elif cmd == "kbd": - tag_text = ":kbd:`" - tag_depth += 1 - escape_pre = True - elif cmd == "/kbd": + + elif cmd == "kbd" or cmd == "/kbd": tag_text = "`" - tag_depth -= 1 - escape_post = True - elif cmd.startswith("enum "): - tag_text = make_enum(cmd[5:], state) - escape_pre = True - escape_post = True + if cmd == "/kbd": + tag_depth -= 1 + escape_post = True + else: + tag_text = ":kbd:" + tag_text + tag_depth += 1 + escape_pre = True + + # Invalid syntax checks. + elif cmd.startswith("/"): + print_error( + '{}.xml: Unrecognized closing tag "{}" in {}.'.format(state.current_class, cmd, context_name), state + ) + + tag_text = "[" + tag_text + "]" + else: - tag_text = make_type(tag_text, state) + print_error( + '{}.xml: Unrecognized opening tag "{}" in {}.'.format(state.current_class, cmd, context_name), state + ) + + tag_text = "``{}``".format(tag_text) escape_pre = True escape_post = True @@ -1423,12 +1660,94 @@ def rstize_text( if tag_depth > 0: print_error( - "{}.xml: Tag depth mismatch: too many (or too little) open/close tags.".format(state.current_class), state + "{}.xml: Tag depth mismatch: too many (or too little) open/close tags in {}.".format( + state.current_class, context_name + ), + state, ) return text +def format_context_name(context: Union[DefinitionBase, None]) -> str: + context_name: str = "unknown context" + if context is not None: + context_name = '{} "{}" description'.format(context.definition_name, context.name) + + return context_name + + +def escape_rst(text: str, until_pos: int = -1) -> str: + # Escape \ character, otherwise it ends up as an escape character in rst + pos = 0 + while True: + pos = text.find("\\", pos, until_pos) + if pos == -1: + break + text = text[:pos] + "\\\\" + text[pos + 1 :] + pos += 2 + + # Escape * character to avoid interpreting it as emphasis + pos = 0 + while True: + pos = text.find("*", pos, until_pos) + if pos == -1: + break + text = text[:pos] + "\*" + text[pos + 1 :] + pos += 2 + + # Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink + pos = 0 + while True: + pos = text.find("_", pos, until_pos) + if pos == -1: + break + if not text[pos + 1].isalnum(): # don't escape within a snake_case word + text = text[:pos] + "\_" + text[pos + 1 :] + pos += 2 + else: + pos += 1 + + return text + + +def format_codeblock(code_type: str, post_text: str, indent_level: int, state: State) -> Union[Tuple[str, int], None]: + end_pos = post_text.find("[/" + code_type + "]") + if end_pos == -1: + print_error("{}.xml: [" + code_type + "] without a closing tag.".format(state.current_class), state) + return None + + code_text = post_text[len("[" + code_type + "]") : end_pos] + post_text = post_text[end_pos:] + + # Remove extraneous tabs + code_pos = 0 + while True: + code_pos = code_text.find("\n", code_pos) + if code_pos == -1: + break + + to_skip = 0 + while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == "\t": + to_skip += 1 + + if to_skip > indent_level: + print_error( + "{}.xml: Four spaces should be used for indentation within [{}].".format( + state.current_class, code_type + ), + state, + ) + + if len(code_text[code_pos + to_skip + 1 :]) == 0: + code_text = code_text[:code_pos] + "\n" + code_pos += 1 + else: + code_text = code_text[:code_pos] + "\n " + code_text[code_pos + to_skip + 1 :] + code_pos += 5 - to_skip + return ("\n[" + code_type + "]" + code_text + post_text, len("\n[" + code_type + "]" + code_text)) + + def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_columns: bool = False) -> None: if len(data) == 0: return @@ -1460,150 +1779,6 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ f.write("\n") -def make_type(klass: str, state: State) -> str: - if klass.find("*") != -1: # Pointer, ignore - return klass - link_type = klass - if link_type.endswith("[]"): # Typed array, strip [] to link to contained type. - link_type = link_type[:-2] - if link_type in state.classes: - return ":ref:`{}<class_{}>`".format(klass, link_type) - print_error('{}.xml: Unresolved type "{}".'.format(state.current_class, klass), state) - return klass - - -def make_enum(t: str, state: State) -> str: - p = t.find(".") - if p >= 0: - c = t[0:p] - e = t[p + 1 :] - # Variant enums live in GlobalScope but still use periods. - if c == "Variant": - c = "@GlobalScope" - e = "Variant." + e - else: - c = state.current_class - e = t - if c in state.classes and e not in state.classes[c].enums: - c = "@GlobalScope" - - if c in state.classes and e in state.classes[c].enums: - return ":ref:`{0}<enum_{1}_{0}>`".format(e, c) - - # Don't fail for `Vector3.Axis`, as this enum is a special case which is expected not to be resolved. - if "{}.{}".format(c, e) != "Vector3.Axis": - print_error('{}.xml: Unresolved enum "{}".'.format(state.current_class, t), state) - - return t - - -def make_method_signature( - class_def: ClassDef, definition: Union[AnnotationDef, MethodDef, SignalDef], ref_type: str, state: State -) -> Tuple[str, str]: - ret_type = "" - - is_method_def = isinstance(definition, MethodDef) - if is_method_def: - ret_type = definition.return_type.to_rst(state) - - qualifiers = None - if is_method_def or isinstance(definition, AnnotationDef): - qualifiers = definition.qualifiers - - out = "" - - if is_method_def and ref_type != "": - if ref_type == "operator": - out += ":ref:`{0}<class_{1}_{2}_{3}_{4}>` ".format( - definition.name.replace("<", "\\<"), # So operator "<" gets correctly displayed. - class_def.name, - ref_type, - sanitize_operator_name(definition.name, state), - definition.return_type.type_name, - ) - else: - out += ":ref:`{0}<class_{1}_{2}_{0}>` ".format(definition.name, class_def.name, ref_type) - else: - out += "**{}** ".format(definition.name) - - out += "**(**" - for i, arg in enumerate(definition.parameters): - if i > 0: - out += ", " - else: - out += " " - - out += "{} {}".format(arg.type_name.to_rst(state), arg.name) - - if arg.default_value is not None: - out += "=" + arg.default_value - - if qualifiers is not None and "vararg" in qualifiers: - if len(definition.parameters) > 0: - out += ", ..." - else: - out += " ..." - - out += " **)**" - - if qualifiers is not None: - # Use substitutions for abbreviations. This is used to display tooltips on hover. - # See `make_footer()` for descriptions. - for qualifier in qualifiers.split(): - out += " |" + qualifier + "|" - - return ret_type, out - - -def make_heading(title: str, underline: str, l10n: bool = True) -> str: - if l10n: - new_title = translate(title) - if new_title != title: - title = new_title - underline *= 2 # Double length to handle wide chars. - return title + "\n" + (underline * len(title)) + "\n\n" - - -def make_footer() -> str: - # Generate reusable abbreviation substitutions. - # This way, we avoid bloating the generated rST with duplicate abbreviations. - # fmt: off - return ( - ".. |virtual| replace:: :abbr:`virtual (" + translate("This method should typically be overridden by the user to have any effect.") + ")`\n" - ".. |const| replace:: :abbr:`const (" + translate("This method has no side effects. It doesn't modify any of the instance's member variables.") + ")`\n" - ".. |vararg| replace:: :abbr:`vararg (" + translate("This method accepts any number of arguments after the ones described here.") + ")`\n" - ".. |constructor| replace:: :abbr:`constructor (" + translate("This method is used to construct a type.") + ")`\n" - ".. |static| replace:: :abbr:`static (" + translate("This method doesn't need an instance to be called, so it can be called directly using the class name.") + ")`\n" - ".. |operator| replace:: :abbr:`operator (" + translate("This method describes a valid operator to use with this type as left-hand operand.") + ")`\n" - ) - # fmt: on - - -def make_link(url: str, title: str) -> str: - match = GODOT_DOCS_PATTERN.search(url) - if match: - groups = match.groups() - if match.lastindex == 2: - # Doc reference with fragment identifier: emit direct link to section with reference to page, for example: - # `#calling-javascript-from-script in Exporting For Web` - # Or use the title if provided. - if title != "": - return "`" + title + " <../" + groups[0] + ".html" + groups[1] + ">`__" - return "`" + groups[1] + " <../" + groups[0] + ".html" + groups[1] + ">`__ in :doc:`../" + groups[0] + "`" - elif match.lastindex == 1: - # Doc reference, for example: - # `Math` - if title != "": - return ":doc:`" + title + " <../" + groups[0] + ">`" - return ":doc:`../" + groups[0] + "`" - - # External link, for example: - # `http://enet.bespin.org/usergroup0.html` - if title != "": - return "`" + title + " <" + url + ">`__" - return "`" + url + " <" + url + ">`__" - - def sanitize_operator_name(dirty_name: str, state: State) -> str: clear_name = dirty_name.replace("operator ", "") diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 2e6de95a46..b301bbf0f9 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -139,7 +139,7 @@ void ResourceImporterLayeredTexture::get_import_options(const String &p_path, Li r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.7)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/bptc_ldr", PROPERTY_HINT_ENUM, "Disabled,Enabled,RGBA Only"), 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized,Normal Map (RG Channels)"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit", PROPERTY_HINT_RANGE, "-1,256"), -1)); @@ -250,7 +250,7 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons } if (p_mipmaps) { - p_images.write[i]->generate_mipmaps(); + p_images.write[i]->generate_mipmaps(p_csource == Image::COMPRESS_SOURCE_NORMAL); } else { p_images.write[i]->clear_mipmaps(); } @@ -354,6 +354,9 @@ Error ResourceImporterLayeredTexture::import(const String &p_source_file, const Image::CompressSource csource = Image::COMPRESS_SOURCE_GENERIC; if (channel_pack == 0) { csource = Image::COMPRESS_SOURCE_SRGB; + } else if (channel_pack == 2) { + // force normal + csource = Image::COMPRESS_SOURCE_NORMAL; } Image::UsedChannels used_channels = image->detect_used_channels(csource); diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 6c544e4437..5ce837f862 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -516,6 +516,7 @@ static const char *gdscript_function_renames[][2] = { { "set_region_filter_clip", "set_region_filter_clip_enabled" }, // Sprite2D { "set_rotate", "set_rotates" }, // PathFollow2D { "set_scancode", "set_keycode" }, // InputEventKey + { "set_shader_param", "set_shader_uniform" }, // ShaderMaterial { "set_shift", "set_shift_pressed" }, // InputEventWithModifiers { "set_size_override", "set_size_2d_override" }, // SubViewport broke ImageTexture { "set_size_override_stretch", "set_size_2d_override_stretch" }, // SubViewport @@ -1618,6 +1619,7 @@ public: RegEx reg_image_lock = RegEx("([a-zA-Z0-9_\\.]+)\\.lock\\(\\)"); RegEx reg_image_unlock = RegEx("([a-zA-Z0-9_\\.]+)\\.unlock\\(\\)"); RegEx reg_os_fullscreen = RegEx("OS.window_fullscreen[= ]+([^#^\n]+)"); + RegEx reg_instantiate = RegEx("\\.instance\\(([^\\)]*)\\)"); }; // Function responsible for converting project @@ -1690,7 +1692,6 @@ int ProjectConverter3To4::convert() { rename_common(builtin_types_renames, file_content); custom_rename(file_content, "\\.shader", ".gdshader"); - custom_rename(file_content, "instance", "instantiate"); } else if (file_name.ends_with(".tscn")) { rename_classes(file_content); // Using only specialized function @@ -1835,7 +1836,6 @@ int ProjectConverter3To4::validate_conversion() { changed_elements.append_array(check_for_rename_common(shaders_renames, file_content)); changed_elements.append_array(check_for_rename_common(builtin_types_renames, file_content)); - changed_elements.append_array(check_for_custom_rename(file_content, "instance", "instantiate")); changed_elements.append_array(check_for_custom_rename(file_content, "\\.shader", ".gdshader")); } else if (file_name.ends_with(".tscn")) { changed_elements.append_array(check_for_rename_classes(file_content)); @@ -2188,6 +2188,15 @@ bool ProjectConverter3To4::test_conversion(const RegExContainer ®_container) } valid = valid & (got == expected); } +{ + String base = "var node = $world/ukraine/lviv."; + String expected = "$world/ukraine/lviv."; + String got = get_object_of_execution(base); + if (got != expected) { + ERR_PRINT("Failed to get proper data from get_object_of_execution `" + base + "` should return `" + expected + "`(" + itos(expected.size()) + "), got instead `" + got + "`(" + itos(got.size()) + ")"); + } + valid = valid & (got == expected); +} } // get_starting_space { @@ -2539,22 +2548,43 @@ String ProjectConverter3To4::get_starting_space(const String &line) const { // so it is `var roman = kieliszek.` and this function return `kieliszek.` String ProjectConverter3To4::get_object_of_execution(const String &line) const { int end = line.size() - 1; // Last one is \0 + int variable_start = end - 1; int start = end - 1; + bool is_possibly_nodepath = false; + bool is_valid_nodepath = false; + while (start >= 0) { char32_t character = line[start]; - if ((character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') || character == '.' || character == '_') { + bool is_variable_char = (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') || character == '.' || character == '_'; + bool is_nodepath_start = character == '$'; + bool is_nodepath_sep = character == '/'; + if (is_variable_char || is_nodepath_start || is_nodepath_sep) { if (start == 0) { break; + } else if (is_nodepath_sep) { + // Freeze variable_start, try to fetch more chars since this might be node path literal + is_possibly_nodepath = true; + } else if (is_nodepath_start) { + // Found $, this is a node path literal + is_valid_nodepath = true; + break; + } + if (!is_possibly_nodepath) { + variable_start--; } start--; continue; } else { - start++; // Found invalid character, needs to be ignored + // Abandon all hope, this is neither a variable nor a node path literal + variable_start++; // Found invalid character, needs to be ignored break; } } - return line.substr(start, (end - start)); + if (is_valid_nodepath) { + variable_start = start; + } + return line.substr(variable_start, (end - variable_start)); } void ProjectConverter3To4::rename_enums(String &file_content) { @@ -2775,6 +2805,9 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai line = reg_container.reg_os_fullscreen.sub(line, "ProjectSettings.set(\"display/window/size/fullscreen\", $1)", true); } + // Instantiate + line = reg_container.reg_instantiate.sub(line, ".instantiate($1)", true); + // -- r.move_and_slide( a, b, c, d, e ) -> r.set_velocity(a) ... r.move_and_slide() KinematicBody if (line.find("move_and_slide(") != -1) { int start = line.find("move_and_slide("); diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml index 6ff8c1ad26..d1a2ce2d2e 100644 --- a/modules/openxr/doc_classes/OpenXRAction.xml +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -5,7 +5,7 @@ </brief_description> <description> This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics). - OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/core] if the trigger is depressed and [code]true[/code] if pressed fully. + OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/code] if the trigger is depressed and [code]true[/code] if pressed fully. Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. Note that the name of the resource is used to register the action with. </description> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 34086add2a..685b1f01af 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -248,6 +248,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' +#ifndef ANDROID_ENABLED void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud); @@ -277,7 +278,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } } -#ifndef ANDROID_ENABLED // Check for devices updates String adb = get_adb_path(); if (FileAccess::exists(adb)) { @@ -389,7 +389,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { ea->devices_changed.set(); } } -#endif uint64_t sleep = 200; uint64_t wait = 3000000; @@ -402,7 +401,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } } -#ifndef ANDROID_ENABLED if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) { String adb = get_adb_path(); if (!FileAccess::exists(adb)) { @@ -413,8 +411,8 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { args.push_back("kill-server"); OS::get_singleton()->execute(adb, args); } -#endif } +#endif String EditorExportPlatformAndroid::get_project_name(const String &p_name) const { String aname; @@ -3138,10 +3136,14 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { devices_changed.set(); plugins_changed.set(); +#ifndef ANDROID_ENABLED check_for_changes_thread.start(_check_for_changes_poll_thread, this); +#endif } EditorExportPlatformAndroid::~EditorExportPlatformAndroid() { +#ifndef ANDROID_ENABLED quit_request.set(); check_for_changes_thread.wait_to_finish(); +#endif } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 9455967053..46012bd46c 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -80,10 +80,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { Vector<Device> devices; SafeFlag devices_changed; Mutex device_lock; +#ifndef ANDROID_ENABLED Thread check_for_changes_thread; SafeFlag quit_request; static void _check_for_changes_poll_thread(void *ud); +#endif String get_project_name(const String &p_name) const; diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index 6b21c18d59..56561cb616 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -46,6 +46,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_seek_end = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_read = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_tell = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_eof = nullptr; +jmethodID FileAccessFilesystemJAndroid::_file_set_eof = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_close = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr; jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr; @@ -162,6 +163,16 @@ bool FileAccessFilesystemJAndroid::eof_reached() const { } } +void FileAccessFilesystemJAndroid::_set_eof(bool eof) { + if (_file_set_eof) { + ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); + + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(file_access_handler, _file_set_eof, id, eof); + } +} + uint8_t FileAccessFilesystemJAndroid::get_8() const { ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); uint8_t byte; @@ -184,6 +195,7 @@ String FileAccessFilesystemJAndroid::get_line() const { while (true) { size_t line_buffer_size = MIN(buffer_size_limit, file_size - get_position()); if (line_buffer_size <= 0) { + const_cast<FileAccessFilesystemJAndroid *>(this)->_set_eof(true); break; } @@ -310,6 +322,7 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) { _file_get_size = env->GetMethodID(cls, "fileGetSize", "(I)J"); _file_tell = env->GetMethodID(cls, "fileGetPosition", "(I)J"); _file_eof = env->GetMethodID(cls, "isFileEof", "(I)Z"); + _file_set_eof = env->GetMethodID(cls, "setFileEof", "(IZ)V"); _file_seek = env->GetMethodID(cls, "fileSeek", "(IJ)V"); _file_seek_end = env->GetMethodID(cls, "fileSeekFromEnd", "(IJ)V"); _file_read = env->GetMethodID(cls, "fileRead", "(ILjava/nio/ByteBuffer;)I"); diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 7deb8de37b..76d7db6e3a 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -44,6 +44,7 @@ class FileAccessFilesystemJAndroid : public FileAccess { static jmethodID _file_seek_end; static jmethodID _file_tell; static jmethodID _file_eof; + static jmethodID _file_set_eof; static jmethodID _file_read; static jmethodID _file_write; static jmethodID _file_flush; @@ -56,6 +57,7 @@ class FileAccessFilesystemJAndroid : public FileAccess { String path_src; void _close(); ///< close a file + void _set_eof(bool eof); public: virtual Error _open(const String &p_path, int p_mode_flags) override; ///< open a file diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt index 463dabfb23..f23537a29e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt @@ -104,7 +104,6 @@ internal abstract class DataAccess(private val filePath: String) { protected abstract val fileChannel: FileChannel internal var endOfFile = false - private set fun close() { try { @@ -125,9 +124,7 @@ internal abstract class DataAccess(private val filePath: String) { fun seek(position: Long) { try { fileChannel.position(position) - if (position <= size()) { - endOfFile = false - } + endOfFile = position >= fileChannel.size() } catch (e: Exception) { Log.w(TAG, "Exception when seeking file $filePath.", e) } @@ -161,8 +158,7 @@ internal abstract class DataAccess(private val filePath: String) { fun read(buffer: ByteBuffer): Int { return try { val readBytes = fileChannel.read(buffer) - endOfFile = readBytes == -1 - || (fileChannel.position() >= fileChannel.size() && fileChannel.size() > 0) + endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size()) if (readBytes == -1) { 0 } else { diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 04b6772c45..83da3a24b3 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -194,6 +194,11 @@ class FileAccessHandler(val context: Context) { return files[fileId].endOfFile } + fun setFileEof(fileId: Int, eof: Boolean) { + val file = files[fileId] ?: return + file.endOfFile = eof + } + fun fileClose(fileId: Int) { if (hasFileId(fileId)) { files[fileId].close() diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 2771ab8ca2..425a977569 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -1884,10 +1884,14 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorEx EditorExportPlatformIOS::EditorExportPlatformIOS() { logo = ImageTexture::create_from_image(memnew(Image(_ios_logo))); plugins_changed.set(); +#ifndef ANDROID_ENABLED check_for_changes_thread.start(_check_for_changes_poll_thread, this); +#endif } EditorExportPlatformIOS::~EditorExportPlatformIOS() { +#ifndef ANDROID_ENABLED quit_request.set(); check_for_changes_thread.wait_to_finish(); +#endif } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 8079eda8f6..abda8e218a 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -57,8 +57,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform { // Plugins SafeFlag plugins_changed; +#ifndef ANDROID_ENABLED Thread check_for_changes_thread; SafeFlag quit_request; +#endif Mutex plugins_lock; Vector<PluginConfigIOS> plugins; @@ -139,6 +141,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { return true; } +#ifndef ANDROID_ENABLED static void _check_for_changes_poll_thread(void *ud) { EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud); @@ -172,6 +175,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { } } } +#endif protected: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 06e5cbc97e..837f3061f1 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -346,13 +346,13 @@ void Line2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_points", "points"), &Line2D::set_points); ClassDB::bind_method(D_METHOD("get_points"), &Line2D::get_points); - ClassDB::bind_method(D_METHOD("set_point_position", "i", "position"), &Line2D::set_point_position); - ClassDB::bind_method(D_METHOD("get_point_position", "i"), &Line2D::get_point_position); + ClassDB::bind_method(D_METHOD("set_point_position", "index", "position"), &Line2D::set_point_position); + ClassDB::bind_method(D_METHOD("get_point_position", "index"), &Line2D::get_point_position); ClassDB::bind_method(D_METHOD("get_point_count"), &Line2D::get_point_count); - ClassDB::bind_method(D_METHOD("add_point", "position", "at_position"), &Line2D::add_point, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("remove_point", "i"), &Line2D::remove_point); + ClassDB::bind_method(D_METHOD("add_point", "position", "index"), &Line2D::add_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_point", "index"), &Line2D::remove_point); ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 06aa913eb1..6d0380c898 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2262,6 +2262,15 @@ void Control::_notify_theme_changed() { } } +void Control::_invalidate_theme_cache() { + data.theme_icon_cache.clear(); + data.theme_style_cache.clear(); + data.theme_font_cache.clear(); + data.theme_font_size_cache.clear(); + data.theme_color_cache.clear(); + data.theme_constant_cache.clear(); +} + void Control::set_theme(const Ref<Theme> &p_theme) { if (data.theme == p_theme) { return; @@ -2443,9 +2452,15 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam } } + if (data.theme_icon_cache.has(p_theme_type) && data.theme_icon_cache[p_theme_type].has(p_name)) { + return data.theme_icon_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + Ref<Texture2D> icon = get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + data.theme_icon_cache[p_theme_type][p_name] = icon; + return icon; } Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { @@ -2456,9 +2471,15 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String } } + if (data.theme_style_cache.has(p_theme_type) && data.theme_style_cache[p_theme_type].has(p_name)) { + return data.theme_style_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + Ref<StyleBox> style = get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + data.theme_style_cache[p_theme_type][p_name] = style; + return style; } Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { @@ -2469,9 +2490,15 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ } } + if (data.theme_font_cache.has(p_theme_type) && data.theme_font_cache[p_theme_type].has(p_name)) { + return data.theme_font_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + Ref<Font> font = get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + data.theme_font_cache[p_theme_type][p_name] = font; + return font; } int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { @@ -2482,9 +2509,15 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t } } + if (data.theme_font_size_cache.has(p_theme_type) && data.theme_font_size_cache[p_theme_type].has(p_name)) { + return data.theme_font_size_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + int font_size = get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + data.theme_font_size_cache[p_theme_type][p_name] = font_size; + return font_size; } Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { @@ -2495,9 +2528,15 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the } } + if (data.theme_color_cache.has(p_theme_type) && data.theme_color_cache[p_theme_type].has(p_name)) { + return data.theme_color_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + Color color = get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + data.theme_color_cache[p_theme_type][p_name] = color; + return color; } int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { @@ -2508,9 +2547,15 @@ int Control::get_theme_constant(const StringName &p_name, const StringName &p_th } } + if (data.theme_constant_cache.has(p_theme_type) && data.theme_constant_cache[p_theme_type].has(p_name)) { + return data.theme_constant_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + int constant = get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + data.theme_constant_cache[p_theme_type][p_name] = constant; + return constant; } bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { @@ -3007,6 +3052,10 @@ void Control::remove_child_notify(Node *p_child) { void Control::_notification(int p_notification) { switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + _invalidate_theme_cache(); + } break; + case NOTIFICATION_POST_ENTER_TREE: { data.minimum_size_valid = false; data.is_rtl_dirty = true; @@ -3144,6 +3193,7 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_THEME_CHANGED: { + _invalidate_theme_cache(); update_minimum_size(); update(); } break; @@ -3164,6 +3214,7 @@ void Control::_notification(int p_notification) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { if (is_inside_tree()) { data.is_rtl_dirty = true; + _invalidate_theme_cache(); _size_changed(); } } break; diff --git a/scene/gui/control.h b/scene/gui/control.h index 9f17eccc3b..db19d09b11 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -229,6 +229,13 @@ private: Theme::ThemeColorMap color_override; Theme::ThemeConstantMap constant_override; + mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache; + mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache; + mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache; + mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache; + mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache; + mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache; + // Internationalization. LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; @@ -291,6 +298,7 @@ private: void _theme_changed(); void _theme_property_override_changed(); void _notify_theme_changed(); + void _invalidate_theme_cache(); static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 5de92acb75..1835604285 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -1167,7 +1167,7 @@ void Curve2D::_get_property_list(List<PropertyInfo> *p_list) const { void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Curve2D::get_point_count); ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve2D::set_point_count); - ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve2D::add_point, DEFVAL(Vector2()), DEFVAL(Vector2()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve2D::set_point_position); ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve2D::get_point_position); ClassDB::bind_method(D_METHOD("set_point_in", "idx", "position"), &Curve2D::set_point_in); @@ -1972,7 +1972,7 @@ void Curve3D::_get_property_list(List<PropertyInfo> *p_list) const { void Curve3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Curve3D::get_point_count); ClassDB::bind_method(D_METHOD("set_point_count", "count"), &Curve3D::set_point_count); - ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "at_position"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_point", "position", "in", "out", "index"), &Curve3D::add_point, DEFVAL(Vector3()), DEFVAL(Vector3()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_point_position", "idx", "position"), &Curve3D::set_point_position); ClassDB::bind_method(D_METHOD("get_point_position", "idx"), &Curve3D::get_point_position); ClassDB::bind_method(D_METHOD("set_point_tilt", "idx", "tilt"), &Curve3D::set_point_tilt); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 619036d296..880876baed 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -2808,6 +2808,8 @@ void SystemFont::_update_base_font() { file->set_oversampling(oversampling); base_font = file; + + break; } if (base_font.is_valid()) { |