summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/classes/@GlobalScope.xml12
-rw-r--r--doc/classes/AStar3D.xml10
-rw-r--r--doc/classes/AStarGrid2D.xml6
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml2
-rw-r--r--doc/classes/BaseMaterial3D.xml4
-rw-r--r--doc/classes/Basis.xml2
-rw-r--r--doc/classes/Bone2D.xml2
-rw-r--r--doc/classes/Button.xml2
-rw-r--r--doc/classes/Camera3D.xml2
-rw-r--r--doc/classes/CharacterBody2D.xml4
-rw-r--r--doc/classes/CharacterBody3D.xml4
-rw-r--r--doc/classes/CodeEdit.xml2
-rw-r--r--doc/classes/CollisionObject2D.xml2
-rw-r--r--doc/classes/CollisionPolygon3D.xml2
-rw-r--r--doc/classes/Control.xml2
-rw-r--r--doc/classes/Curve3D.xml8
-rw-r--r--doc/classes/DampedSpringJoint2D.xml2
-rw-r--r--doc/classes/DisplayServer.xml46
-rw-r--r--doc/classes/EditorExportPlugin.xml4
-rw-r--r--doc/classes/EditorFileDialog.xml2
-rw-r--r--doc/classes/EditorImportPlugin.xml2
-rw-r--r--doc/classes/EditorInterface.xml2
-rw-r--r--doc/classes/EditorNode3DGizmo.xml2
-rw-r--r--doc/classes/EditorNode3DGizmoPlugin.xml2
-rw-r--r--doc/classes/Environment.xml8
-rw-r--r--doc/classes/FileAccess.xml4
-rw-r--r--doc/classes/FontFile.xml2
-rw-r--r--doc/classes/FontVariation.xml2
-rw-r--r--doc/classes/GPUParticles3D.xml4
-rw-r--r--doc/classes/HTTPRequest.xml6
-rw-r--r--doc/classes/Image.xml4
-rw-r--r--doc/classes/ImageTextureLayered.xml2
-rw-r--r--doc/classes/JavaScriptBridge.xml2
-rw-r--r--doc/classes/Joint2D.xml2
-rw-r--r--doc/classes/Label.xml2
-rw-r--r--doc/classes/Line2D.xml2
-rw-r--r--doc/classes/LinkButton.xml2
-rw-r--r--doc/classes/MarginContainer.xml8
-rw-r--r--doc/classes/MenuButton.xml2
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData3D.xml6
-rw-r--r--doc/classes/NavigationPathQueryParameters2D.xml2
-rw-r--r--doc/classes/NavigationPathQueryParameters3D.xml2
-rw-r--r--doc/classes/NavigationRegion2D.xml4
-rw-r--r--doc/classes/NavigationRegion3D.xml4
-rw-r--r--doc/classes/Node.xml4
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/Object.xml4
-rw-r--r--doc/classes/PackedByteArray.xml4
-rw-r--r--doc/classes/PackedScene.xml4
-rw-r--r--doc/classes/PacketPeer.xml2
-rw-r--r--doc/classes/ParticleProcessMaterial.xml6
-rw-r--r--doc/classes/PhysicsServer2D.xml16
-rw-r--r--doc/classes/PhysicsServer3D.xml7
-rw-r--r--doc/classes/Plane.xml4
-rw-r--r--doc/classes/Polygon2D.xml12
-rw-r--r--doc/classes/ProjectSettings.xml5
-rw-r--r--doc/classes/Quaternion.xml2
-rw-r--r--doc/classes/RichTextEffect.xml4
-rw-r--r--doc/classes/RichTextLabel.xml58
-rw-r--r--doc/classes/RigidBody3D.xml2
-rw-r--r--doc/classes/Slider.xml8
-rw-r--r--doc/classes/SpinBox.xml6
-rw-r--r--doc/classes/TextEdit.xml2
-rw-r--r--doc/classes/TextServer.xml7
-rw-r--r--doc/classes/TextureProgressBar.xml6
-rw-r--r--doc/classes/TileSet.xml2
-rw-r--r--doc/classes/TileSetAtlasSource.xml2
-rw-r--r--doc/classes/Time.xml4
-rw-r--r--doc/classes/Transform2D.xml2
-rw-r--r--doc/classes/Transform3D.xml2
-rw-r--r--doc/classes/Tree.xml2
-rw-r--r--doc/classes/Variant.xml2
-rw-r--r--doc/classes/Vector2.xml2
-rw-r--r--doc/classes/Vector3.xml2
-rw-r--r--doc/classes/Vector4.xml2
-rw-r--r--doc/classes/VisualShaderNode.xml16
-rw-r--r--doc/classes/VisualShaderNodeBooleanConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeFloatConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeIntConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeSwitch.xml2
-rw-r--r--doc/classes/WorldBoundaryShape2D.xml2
-rw-r--r--doc/classes/float.xml2
-rwxr-xr-xdoc/tools/make_rst.py410
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp1
-rw-r--r--editor/connections_dialog.cpp85
-rw-r--r--editor/connections_dialog.h2
-rw-r--r--editor/create_dialog.cpp5
-rw-r--r--editor/editor_build_profile.cpp29
-rw-r--r--editor/editor_feature_profile.cpp25
-rw-r--r--editor/editor_help.cpp295
-rw-r--r--editor/editor_help.h31
-rw-r--r--editor/editor_inspector.cpp182
-rw-r--r--editor/editor_inspector.h8
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/editor_properties.cpp136
-rw-r--r--editor/editor_properties.h20
-rw-r--r--editor/editor_properties_array_dict.cpp3
-rw-r--r--editor/icons/CodeRegionFoldDownArrow.svg2
-rw-r--r--editor/icons/CodeRegionFoldedRightArrow.svg2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp245
-rw-r--r--editor/plugins/animation_player_editor_plugin.h27
-rw-r--r--editor/property_selector.cpp43
-rw-r--r--main/main.cpp11
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs42
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs28
-rw-r--r--modules/mono/editor/bindings_generator.cpp20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Compat.cs102
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp72
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h28
-rw-r--r--modules/openxr/openxr_interface.cpp48
-rw-r--r--modules/openxr/openxr_interface.h4
-rw-r--r--modules/openxr/register_types.cpp12
-rw-r--r--modules/openxr/scene/openxr_hand.cpp15
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp100
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h5
-rw-r--r--platform/linuxbsd/platform_gl.h4
-rw-r--r--platform/linuxbsd/x11/SCsub4
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp80
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.cpp63
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.h61
-rw-r--r--platform/macos/display_server_macos.h17
-rw-r--r--platform/macos/display_server_macos.mm551
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml12
-rw-r--r--platform/macos/godot_menu_delegate.mm28
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/web/js/libs/library_godot_javascript_singleton.js2
-rw-r--r--platform/windows/display_server_windows.cpp44
-rw-r--r--scene/2d/physics_body_2d.cpp7
-rw-r--r--scene/3d/physics_body_3d.cpp14
-rw-r--r--scene/gui/file_dialog.cpp3
-rw-r--r--scene/gui/file_dialog.h2
-rw-r--r--scene/gui/menu_bar.cpp228
-rw-r--r--scene/gui/menu_bar.h24
-rw-r--r--scene/gui/popup_menu.cpp461
-rw-r--r--scene/gui/popup_menu.h9
-rw-r--r--scene/theme/default_theme.cpp4
-rw-r--r--scene/theme/icons/folder_down_arrow.svg1
-rw-r--r--scene/theme/icons/folder_right_arrow.svg1
-rw-r--r--scene/theme/icons/region_folded.svg1
-rw-r--r--scene/theme/icons/region_unfolded.svg1
-rw-r--r--servers/display_server.cpp21
-rw-r--r--servers/display_server.h8
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp5
-rw-r--r--servers/rendering/renderer_viewport.cpp17
-rw-r--r--servers/rendering/renderer_viewport.h2
-rw-r--r--servers/rendering/rendering_server_default.cpp4
149 files changed, 2872 insertions, 1271 deletions
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index e84ed82241..c854d9b54e 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -120,7 +120,7 @@
<param index="0" name="x" type="float" />
<description>
Returns the arc tangent of [param x] in radians. Use it to get the angle from an angle's tangent in trigonometry.
- The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code]x[/code].
+ The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code skip-lint]x[/code].
[codeblock]
var a = atan(0.5) # a is 0.463648
[/codeblock]
@@ -338,7 +338,7 @@
<param index="7" name="post_t" type="float" />
<description>
Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with [param pre] and [param post] values. See also [method lerp_angle].
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="cubic_interpolate_in_time">
@@ -879,10 +879,10 @@
URL tags only support URLs wrapped by a URL tag, not URLs with a different title.
When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Support for ANSI escape codes varies across terminal emulators, especially for italic and strikethrough. In standard output, [code]code[/code] is represented with faint text but without any font change. Unsupported tags are left as-is in standard output.
[codeblocks]
- [gdscript]
+ [gdscript skip-lint]
print_rich("[color=green][b]Hello world![/b][/color]") # Prints out "Hello world!" in green with a bold font
[/gdscript]
- [csharp]
+ [csharp skip-lint]
GD.PrintRich("[color=green][b]Hello world![/b][/color]"); // Prints out "Hello world!" in green with a bold font
[/csharp]
[/codeblocks]
@@ -1392,7 +1392,7 @@
<param index="1" name="type" type="int" />
<description>
Converts the given [param variant] to the given [param type], using the [enum Variant.Type] values. This method is generous with how it handles types, it can automatically convert between array types, convert numeric [String]s to [int], and converting most things to [String].
- If the type conversion cannot be done, this method will return the default value for that type, for example converting [Rect2] to [Vector2] will always return [code]Vector2.ZERO[/code]. This method will never show error messages as long as [param type] is a valid Variant type.
+ If the type conversion cannot be done, this method will return the default value for that type, for example converting [Rect2] to [Vector2] will always return [constant Vector2.ZERO]. This method will never show error messages as long as [param type] is a valid Variant type.
The returned value is a [Variant], but the data inside and the [enum Variant.Type] will be the same as the requested type.
[codeblock]
type_convert("Hi!", TYPE_INT) # Returns 0
@@ -1521,7 +1521,7 @@
angle = wrapf(angle + 0.1, -PI, PI)
[/codeblock]
[b]Note:[/b] If [param min] is [code]0[/code], this is equivalent to [method fposmod], so prefer using that instead.
- [code]wrapf[/code] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value.
+ [method wrapf] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value.
</description>
</method>
<method name="wrapi">
diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml
index d35835d73d..e2afeef377 100644
--- a/doc/classes/AStar3D.xml
+++ b/doc/classes/AStar3D.xml
@@ -6,7 +6,7 @@
<description>
A* (A star) is a computer algorithm used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in 3D space and Euclidean distances by default.
You must add points manually with [method add_point] and create segments manually with [method connect_points]. Once done, you can test if there is a path between two points with the [method are_points_connected] function, get a path containing indices by [method get_id_path], or one containing actual coordinates with [method get_point_path].
- It is also possible to use non-Euclidean distances. To do so, create a class that extends [code]AStar3D[/code] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example.
+ It is also possible to use non-Euclidean distances. To do so, create a class that extends [AStar3D] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example.
[codeblocks]
[gdscript]
class MyAStar:
@@ -33,7 +33,7 @@
}
[/csharp]
[/codeblocks]
- [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) &lt;= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information.
+ [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) &lt;= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [method _compute_cost] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information.
If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path.
</description>
<tutorials>
@@ -45,7 +45,7 @@
<param index="1" name="to_id" type="int" />
<description>
Called when computing the cost between two connected points.
- Note that this function is hidden in the default [code]AStar3D[/code] class.
+ Note that this function is hidden in the default [AStar3D] class.
</description>
</method>
<method name="_estimate_cost" qualifiers="virtual const">
@@ -54,7 +54,7 @@
<param index="1" name="to_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
- Note that this function is hidden in the default [code]AStar3D[/code] class.
+ Note that this function is hidden in the default [AStar3D] class.
</description>
</method>
<method name="add_point">
@@ -204,7 +204,7 @@
<method name="get_point_capacity" qualifiers="const">
<return type="int" />
<description>
- Returns the capacity of the structure backing the points, useful in conjunction with [code]reserve_space[/code].
+ Returns the capacity of the structure backing the points, useful in conjunction with [method reserve_space].
</description>
</method>
<method name="get_point_connections">
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index 276db35d83..277630588a 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -35,7 +35,7 @@
<param index="1" name="to_id" type="Vector2i" />
<description>
Called when computing the cost between two connected points.
- Note that this function is hidden in the default [code]AStarGrid2D[/code] class.
+ Note that this function is hidden in the default [AStarGrid2D] class.
</description>
</method>
<method name="_estimate_cost" qualifiers="virtual const">
@@ -44,7 +44,7 @@
<param index="1" name="to_id" type="Vector2i" />
<description>
Called when estimating the cost between a point and the path's ending point.
- Note that this function is hidden in the default [code]AStarGrid2D[/code] class.
+ Note that this function is hidden in the default [AStarGrid2D] class.
</description>
</method>
<method name="clear">
@@ -84,7 +84,7 @@
<param index="0" name="from_id" type="Vector2i" />
<param index="1" name="to_id" type="Vector2i" />
<description>
- Returns an array with the points that are in the path found by AStarGrid2D between the given points. The array is ordered from the starting point to the ending point of the path.
+ Returns an array with the points that are in the path found by [AStarGrid2D] between the given points. The array is ordered from the starting point to the ending point of the path.
[b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty [PackedVector3Array] and will print an error message.
</description>
</method>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index c48c7f4300..ac50a5ee30 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -79,7 +79,7 @@
The [AudioStream] object to be played.
</member>
<member name="stream_paused" type="bool" setter="set_stream_paused" getter="get_stream_paused" default="false">
- If [code]true[/code], the playback is paused. You can resume it by setting [code]stream_paused[/code] to [code]false[/code].
+ If [code]true[/code], the playback is paused. You can resume it by setting [member stream_paused] to [code]false[/code].
</member>
<member name="volume_db" type="float" setter="set_volume_db" getter="get_volume_db" default="0.0">
Volume of sound, in dB.
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index 7c59089708..77a038c5fa 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -85,7 +85,7 @@
The [AudioStream] object to be played.
</member>
<member name="stream_paused" type="bool" setter="set_stream_paused" getter="get_stream_paused" default="false">
- If [code]true[/code], the playback is paused. You can resume it by setting [code]stream_paused[/code] to [code]false[/code].
+ If [code]true[/code], the playback is paused. You can resume it by setting [member stream_paused] to [code]false[/code].
</member>
<member name="volume_db" type="float" setter="set_volume_db" getter="get_volume_db" default="0.0">
Base volume before attenuation.
diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml
index 2246e30817..72bfc2dcc7 100644
--- a/doc/classes/BaseMaterial3D.xml
+++ b/doc/classes/BaseMaterial3D.xml
@@ -184,11 +184,11 @@
</member>
<member name="distance_fade_max_distance" type="float" setter="set_distance_fade_max_distance" getter="get_distance_fade_max_distance" default="10.0">
Distance at which the object appears fully opaque.
- [b]Note:[/b] If [code]distance_fade_max_distance[/code] is less than [code]distance_fade_min_distance[/code], the behavior will be reversed. The object will start to fade away at [code]distance_fade_max_distance[/code] and will fully disappear once it reaches [code]distance_fade_min_distance[/code].
+ [b]Note:[/b] If [member distance_fade_max_distance] is less than [member distance_fade_min_distance], the behavior will be reversed. The object will start to fade away at [member distance_fade_max_distance] and will fully disappear once it reaches [member distance_fade_min_distance].
</member>
<member name="distance_fade_min_distance" type="float" setter="set_distance_fade_min_distance" getter="get_distance_fade_min_distance" default="0.0">
Distance at which the object starts to become visible. If the object is less than this distance away, it will be invisible.
- [b]Note:[/b] If [code]distance_fade_min_distance[/code] is greater than [code]distance_fade_max_distance[/code], the behavior will be reversed. The object will start to fade away at [code]distance_fade_max_distance[/code] and will fully disappear once it reaches [code]distance_fade_min_distance[/code].
+ [b]Note:[/b] If [member distance_fade_min_distance] is greater than [member distance_fade_max_distance], the behavior will be reversed. The object will start to fade away at [member distance_fade_max_distance] and will fully disappear once it reaches [member distance_fade_min_distance].
</member>
<member name="distance_fade_mode" type="int" setter="set_distance_fade" getter="get_distance_fade" enum="BaseMaterial3D.DistanceFadeMode" default="0">
Specifies which type of fade to use. Can be any of the [enum DistanceFadeMode]s.
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 972a8eb114..f98c207a6e 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -207,7 +207,7 @@
<constants>
<constant name="IDENTITY" value="Basis(1, 0, 0, 0, 1, 0, 0, 0, 1)">
The identity basis, with no rotation or scaling applied.
- This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
+ This is identical to creating [constructor Basis] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
</constant>
<constant name="FLIP_X" value="Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1)">
The basis that will flip something along the X axis when used in a transformation.
diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml
index fa8112de29..a62c160080 100644
--- a/doc/classes/Bone2D.xml
+++ b/doc/classes/Bone2D.xml
@@ -46,7 +46,7 @@
<method name="get_skeleton_rest" qualifiers="const">
<return type="Transform2D" />
<description>
- Returns the node's [member rest] [code]Transform2D[/code] if it doesn't have a parent, or its rest pose relative to its parent.
+ Returns the node's [member rest] [Transform2D] if it doesn't have a parent, or its rest pose relative to its parent.
</description>
</method>
<method name="set_autocalculate_length_and_angle">
diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml
index bee0cbcf4a..bf0534ab1e 100644
--- a/doc/classes/Button.xml
+++ b/doc/classes/Button.xml
@@ -140,7 +140,7 @@
[StyleBox] used when the [Button] is disabled (for right-to-left layouts).
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- [StyleBox] used when the [Button] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ [StyleBox] used when the [Button] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="hover" data_type="style" type="StyleBox">
[StyleBox] used when the [Button] is being hovered.
diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml
index 83f1ffa08e..07b23e0252 100644
--- a/doc/classes/Camera3D.xml
+++ b/doc/classes/Camera3D.xml
@@ -179,7 +179,7 @@
The distance to the far culling boundary for this camera relative to its local Z axis. Higher values allow the camera to see further away, while decreasing [member far] can improve performance if it results in objects being partially or fully culled.
</member>
<member name="fov" type="float" setter="set_fov" getter="get_fov" default="75.0">
- The camera's field of view angle (in degrees). Only applicable in perspective mode. Since [member keep_aspect] locks one axis, [code]fov[/code] sets the other axis' field of view angle.
+ The camera's field of view angle (in degrees). Only applicable in perspective mode. Since [member keep_aspect] locks one axis, [member fov] sets the other axis' field of view angle.
For reference, the default vertical field of view value ([code]75.0[/code]) is equivalent to a horizontal FOV of:
- ~91.31 degrees in a 4:3 viewport
- ~101.67 degrees in a 16:10 viewport
diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml
index 7b22fe2e93..b66a01a282 100644
--- a/doc/classes/CharacterBody2D.xml
+++ b/doc/classes/CharacterBody2D.xml
@@ -24,7 +24,7 @@
<return type="float" />
<param index="0" name="up_direction" type="Vector2" default="Vector2(0, -1)" />
<description>
- Returns the floor's collision angle at the last collision point according to [param up_direction], which is [code]Vector2.UP[/code] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
+ Returns the floor's collision angle at the last collision point according to [param up_direction], which is [constant Vector2.UP] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
</description>
</method>
<method name="get_floor_normal" qualifiers="const">
@@ -188,7 +188,7 @@
If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically.
</member>
<member name="up_direction" type="Vector2" setter="set_up_direction" getter="get_up_direction" default="Vector2(0, -1)">
- Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [code]Vector2.UP[/code]. As the vector will be normalized it can't be equal to [constant Vector2.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
+ Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [constant Vector2.UP]. As the vector will be normalized it can't be equal to [constant Vector2.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
</member>
<member name="velocity" type="Vector2" setter="set_velocity" getter="get_velocity" default="Vector2(0, 0)">
Current velocity vector in pixels per second, used and modified during calls to [method move_and_slide].
diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml
index b4f68cb1aa..2382c77a12 100644
--- a/doc/classes/CharacterBody3D.xml
+++ b/doc/classes/CharacterBody3D.xml
@@ -25,7 +25,7 @@
<return type="float" />
<param index="0" name="up_direction" type="Vector3" default="Vector3(0, 1, 0)" />
<description>
- Returns the floor's collision angle at the last collision point according to [param up_direction], which is [code]Vector3.UP[/code] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
+ Returns the floor's collision angle at the last collision point according to [param up_direction], which is [constant Vector3.UP] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
</description>
</method>
<method name="get_floor_normal" qualifiers="const">
@@ -179,7 +179,7 @@
If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically.
</member>
<member name="up_direction" type="Vector3" setter="set_up_direction" getter="get_up_direction" default="Vector3(0, 1, 0)">
- Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [code]Vector3.UP[/code]. As the vector will be normalized it can't be equal to [constant Vector3.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
+ Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [constant Vector3.UP]. As the vector will be normalized it can't be equal to [constant Vector3.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
</member>
<member name="velocity" type="Vector3" setter="set_velocity" getter="get_velocity" default="Vector3(0, 0, 0)">
Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide].
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index e5c255706f..ec0c2fbf06 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -544,7 +544,7 @@
Prefixes to trigger an automatic indent.
</member>
<member name="indent_size" type="int" setter="set_indent_size" getter="get_indent_size" default="4">
- Size of tabs, if [code]indent_use_spaces[/code] is enabled the number of spaces to use.
+ Size of the tabulation indent (one [kbd]Tab[/kbd] press) in characters. If [member indent_use_spaces] is enabled the number of spaces to use.
</member>
<member name="indent_use_spaces" type="bool" setter="set_indent_using_spaces" getter="is_indent_using_spaces" default="false">
Use spaces instead of tabs for indentation.
diff --git a/doc/classes/CollisionObject2D.xml b/doc/classes/CollisionObject2D.xml
index 07bc53575c..137e712913 100644
--- a/doc/classes/CollisionObject2D.xml
+++ b/doc/classes/CollisionObject2D.xml
@@ -16,7 +16,7 @@
<param index="1" name="event" type="InputEvent" />
<param index="2" name="shape_idx" type="int" />
<description>
- Accepts unhandled [InputEvent]s. [param shape_idx] is the child index of the clicked [Shape2D]. Connect to the [code]input_event[/code] signal to easily pick up these events.
+ Accepts unhandled [InputEvent]s. [param shape_idx] is the child index of the clicked [Shape2D]. Connect to [signal input_event] to easily pick up these events.
[b]Note:[/b] [method _input_event] requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set.
</description>
</method>
diff --git a/doc/classes/CollisionPolygon3D.xml b/doc/classes/CollisionPolygon3D.xml
index 0a3a3cfd26..ed6fa0ba3c 100644
--- a/doc/classes/CollisionPolygon3D.xml
+++ b/doc/classes/CollisionPolygon3D.xml
@@ -21,7 +21,7 @@
</member>
<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()">
Array of vertices which define the 2D polygon in the local XY plane.
- [b]Note:[/b] The returned value is a copy of the original. Methods which mutate the size or properties of the return value will not impact the original polygon. To change properties of the polygon, assign it to a temporary variable and make changes before reassigning the [code]polygon[/code] member.
+ [b]Note:[/b] The returned value is a copy of the original. Methods which mutate the size or properties of the return value will not impact the original polygon. To change properties of the polygon, assign it to a temporary variable and make changes before reassigning the class property.
</member>
</members>
</class>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index d665084469..21d6cb2756 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1052,7 +1052,7 @@
[b]Note:[/b] [Window] styles will have no effect unless the window is embedded.
</member>
<member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&amp;&quot;&quot;">
- The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance).
+ The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code skip-lint]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance).
When set, this property gives the highest priority to the type of the specified name. This type can in turn extend another type, forming a dependency chain. See [method Theme.set_type_variation]. If the theme item cannot be found using this type or its base types, lookup falls back on the class names.
[b]Note:[/b] To look up [Control]'s own items use various [code]get_theme_*[/code] methods without specifying [code]theme_type[/code].
[b]Note:[/b] Theme items are looked for in the tree order, from branch to root, where each [Control] node is checked for its [member theme] property. The earliest match against any type/class name is returned. The project-level Theme and the default Theme are checked last.
diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml
index 06b6409e09..9157649af2 100644
--- a/doc/classes/Curve3D.xml
+++ b/doc/classes/Curve3D.xml
@@ -117,8 +117,7 @@
<param index="0" name="offset" type="float" default="0.0" />
<param index="1" name="cubic" type="bool" default="false" />
<description>
- Returns a point within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve.
- To do that, it finds the two cached points where the [param offset] lies between, then interpolates the values. This interpolation is cubic if [param cubic] is set to [code]true[/code], or linear if set to [code]false[/code].
+ Returns a point within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve. To do that, it finds the two cached points where the [param offset] lies between, then interpolates the values. This interpolation is cubic if [param cubic] is set to [code]true[/code], or linear if set to [code]false[/code].
Cubic interpolation tends to follow the curves better, but linear is faster (and often, precise enough).
</description>
</method>
@@ -127,8 +126,7 @@
<param index="0" name="offset" type="float" />
<param index="1" name="apply_tilt" type="bool" default="false" />
<description>
- Returns an up vector within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve.
- To do that, it finds the two cached up vectors where the [param offset] lies between, then interpolates the values. If [param apply_tilt] is [code]true[/code], an interpolated tilt is applied to the interpolated up vector.
+ Returns an up vector within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve. To do that, it finds the two cached up vectors where the [param offset] lies between, then interpolates the values. If [param apply_tilt] is [code]true[/code], an interpolated tilt is applied to the interpolated up vector.
If the curve has no up vectors, the function sends an error to the console, and returns [code](0, 1, 0)[/code].
</description>
</method>
@@ -138,7 +136,7 @@
<param index="1" name="cubic" type="bool" default="false" />
<param index="2" name="apply_tilt" type="bool" default="false" />
<description>
- Similar with [code]interpolate_baked()[/code]. The return value is [code]Transform3D[/code], with [code]origin[/code] as point position, [code]basis.x[/code] as sideway vector, [code]basis.y[/code] as up vector, [code]basis.z[/code] as forward vector. When the curve length is 0, there is no reasonable way to calculate the rotation, all vectors aligned with global space axes.
+ Returns a [Transform3D] with [code]origin[/code] as point position, [code]basis.x[/code] as sideway vector, [code]basis.y[/code] as up vector, [code]basis.z[/code] as forward vector. When the curve length is 0, there is no reasonable way to calculate the rotation, all vectors aligned with global space axes. See also [method sample_baked].
</description>
</method>
<method name="samplef" qualifiers="const">
diff --git a/doc/classes/DampedSpringJoint2D.xml b/doc/classes/DampedSpringJoint2D.xml
index c47a7d9303..4bb80c84e5 100644
--- a/doc/classes/DampedSpringJoint2D.xml
+++ b/doc/classes/DampedSpringJoint2D.xml
@@ -10,7 +10,7 @@
</tutorials>
<members>
<member name="damping" type="float" setter="set_damping" getter="get_damping" default="1.0">
- The spring joint's damping ratio. A value between [code]0[/code] and [code]1[/code]. When the two bodies move into different directions the system tries to align them to the spring axis again. A high [code]damping[/code] value forces the attached bodies to align faster.
+ The spring joint's damping ratio. A value between [code]0[/code] and [code]1[/code]. When the two bodies move into different directions the system tries to align them to the spring axis again. A high [member damping] value forces the attached bodies to align faster.
</member>
<member name="length" type="float" setter="set_length" getter="get_length" default="50.0">
The spring joint's maximum length. The two attached bodies cannot stretch it past this value.
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 7bbfd8077a..3c7d66a67b 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -119,8 +119,8 @@
<param index="6" name="callback" type="Callable" />
<description>
Displays OS native dialog for selecting files or directories in the file system.
- Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code].
- [b]Note:[/b] This method is implemented if the display server has the [code]FEATURE_NATIVE_DIALOG[/code] feature.
+ Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths, int selected_filter_index[/code].
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature.
[b]Note:[/b] This method is implemented on Linux, Windows and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux, [param show_hidden] is ignored.
@@ -577,6 +577,16 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_is_item_hidden" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <description>
+ Returns [code]true[/code] if the item at index [param idx] is hidden.
+ See [method global_menu_set_item_hidden] for more info on how to hide an item.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_is_item_radio_checkable" qualifiers="const">
<return type="bool" />
<param index="0" name="menu_root" type="String" />
@@ -648,6 +658,27 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_item_hidden">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="hidden" type="bool" />
+ <description>
+ Hides/shows the item at index [param idx]. When it is hidden, an item does not appear in a menu and its action cannot be invoked.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
+ <method name="global_menu_set_item_hover_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="callback" type="Callable" />
+ <description>
+ Sets the callback of the item at index [param idx]. The callback is emitted when an item is hovered.
+ [b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_set_item_icon">
<return type="void" />
<param index="0" name="menu_root" type="String" />
@@ -751,6 +782,15 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_popup_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="open_callback" type="Callable" />
+ <param index="2" name="close_callback" type="Callable" />
+ <description>
+ Registers callables to emit when the menu is respectively about to show or closed.
+ </description>
+ </method>
<method name="has_feature" qualifiers="const">
<return type="bool" />
<param index="0" name="feature" type="int" enum="DisplayServer.Feature" />
@@ -1055,7 +1095,7 @@
Each [Dictionary] contains two [String] entries:
- [code]name[/code] is voice name.
- [code]id[/code] is voice identifier.
- - [code]language[/code] is language code in [code]lang_Variant[/code] format. [code]lang[/code] part is a 2 or 3-letter code based on the ISO-639 standard, in lowercase. And [code]Variant[/code] part is an engine dependent string describing country, region or/and dialect.
+ - [code]language[/code] is language code in [code]lang_Variant[/code] format. The [code]lang[/code] part is a 2 or 3-letter code based on the ISO-639 standard, in lowercase. The [code skip-lint]Variant[/code] part is an engine-dependent string describing country, region or/and dialect.
Note that Godot depends on system libraries for text-to-speech functionality. These libraries are installed by default on Windows and macOS, but not on all Linux distributions. If they are not present, this method will return an empty list. This applies to both Godot users on Linux, as well as end-users on Linux running Godot games that use text-to-speech.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index fde6307a13..853348f64c 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -228,8 +228,8 @@
<param index="0" name="path" type="String" />
<description>
Adds a dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project and embeds it into resulting binary.
- [b]Note:[/b] For static libraries (*.a) works in same way as [code]add_ios_framework[/code].
- This method should not be used for System libraries as they are already present on the device.
+ [b]Note:[/b] For static libraries (*.a) works in same way as [method add_ios_framework].
+ [b]Note:[/b] This method should not be used for System libraries as they are already present on the device.
</description>
</method>
<method name="add_ios_framework">
diff --git a/doc/classes/EditorFileDialog.xml b/doc/classes/EditorFileDialog.xml
index 59c0b7b912..b51341dc24 100644
--- a/doc/classes/EditorFileDialog.xml
+++ b/doc/classes/EditorFileDialog.xml
@@ -43,7 +43,7 @@
<method name="get_vbox">
<return type="VBoxContainer" />
<description>
- Returns the [code]VBoxContainer[/code] used to display the file system.
+ Returns the [VBoxContainer] used to display the file system.
[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property.
</description>
</method>
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index d1361c4f0d..82ba956151 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -234,7 +234,7 @@
<param index="2" name="custom_importer" type="String" default="&quot;&quot;" />
<param index="3" name="generator_parameters" type="Variant" default="null" />
<description>
- This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data.
+ This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code skip-lint]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data.
</description>
</method>
</methods>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index f8a73141a9..d0de09e451 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -339,7 +339,7 @@
<return type="void" />
<param index="0" name="name" type="String" />
<description>
- Sets the editor's current main screen to the one specified in [param name]. [param name] must match the text of the tab in question exactly ([code]2D[/code], [code]3D[/code], [code]Script[/code], [code]AssetLib[/code]).
+ Sets the editor's current main screen to the one specified in [param name]. [param name] must match the title of the tab in question exactly (e.g. [code]2D[/code], [code]3D[/code], [code skip-lint]Script[/code], or [code]AssetLib[/code] for default tabs).
</description>
</method>
<method name="set_plugin_enabled">
diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml
index 5ac9840f81..1680400384 100644
--- a/doc/classes/EditorNode3DGizmo.xml
+++ b/doc/classes/EditorNode3DGizmo.xml
@@ -95,7 +95,7 @@
<param index="0" name="camera" type="Camera3D" />
<param index="1" name="frustum" type="Plane[]" />
<description>
- Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and a [param frustum], this method should return which subgizmos are contained within the frustum. The [param frustum] argument consists of an array with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
+ Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and a [param frustum], this method should return which subgizmos are contained within the frustum. The [param frustum] argument consists of an array with all the [Plane]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
</description>
</method>
<method name="_subgizmos_intersect_ray" qualifiers="virtual const">
diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml
index 3e90c54647..da7ee17335 100644
--- a/doc/classes/EditorNode3DGizmoPlugin.xml
+++ b/doc/classes/EditorNode3DGizmoPlugin.xml
@@ -147,7 +147,7 @@
<param index="1" name="camera" type="Camera3D" />
<param index="2" name="frustum_planes" type="Plane[]" />
<description>
- Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and [param frustum_planes], this method should return which subgizmos are contained within the frustums. The [param frustum_planes] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos.
+ Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and [param frustum_planes], this method should return which subgizmos are contained within the frustums. The [param frustum_planes] argument consists of an array with all the [Plane]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos.
</description>
</method>
<method name="_subgizmos_intersect_ray" qualifiers="virtual const">
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index 3b6263b67b..acd959d7f7 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -36,20 +36,20 @@
</methods>
<members>
<member name="adjustment_brightness" type="float" setter="set_adjustment_brightness" getter="get_adjustment_brightness" default="1.0">
- The global brightness value of the rendered scene. Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global brightness value of the rendered scene. Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_color_correction" type="Texture" setter="set_adjustment_color_correction" getter="get_adjustment_color_correction">
- The [Texture2D] or [Texture3D] lookup table (LUT) to use for the built-in post-process color grading. Can use a [GradientTexture1D] for a 1-dimensional LUT, or a [Texture3D] for a more complex LUT. Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The [Texture2D] or [Texture3D] lookup table (LUT) to use for the built-in post-process color grading. Can use a [GradientTexture1D] for a 1-dimensional LUT, or a [Texture3D] for a more complex LUT. Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_contrast" type="float" setter="set_adjustment_contrast" getter="get_adjustment_contrast" default="1.0">
- The global contrast value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global contrast value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_enabled" type="bool" setter="set_adjustment_enabled" getter="is_adjustment_enabled" default="false">
If [code]true[/code], enables the [code]adjustment_*[/code] properties provided by this resource. If [code]false[/code], modifications to the [code]adjustment_*[/code] properties will have no effect on the rendered scene.
[b]Note:[/b] Adjustments are only supported in the Forward+ and Mobile rendering methods, not Compatibility.
</member>
<member name="adjustment_saturation" type="float" setter="set_adjustment_saturation" getter="get_adjustment_saturation" default="1.0">
- The global color saturation value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global color saturation value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="ambient_light_color" type="Color" setter="set_ambient_light_color" getter="get_ambient_light_color" default="Color(0, 0, 0, 1)">
The ambient light's [Color]. Only effective if [member ambient_light_sky_contribution] is lower than [code]1.0[/code] (exclusive).
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index e50e610e71..42528dc287 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -341,7 +341,7 @@
<param index="0" name="file" type="String" />
<param index="1" name="hidden" type="bool" />
<description>
- Sets file [code]hidden[/code] attribute.
+ Sets file [b]hidden[/b] attribute.
[b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows.
</description>
</method>
@@ -350,7 +350,7 @@
<param index="0" name="file" type="String" />
<param index="1" name="ro" type="bool" />
<description>
- Sets file [code]read only[/code] attribute.
+ Sets file [b]read only[/b] attribute.
[b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows.
</description>
</method>
diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml
index 1628f8819f..e6557343d3 100644
--- a/doc/classes/FontFile.xml
+++ b/doc/classes/FontFile.xml
@@ -255,7 +255,7 @@
<return type="Vector2i[]" />
<param index="0" name="cache_index" type="int" />
<description>
- Returns list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size.
+ Returns list of the font sizes in the cache. Each size is [Vector2i] with font size and outline size.
</description>
</method>
<method name="get_texture_count" qualifiers="const">
diff --git a/doc/classes/FontVariation.xml b/doc/classes/FontVariation.xml
index 128f3b5a85..e21881fc3e 100644
--- a/doc/classes/FontVariation.xml
+++ b/doc/classes/FontVariation.xml
@@ -70,7 +70,7 @@
</member>
<member name="variation_opentype" type="Dictionary" setter="set_variation_opentype" getter="get_variation_opentype" default="{}">
Font OpenType variation coordinates. More info: [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]OpenType variation tags[/url].
- [b]Note:[/b] This [Dictionary] uses OpenType tags as keys. Variation axes can be identified both by tags([code]int[/code]) and names ([code]string[/code]). Some axes might be accessible by multiple names. For example, [code]wght[/code] refers to the same axis as [code]weight[/code]. Tags on the other hand are unique. To convert between names and tags, use [method TextServer.name_to_tag] and [method TextServer.tag_to_name].
+ [b]Note:[/b] This [Dictionary] uses OpenType tags as keys. Variation axes can be identified both by tags ([int], e.g. [code]0x77678674[/code]) and names ([String], e.g. [code]wght[/code]). Some axes might be accessible by multiple names. For example, [code]wght[/code] refers to the same axis as [code]weight[/code]. Tags on the other hand are unique. To convert between names and tags, use [method TextServer.name_to_tag] and [method TextServer.tag_to_name].
[b]Note:[/b] To get available variation axes of a font, use [method Font.get_supported_variation_list].
</member>
<member name="variation_transform" type="Transform2D" setter="set_variation_transform" getter="get_variation_transform" default="Transform2D(1, 0, 0, 1, 0, 0)">
diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml
index e42aca1ada..3d7243fcbd 100644
--- a/doc/classes/GPUParticles3D.xml
+++ b/doc/classes/GPUParticles3D.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
3D particle node used to create a variety of particle systems and effects. [GPUParticles3D] features an emitter that generates some number of particles at a given rate.
- Use the [code]process_material[/code] property to add a [ParticleProcessMaterial] to configure particle appearance and behavior. Alternatively, you can add a [ShaderMaterial] which will be applied to all particles.
+ Use [member process_material] to add a [ParticleProcessMaterial] to configure particle appearance and behavior. Alternatively, you can add a [ShaderMaterial] which will be applied to all particles.
</description>
<tutorials>
<link title="Particle systems (3D)">$DOCS_URL/tutorials/3d/particles/index.html</link>
@@ -108,7 +108,7 @@
If [code]true[/code], particles use the parent node's coordinate space (known as local coordinates). This will cause particles to move and rotate along the [GPUParticles3D] node (and its parents) when it is moved or rotated. If [code]false[/code], particles use global coordinates; they will not move or rotate along the [GPUParticles3D] node (and its parents) when it is moved or rotated.
</member>
<member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot" default="false">
- If [code]true[/code], only [code]amount[/code] particles will be emitted.
+ If [code]true[/code], only the number of particles equal to [member amount] will be emitted.
</member>
<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0">
Amount of time to preprocess the particles before animation starts. Lets you start the animation some time after particles have started emitting.
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 961e9f216f..03c1ce2e00 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -235,9 +235,9 @@
<members>
<member name="accept_gzip" type="bool" setter="set_accept_gzip" getter="is_accepting_gzip" default="true">
If [code]true[/code], this header will be added to each request: [code]Accept-Encoding: gzip, deflate[/code] telling servers that it's okay to compress response bodies.
- Any Response body declaring a [code]Content-Encoding[/code] of either [code]gzip[/code] or [code]deflate[/code] will then be automatically decompressed, and the uncompressed bytes will be delivered via [code]request_completed[/code].
- If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regardless of [code]accept_gzip[/code].
- If [code]false[/code] no header will be added, and no decompression will be performed on response bodies. The raw bytes of the response body will be returned via [code]request_completed[/code].
+ Any Response body declaring a [code]Content-Encoding[/code] of either [code]gzip[/code] or [code]deflate[/code] will then be automatically decompressed, and the uncompressed bytes will be delivered via [signal request_completed].
+ If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regardless of [member accept_gzip].
+ If [code]false[/code] no header will be added, and no decompression will be performed on response bodies. The raw bytes of the response body will be returned via [signal request_completed].
</member>
<member name="body_size_limit" type="int" setter="set_body_size_limit" getter="get_body_size_limit" default="-1">
Maximum allowed size for response bodies. If the response body is compressed, this will be used as the maximum allowed size for the decompressed body.
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index fabe6fe4bd..310dae6c65 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Native image datatype. Contains image data which can be converted to an [ImageTexture] and provides commonly used [i]image processing[/i] methods. The maximum width and height for an [Image] are [constant MAX_WIDTH] and [constant MAX_HEIGHT].
- An [Image] cannot be assigned to a [code]texture[/code] property of an object directly (such as [Sprite2D]), and has to be converted manually to an [ImageTexture] first.
+ An [Image] cannot be assigned to a texture property of an object directly (such as [member Sprite2D.texture]), and has to be converted manually to an [ImageTexture] first.
[b]Note:[/b] The maximum image size is 16384×16384 pixels due to graphics hardware limitations. Larger images may fail to import.
</description>
<tutorials>
@@ -233,7 +233,7 @@
<return type="int" />
<param index="0" name="mipmap" type="int" />
<description>
- Returns the offset where the image's mipmap with index [param mipmap] is stored in the [code]data[/code] dictionary.
+ Returns the offset where the image's mipmap with index [param mipmap] is stored in the [member data] dictionary.
</description>
</method>
<method name="get_pixel" qualifiers="const">
diff --git a/doc/classes/ImageTextureLayered.xml b/doc/classes/ImageTextureLayered.xml
index 545c9289d1..e8d054bdd7 100644
--- a/doc/classes/ImageTextureLayered.xml
+++ b/doc/classes/ImageTextureLayered.xml
@@ -23,7 +23,7 @@
<param index="1" name="layer" type="int" />
<description>
Replaces the existing [Image] data at the given [param layer] with this new image.
- The given [Image] must have the same width, height, image format and mipmapping setting (a [code]bool[/code] value) as the rest of the referenced images.
+ The given [Image] must have the same width, height, image format, and mipmapping flag as the rest of the referenced images.
If the image format is unsupported, it will be decompressed and converted to a similar and supported [enum Image.Format].
The update is immediate: it's synchronized with drawing.
</description>
diff --git a/doc/classes/JavaScriptBridge.xml b/doc/classes/JavaScriptBridge.xml
index 6ce5801864..faf5424c47 100644
--- a/doc/classes/JavaScriptBridge.xml
+++ b/doc/classes/JavaScriptBridge.xml
@@ -42,7 +42,7 @@
<param index="0" name="code" type="String" />
<param index="1" name="use_global_execution_context" type="bool" default="false" />
<description>
- Execute the string [param code] as JavaScript code within the browser window. This is a call to the actual global JavaScript function [code]eval()[/code].
+ Execute the string [param code] as JavaScript code within the browser window. This is a call to the actual global JavaScript function [code skip-lint]eval()[/code].
If [param use_global_execution_context] is [code]true[/code], the code will be evaluated in the global execution context. Otherwise, it is evaluated in the execution context of a function within the engine's runtime environment.
</description>
</method>
diff --git a/doc/classes/Joint2D.xml b/doc/classes/Joint2D.xml
index 9fb9154827..af0a54815f 100644
--- a/doc/classes/Joint2D.xml
+++ b/doc/classes/Joint2D.xml
@@ -18,7 +18,7 @@
</methods>
<members>
<member name="bias" type="float" setter="set_bias" getter="get_bias" default="0.0">
- When [member node_a] and [member node_b] move in different directions the [code]bias[/code] controls how fast the joint pulls them back to their original position. The lower the [code]bias[/code] the more the two bodies can pull on the joint.
+ When [member node_a] and [member node_b] move in different directions the [member bias] controls how fast the joint pulls them back to their original position. The lower the [member bias] the more the two bodies can pull on the joint.
When set to [code]0[/code], the default value from [member ProjectSettings.physics/2d/solver/default_constraint_bias] is used.
</member>
<member name="disable_collision" type="bool" setter="set_exclude_nodes_from_collision" getter="get_exclude_nodes_from_collision" default="true">
diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml
index d0aa89b2e7..f13f1bdcf4 100644
--- a/doc/classes/Label.xml
+++ b/doc/classes/Label.xml
@@ -58,7 +58,7 @@
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
<member name="lines_skipped" type="int" setter="set_lines_skipped" getter="get_lines_skipped" default="0">
- The node ignores the first [code]lines_skipped[/code] lines before it starts to display text.
+ The number of the lines ignored and not displayed from the start of the [member text] value.
</member>
<member name="max_lines_visible" type="int" setter="set_max_lines_visible" getter="get_max_lines_visible" default="-1">
Limits the lines of text the node shows on screen.
diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml
index 4c444721f4..283d847a93 100644
--- a/doc/classes/Line2D.xml
+++ b/doc/classes/Line2D.xml
@@ -92,7 +92,7 @@
Determines the miter limit of the polyline. Normally, when [member joint_mode] is set to [constant LINE_JOINT_SHARP], sharp angles fall back to using the logic of [constant LINE_JOINT_BEVEL] joints to prevent very long miters. Higher values of this property mean that the fallback to a bevel joint will happen at sharper angles.
</member>
<member name="texture" type="Texture2D" setter="set_texture" getter="get_texture">
- The texture used for the polyline. Uses [code]texture_mode[/code] for drawing style.
+ The texture used for the polyline. Uses [member texture_mode] for drawing style.
</member>
<member name="texture_mode" type="int" setter="set_texture_mode" getter="get_texture_mode" enum="Line2D.LineTextureMode" default="0">
The style to render the [member texture] of the polyline. Use [enum LineTextureMode] constants.
diff --git a/doc/classes/LinkButton.xml b/doc/classes/LinkButton.xml
index 4f38b4d336..aa81b6fde0 100644
--- a/doc/classes/LinkButton.xml
+++ b/doc/classes/LinkButton.xml
@@ -94,7 +94,7 @@
Font size of the [LinkButton]'s text.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- [StyleBox] used when the [LinkButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ [StyleBox] used when the [LinkButton] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/MarginContainer.xml b/doc/classes/MarginContainer.xml
index 1a6621d354..b83ee7ba00 100644
--- a/doc/classes/MarginContainer.xml
+++ b/doc/classes/MarginContainer.xml
@@ -30,16 +30,16 @@
</tutorials>
<theme_items>
<theme_item name="margin_bottom" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a bottom margin of [code]margin_bottom[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the bottom.
</theme_item>
<theme_item name="margin_left" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a left margin of [code]margin_left[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the left.
</theme_item>
<theme_item name="margin_right" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a right margin of [code]margin_right[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the right.
</theme_item>
<theme_item name="margin_top" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a top margin of [code]margin_top[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the top.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml
index 6aa17c1e16..16b3772fa9 100644
--- a/doc/classes/MenuButton.xml
+++ b/doc/classes/MenuButton.xml
@@ -39,7 +39,7 @@
The number of items currently in the list.
</member>
<member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="false">
- If [code]true[/code], when the cursor hovers above another [MenuButton] within the same parent which also has [code]switch_on_hover[/code] enabled, it will close the current [MenuButton] and open the other one.
+ If [code]true[/code], when the cursor hovers above another [MenuButton] within the same parent which also has [member switch_on_hover] enabled, it will close the current [MenuButton] and open the other one.
</member>
<member name="toggle_mode" type="bool" setter="set_toggle_mode" getter="is_toggle_mode" overrides="BaseButton" default="true" />
</members>
diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml
index dc446dcaaa..dad3623416 100644
--- a/doc/classes/NavigationMeshSourceGeometryData3D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml
@@ -14,7 +14,7 @@
<param index="0" name="faces" type="PackedVector3Array" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds an array of vertex positions to the geometry data for navigation mesh baking to form triangulated faces. For each face the array must have three vertex positions in clockwise winding order. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds an array of vertex positions to the geometry data for navigation mesh baking to form triangulated faces. For each face the array must have three vertex positions in clockwise winding order. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="add_mesh">
@@ -22,7 +22,7 @@
<param index="0" name="mesh" type="Mesh" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds the geometry data of a [Mesh] resource to the navigation mesh baking data. The mesh must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds the geometry data of a [Mesh] resource to the navigation mesh baking data. The mesh must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="add_mesh_array">
@@ -30,7 +30,7 @@
<param index="0" name="mesh_array" type="Array" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="clear">
diff --git a/doc/classes/NavigationPathQueryParameters2D.xml b/doc/classes/NavigationPathQueryParameters2D.xml
index 4acb6b697f..74cf4bdb75 100644
--- a/doc/classes/NavigationPathQueryParameters2D.xml
+++ b/doc/classes/NavigationPathQueryParameters2D.xml
@@ -11,7 +11,7 @@
</tutorials>
<members>
<member name="map" type="RID" setter="set_map" getter="get_map" default="RID()">
- The navigation [code]map[/code] [RID] used in the path query.
+ The navigation map [RID] used in the path query.
</member>
<member name="metadata_flags" type="int" setter="set_metadata_flags" getter="get_metadata_flags" enum="NavigationPathQueryParameters2D.PathMetadataFlags" is_bitfield="true" default="7">
Additional information to include with the navigation path.
diff --git a/doc/classes/NavigationPathQueryParameters3D.xml b/doc/classes/NavigationPathQueryParameters3D.xml
index 204e915efd..2a366c0498 100644
--- a/doc/classes/NavigationPathQueryParameters3D.xml
+++ b/doc/classes/NavigationPathQueryParameters3D.xml
@@ -11,7 +11,7 @@
</tutorials>
<members>
<member name="map" type="RID" setter="set_map" getter="get_map" default="RID()">
- The navigation [code]map[/code] [RID] used in the path query.
+ The navigation map [RID] used in the path query.
</member>
<member name="metadata_flags" type="int" setter="set_metadata_flags" getter="get_metadata_flags" enum="NavigationPathQueryParameters3D.PathMetadataFlags" is_bitfield="true" default="7">
Additional information to include with the navigation path.
diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml
index e023d8dd7f..ef660305f4 100644
--- a/doc/classes/NavigationRegion2D.xml
+++ b/doc/classes/NavigationRegion2D.xml
@@ -85,7 +85,7 @@
Determines if the [NavigationRegion2D] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this region's navigation mesh from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this region's navigation mesh from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the region belongs to. These navigation layers can be checked upon when requesting a path with [method NavigationServer2D.map_get_path].
@@ -94,7 +94,7 @@
The [NavigationPolygon] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [member travel_cost] for determining the shortest path.
</member>
<member name="use_edge_connections" type="bool" setter="set_use_edge_connections" getter="get_use_edge_connections" default="true">
If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
diff --git a/doc/classes/NavigationRegion3D.xml b/doc/classes/NavigationRegion3D.xml
index 7e8ead2032..3257160485 100644
--- a/doc/classes/NavigationRegion3D.xml
+++ b/doc/classes/NavigationRegion3D.xml
@@ -63,7 +63,7 @@
Determines if the [NavigationRegion3D] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this region's navigation mesh from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this region's navigation mesh from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the region belongs to. These navigation layers can be checked upon when requesting a path with [method NavigationServer3D.map_get_path].
@@ -72,7 +72,7 @@
The [NavigationMesh] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [member travel_cost] for determining the shortest path.
</member>
<member name="use_edge_connections" type="bool" setter="set_use_edge_connections" getter="get_use_edge_connections" default="true">
If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 8da2f0b86b..7eff3fc950 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -93,7 +93,7 @@
Called when the node is "ready", i.e. when both the node and its children have entered the scene tree. If the node has children, their [method _ready] callbacks get triggered first, and the parent node will receive the ready notification afterwards.
Corresponds to the [constant NOTIFICATION_READY] notification in [method Object._notification]. See also the [code]@onready[/code] annotation for variables.
Usually used for initialization. For even earlier initialization, [method Object._init] may be used. See also [method _enter_tree].
- [b]Note:[/b] [method _ready] may be called only once for each node. After removing a node from the scene tree and adding it again, [code]_ready[/code] will not be called a second time. This can be bypassed by requesting another call with [method request_ready], which may be called anywhere before adding the node again.
+ [b]Note:[/b] [method _ready] may be called only once for each node. After removing a node from the scene tree and adding it again, [method _ready] will not be called a second time. This can be bypassed by requesting another call with [method request_ready], which may be called anywhere before adding the node again.
</description>
</method>
<method name="_shortcut_input" qualifiers="virtual">
@@ -696,7 +696,7 @@
<method name="request_ready">
<return type="void" />
<description>
- Requests that [code]_ready[/code] be called again. Note that the method won't be called immediately, but is scheduled for when the node is added to the scene tree again (see [method _ready]). [code]_ready[/code] is called only for the node which requested it, which means that you need to request ready for each child if you want them to call [code]_ready[/code] too (in which case, [code]_ready[/code] will be called in the same order as it would normally).
+ Requests that [method _ready] be called again. Note that the method won't be called immediately, but is scheduled for when the node is added to the scene tree again. [method _ready] is called only for the node which requested it, which means that you need to request ready for each child if you want them to call [method _ready] too (in which case, [method _ready] will be called in the same order as it would normally).
</description>
</method>
<method name="rpc" qualifiers="vararg">
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 7cfbca1178..ae2ca324e3 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -257,7 +257,7 @@
<description>
Returns the host OS locale as a string of the form [code]language_Script_COUNTRY_VARIANT@extra[/code]. If you want only the language code and not the fully specified locale from the OS, you can use [method get_locale_language].
[code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case.
- [code]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case.
+ [code skip-lint]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case.
[code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case.
[code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored keywords.
[code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information.
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index c530569786..bb7289cc99 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -487,7 +487,7 @@
}
[/csharp]
[/codeblocks]
- [b][code]Object.connect()[/code] or [code]Signal.connect()[/code]?[/b]
+ [b][code skip-lint]Object.connect()[/code] or [code skip-lint]Signal.connect()[/code]?[/b]
As seen above, the recommended method to connect signals is not [method Object.connect]. The code block below shows the four options for connecting signals, using either this legacy method or the recommended [method Signal.connect], and using either an implicit [Callable] or a manually defined one.
[codeblocks]
[gdscript]
@@ -718,7 +718,7 @@
<param index="0" name="signal" type="StringName" />
<description>
Returns an [Array] of connections for the given [param signal] name. Each connection is represented as a [Dictionary] that contains three entries:
- - [code]signal[/code] is a reference to the [Signal];
+ - [code skip-lint]signal[/code] is a reference to the [Signal];
- [code]callable[/code] is a reference to the connected [Callable];
- [code]flags[/code] is a combination of [enum ConnectFlags].
</description>
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index b706e2782f..516cc9f020 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -182,7 +182,7 @@
<param index="1" name="compression_mode" type="int" default="0" />
<description>
Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b]
- This method is potentially slower than [code]decompress[/code], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [code]decompress[/code] knows it's output buffer size from the beginning.
+ This method is potentially slower than [method decompress], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [method decompress] knows it's output buffer size from the beginning.
GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.
</description>
</method>
@@ -447,7 +447,7 @@
<method name="to_float32_array" qualifiers="const">
<return type="PackedFloat32Array" />
<description>
- Returns a copy of the data converted to a [PackedFloat32Array], where each block of 4 bytes has been converted to a 32-bit float (C++ [code]float[/code]).
+ Returns a copy of the data converted to a [PackedFloat32Array], where each block of 4 bytes has been converted to a 32-bit float (C++ [code skip-lint]float[/code]).
The size of the input array must be a multiple of 4 (size of 32-bit float). The size of the new array will be [code]byte_array.size() / 4[/code].
If the original data can't be converted to 32-bit floats, the resulting data is undefined.
</description>
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index f945ce30ad..a8cfc288f3 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -22,7 +22,7 @@
AddChild(scene);
[/csharp]
[/codeblocks]
- [b]Example of saving a node with different owners:[/b] The following example creates 3 objects: [Node2D] ([code]node[/code]), [RigidBody2D] ([code]body[/code]) and [CollisionObject2D] ([code]collision[/code]). [code]collision[/code] is a child of [code]body[/code] which is a child of [code]node[/code]. Only [code]body[/code] is owned by [code]node[/code] and [code]pack[/code] will therefore only save those two nodes, but not [code]collision[/code].
+ [b]Example of saving a node with different owners:[/b] The following example creates 3 objects: [Node2D] ([code]node[/code]), [RigidBody2D] ([code]body[/code]) and [CollisionObject2D] ([code]collision[/code]). [code]collision[/code] is a child of [code]body[/code] which is a child of [code]node[/code]. Only [code]body[/code] is owned by [code]node[/code] and [method pack] will therefore only save those two nodes, but not [code]collision[/code].
[codeblocks]
[gdscript]
# Create the objects.
@@ -85,7 +85,7 @@
<method name="get_state" qualifiers="const">
<return type="SceneState" />
<description>
- Returns the [code]SceneState[/code] representing the scene file contents.
+ Returns the [SceneState] representing the scene file contents.
</description>
</method>
<method name="instantiate" qualifiers="const">
diff --git a/doc/classes/PacketPeer.xml b/doc/classes/PacketPeer.xml
index 85917efe25..9a2a23c9b7 100644
--- a/doc/classes/PacketPeer.xml
+++ b/doc/classes/PacketPeer.xml
@@ -57,7 +57,7 @@
<members>
<member name="encode_buffer_max_size" type="int" setter="set_encode_buffer_max_size" getter="get_encode_buffer_max_size" default="8388608">
Maximum buffer size allowed when encoding [Variant]s. Raise this value to support heavier memory allocations.
- The [method put_var] method allocates memory on the stack, and the buffer used will grow automatically to the closest power of two to match the size of the [Variant]. If the [Variant] is bigger than [code]encode_buffer_max_size[/code], the method will error out with [constant ERR_OUT_OF_MEMORY].
+ The [method put_var] method allocates memory on the stack, and the buffer used will grow automatically to the closest power of two to match the size of the [Variant]. If the [Variant] is bigger than [member encode_buffer_max_size], the method will error out with [constant ERR_OUT_OF_MEMORY].
</member>
</members>
</class>
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 355fec3713..0f6cc3b29e 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -152,7 +152,7 @@
Unit vector specifying the particles' emission direction.
</member>
<member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents">
- The box's extents if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_BOX].
+ The box's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX].
</member>
<member name="emission_color_texture" type="Texture2D" setter="set_emission_color_texture" getter="get_emission_color_texture">
Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
@@ -162,7 +162,7 @@
Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
</member>
<member name="emission_point_count" type="int" setter="set_emission_point_count" getter="get_emission_point_count">
- The number of emission points if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
+ The number of emission points if [member emission_shape] is set to [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
</member>
<member name="emission_point_texture" type="Texture2D" setter="set_emission_point_texture" getter="get_emission_point_texture">
Particles will be emitted at positions determined by sampling this texture at a random position. Used with [constant EMISSION_SHAPE_POINTS] and [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
@@ -183,7 +183,7 @@
Particles will be emitted inside this region. Use [enum EmissionShape] constants for values.
</member>
<member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius">
- The sphere's radius if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_SPHERE].
+ The sphere's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_SPHERE].
</member>
<member name="flatness" type="float" setter="set_flatness" getter="get_flatness" default="0.0">
Amount of [member spread] along the Y axis.
diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml
index ae25af1dd9..8be92edbad 100644
--- a/doc/classes/PhysicsServer2D.xml
+++ b/doc/classes/PhysicsServer2D.xml
@@ -118,7 +118,7 @@
<return type="RID" />
<param index="0" name="area" type="RID" />
<description>
- Returns the [RID] of the space assigned to the area. Returns [code]RID()[/code] if no space is assigned.
+ Returns the [RID] of the space assigned to the area. Returns an empty [RID] if no space is assigned.
</description>
</method>
<method name="area_get_transform" qualifiers="const">
@@ -486,7 +486,7 @@
<return type="RID" />
<param index="0" name="body" type="RID" />
<description>
- Returns the [RID] of the space assigned to the body. Returns [code]RID()[/code] if no space is assigned.
+ Returns the [RID] of the space assigned to the body. Returns an empty [RID] if no space is assigned.
</description>
</method>
<method name="body_get_state" qualifiers="const">
@@ -827,7 +827,7 @@
<param index="2" name="body_a" type="RID" />
<param index="3" name="body_b" type="RID" default="RID()" />
<description>
- Makes the joint a pin joint. If [param body_b] is [code]RID()[/code], then [param body_a] is pinned to the point [param anchor] (given in global coordinates); otherwise, [param body_a] is pinned to [param body_b] at the point [param anchor] (given in global coordinates). To set the parameters which are specific to the pin joint, see [method pin_joint_set_param].
+ Makes the joint a pin joint. If [param body_b] is an empty [RID], then [param body_a] is pinned to the point [param anchor] (given in global coordinates); otherwise, [param body_a] is pinned to [param body_b] at the point [param anchor] (given in global coordinates). To set the parameters which are specific to the pin joint, see [method pin_joint_set_param].
</description>
</method>
<method name="joint_set_param">
@@ -918,13 +918,13 @@
<param index="1" name="data" type="Variant" />
<description>
Sets the shape data that defines the configuration of the shape. The [param data] to be passed depends on the shape's type (see [method shape_get_type]):
- - [constant SHAPE_WORLD_BOUNDARY]: an array of length two containing a [Vector2] [code]normal[/code] direction and a [code]float[/code] distance [code]d[/code],
- - [constant SHAPE_SEPARATION_RAY]: a dictionary containing the key [code]length[/code] with a [code]float[/code] value and the key [code]slide_on_slope[/code] with a [code]bool[/code] value,
+ - [constant SHAPE_WORLD_BOUNDARY]: an array of length two containing a [Vector2] [code]normal[/code] direction and a [float] distance [code]d[/code],
+ - [constant SHAPE_SEPARATION_RAY]: a dictionary containing the key [code]length[/code] with a [float] value and the key [code]slide_on_slope[/code] with a [bool] value,
- [constant SHAPE_SEGMENT]: a [Rect2] [code]rect[/code] containing the first point of the segment in [code]rect.position[/code] and the second point of the segment in [code]rect.size[/code],
- - [constant SHAPE_CIRCLE]: a [code]float[/code] [code]radius[/code],
+ - [constant SHAPE_CIRCLE]: a [float] [code]radius[/code],
- [constant SHAPE_RECTANGLE]: a [Vector2] [code]half_extents[/code],
- - [constant SHAPE_CAPSULE]: an array of length two (or a [Vector2]) containing a [code]float[/code] [code]height[/code] and a [code]float[/code] [code]radius[/code],
- - [constant SHAPE_CONVEX_POLYGON]: either a [PackedVector2Array] of points defining a convex polygon in counterclockwise order (the clockwise outward normal of each segment formed by consecutive points is calculated internally), or a [PackedFloat32Array] of length divisible by four so that every 4-tuple of [code]float[/code]s contains the coordinates of a point followed by the coordinates of the clockwise outward normal vector to the segment between the current point and the next point,
+ - [constant SHAPE_CAPSULE]: an array of length two (or a [Vector2]) containing a [float] [code]height[/code] and a [float] [code]radius[/code],
+ - [constant SHAPE_CONVEX_POLYGON]: either a [PackedVector2Array] of points defining a convex polygon in counterclockwise order (the clockwise outward normal of each segment formed by consecutive points is calculated internally), or a [PackedFloat32Array] of length divisible by four so that every 4-tuple of [float]s contains the coordinates of a point followed by the coordinates of the clockwise outward normal vector to the segment between the current point and the next point,
- [constant SHAPE_CONCAVE_POLYGON]: a [PackedVector2Array] of length divisible by two (each pair of points forms one segment).
[b]Warning:[/b] In the case of [constant SHAPE_CONVEX_POLYGON], this method does not check if the points supplied actually form a convex polygon (unlike the [member CollisionPolygon2D.polygon] property).
</description>
diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml
index f3441f45ed..32810b0daf 100644
--- a/doc/classes/PhysicsServer3D.xml
+++ b/doc/classes/PhysicsServer3D.xml
@@ -582,10 +582,9 @@
<param index="1" name="callable" type="Callable" />
<param index="2" name="userdata" type="Variant" default="null" />
<description>
- Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]).
- The force integration function takes 2 arguments:
- [code]state:[/code] [PhysicsDirectBodyState3D] used to retrieve and modify the body's state.
- [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code].
+ Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]). The force integration function takes 2 arguments:
+ - [code]state[/code] — [PhysicsDirectBodyState3D] used to retrieve and modify the body's state.
+ - [code skip-lint]userdata[/code] — optional user data passed to [method body_set_force_integration_callback].
</description>
</method>
<method name="body_set_max_contacts_reported">
diff --git a/doc/classes/Plane.xml b/doc/classes/Plane.xml
index 6faf83c0d6..a0da82d1d6 100644
--- a/doc/classes/Plane.xml
+++ b/doc/classes/Plane.xml
@@ -152,11 +152,11 @@
<members>
<member name="d" type="float" setter="" getter="" default="0.0">
The distance from the origin to the plane, expressed in terms of [member normal] (according to its direction and magnitude). Actual absolute distance from the origin to the plane can be calculated as [code]abs(d) / normal.length()[/code] (if [member normal] has zero length then this [Plane] does not represent a valid plane).
- In the scalar equation of the plane [code]ax + by + cz = d[/code], this is [code]d[/code], while the [code](a, b, c)[/code] coordinates are represented by the [member normal] property.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is [code skip-lint]d[/code], while the [code](a, b, c)[/code] coordinates are represented by the [member normal] property.
</member>
<member name="normal" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)">
The normal of the plane, typically a unit vector. Shouldn't be a zero vector as [Plane] with such [member normal] does not represent a valid plane.
- In the scalar equation of the plane [code]ax + by + cz = d[/code], this is the vector [code](a, b, c)[/code], where [code]d[/code] is the [member d] property.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is the vector [code](a, b, c)[/code], where [code skip-lint]d[/code] is the [member d] property.
</member>
<member name="x" type="float" setter="" getter="" default="0.0">
The X component of the plane's [member normal] vector.
diff --git a/doc/classes/Polygon2D.xml b/doc/classes/Polygon2D.xml
index 73c3c022b7..25adc57990 100644
--- a/doc/classes/Polygon2D.xml
+++ b/doc/classes/Polygon2D.xml
@@ -75,7 +75,7 @@
Internal list of [Bone2D] nodes used by the assigned [member skeleton]. Edited using the Polygon2D editor ("UV" button on the top toolbar).
</member>
<member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)">
- The polygon's fill color. If [code]texture[/code] is defined, it will be multiplied by this color. It will also be the default color for vertices not set in [code]vertex_colors[/code].
+ The polygon's fill color. If [member texture] is set, it will be multiplied by this color. It will also be the default color for vertices not set in [member vertex_colors].
</member>
<member name="internal_vertex_count" type="int" setter="set_internal_vertex_count" getter="get_internal_vertex_count" default="0">
Number of internal vertices, used for UV mapping.
@@ -100,22 +100,22 @@
Path to a [Skeleton2D] node used for skeleton-based deformations of this polygon. If empty or invalid, skeletal deformations will not be used.
</member>
<member name="texture" type="Texture2D" setter="set_texture" getter="get_texture">
- The polygon's fill texture. Use [code]uv[/code] to set texture coordinates.
+ The polygon's fill texture. Use [member uv] to set texture coordinates.
</member>
<member name="texture_offset" type="Vector2" setter="set_texture_offset" getter="get_texture_offset" default="Vector2(0, 0)">
- Amount to offset the polygon's [code]texture[/code]. If [code](0, 0)[/code] the texture's origin (its top-left corner) will be placed at the polygon's [code]position[/code].
+ Amount to offset the polygon's [member texture]. If set to [code]Vector2(0, 0)[/code], the texture's origin (its top-left corner) will be placed at the polygon's position.
</member>
<member name="texture_rotation" type="float" setter="set_texture_rotation" getter="get_texture_rotation" default="0.0">
The texture's rotation in radians.
</member>
<member name="texture_scale" type="Vector2" setter="set_texture_scale" getter="get_texture_scale" default="Vector2(1, 1)">
- Amount to multiply the [code]uv[/code] coordinates when using a [code]texture[/code]. Larger values make the texture smaller, and vice versa.
+ Amount to multiply the [member uv] coordinates when using [member texture]. Larger values make the texture smaller, and vice versa.
</member>
<member name="uv" type="PackedVector2Array" setter="set_uv" getter="get_uv" default="PackedVector2Array()">
- Texture coordinates for each vertex of the polygon. There should be one [code]uv[/code] per polygon vertex. If there are fewer, undefined vertices will use [code](0, 0)[/code].
+ Texture coordinates for each vertex of the polygon. There should be one UV value per polygon vertex. If there are fewer, undefined vertices will use [code]Vector2(0, 0)[/code].
</member>
<member name="vertex_colors" type="PackedColorArray" setter="set_vertex_colors" getter="get_vertex_colors" default="PackedColorArray()">
- Color for each vertex. Colors are interpolated between vertices, resulting in smooth gradients. There should be one per polygon vertex. If there are fewer, undefined vertices will use [code]color[/code].
+ Color for each vertex. Colors are interpolated between vertices, resulting in smooth gradients. There should be one per polygon vertex. If there are fewer, undefined vertices will use [member color].
</member>
</members>
</class>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 7e75cbc753..b49de068fd 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2728,11 +2728,14 @@
<member name="xr/openxr/extensions/eye_gaze_interaction" type="bool" setter="" getter="" default="false">
Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
</member>
+ <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="true">
+ If true we enable the hand tracking extension if available.
+ </member>
<member name="xr/openxr/form_factor" type="int" setter="" getter="" default="&quot;0&quot;">
Specify whether OpenXR should be configured for an HMD or a hand held device.
</member>
<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
- If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [code]xr/openxr/foveation_level[/code].
+ If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
</member>
<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default="&quot;0&quot;">
Applied foveation level if supported: 0 = off, 1 = low, 2 = medium, 3 = high.
diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml
index 0499d51d79..74dfae77fc 100644
--- a/doc/classes/Quaternion.xml
+++ b/doc/classes/Quaternion.xml
@@ -191,7 +191,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a spherical cubic interpolation between quaternions [param pre_a], this vector, [param b], and [param post_b], by the given amount [param weight].
- It can perform smoother interpolation than [code]spherical_cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method spherical_cubic_interpolate] by the time values.
</description>
</method>
</methods>
diff --git a/doc/classes/RichTextEffect.xml b/doc/classes/RichTextEffect.xml
index 0f429c31fa..ca95557f1b 100644
--- a/doc/classes/RichTextEffect.xml
+++ b/doc/classes/RichTextEffect.xml
@@ -7,11 +7,11 @@
A custom effect for a [RichTextLabel].
[b]Note:[/b] For a [RichTextEffect] to be usable, a BBCode tag must be defined as a member variable called [code]bbcode[/code] in the script.
[codeblocks]
- [gdscript]
+ [gdscript skip-lint]
# The RichTextEffect will be usable like this: `[example]Some text[/example]`
var bbcode = "example"
[/gdscript]
- [csharp]
+ [csharp skip-lint]
// The RichTextEffect will be usable like this: `[example]Some text[/example]`
string bbcode = "example";
[/csharp]
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 1e5b09516f..405d189c54 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -6,9 +6,9 @@
<description>
A control for displaying text that can contain custom fonts, images, and basic formatting. [RichTextLabel] manages these as an internal tag stack. It also adapts itself to given width/heights.
[b]Note:[/b] Assignments to [member text] clear the tag stack and reconstruct it from the property's contents. Any edits made to [member text] will erase previous edits made from other manual sources such as [method append_text] and the [code]push_*[/code] / [method pop] methods.
- [b]Note:[/b] RichTextLabel doesn't support entangled BBCode tags. For example, instead of using [code][b]bold[i]bold italic[/b]italic[/i][/code], use [code][b]bold[i]bold italic[/i][/b][i]italic[/i][/code].
+ [b]Note:[/b] RichTextLabel doesn't support entangled BBCode tags. For example, instead of using [code skip-lint][b]bold[i]bold italic[/b]italic[/i][/code], use [code skip-lint][b]bold[i]bold italic[/i][/b][i]italic[/i][/code].
[b]Note:[/b] [code]push_*/pop_*[/code] functions won't affect BBCode.
- [b]Note:[/b] Unlike [Label], [RichTextLabel] doesn't have a [i]property[/i] to horizontally align text to the center. Instead, enable [member bbcode_enabled] and surround the text in a [code][center][/code] tag as follows: [code][center]Example[/center][/code]. There is currently no built-in way to vertically align text either, but this can be emulated by relying on anchors/containers and the [member fit_content] property.
+ [b]Note:[/b] Unlike [Label], [RichTextLabel] doesn't have a [i]property[/i] to horizontally align text to the center. Instead, enable [member bbcode_enabled] and surround the text in a [code skip-lint][center][/code] tag as follows: [code skip-lint][center]Example[/center][/code]. There is currently no built-in way to vertically align text either, but this can be emulated by relying on anchors/containers and the [member fit_content] property.
</description>
<tutorials>
<link title="BBCode in RichTextLabel">$DOCS_URL/tutorials/ui/bbcode_in_richtextlabel.html</link>
@@ -289,32 +289,32 @@
<return type="void" />
<param index="0" name="bgcolor" type="Color" />
<description>
- Adds a [code][bgcolor][/code] tag to the tag stack.
+ Adds a [code skip-lint][bgcolor][/code] tag to the tag stack.
</description>
</method>
<method name="push_bold">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a bold font to the tag stack. This is the same as adding a [code][b][/code] tag if not currently in a [code][i][/code] tag.
+ Adds a [code skip-lint][font][/code] tag with a bold font to the tag stack. This is the same as adding a [code skip-lint][b][/code] tag if not currently in a [code skip-lint][i][/code] tag.
</description>
</method>
<method name="push_bold_italics">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a bold italics font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a bold italics font to the tag stack.
</description>
</method>
<method name="push_cell">
<return type="void" />
<description>
- Adds a [code][cell][/code] tag to the tag stack. Must be inside a [code][table][/code] tag. See [method push_table] for details.
+ Adds a [code skip-lint][cell][/code] tag to the tag stack. Must be inside a [code skip-lint][table][/code] tag. See [method push_table] for details.
</description>
</method>
<method name="push_color">
<return type="void" />
<param index="0" name="color" type="Color" />
<description>
- Adds a [code][color][/code] tag to the tag stack.
+ Adds a [code skip-lint][color][/code] tag to the tag stack.
</description>
</method>
<method name="push_context">
@@ -341,14 +341,14 @@
<param index="5" name="outline_size" type="int" default="0" />
<param index="6" name="outline_color" type="Color" default="Color(0, 0, 0, 0)" />
<description>
- Adds a [code][dropcap][/code] tag to the tag stack. Drop cap (dropped capital) is a decorative element at the beginning of a paragraph that is larger than the rest of the text.
+ Adds a [code skip-lint][dropcap][/code] tag to the tag stack. Drop cap (dropped capital) is a decorative element at the beginning of a paragraph that is larger than the rest of the text.
</description>
</method>
<method name="push_fgcolor">
<return type="void" />
<param index="0" name="fgcolor" type="Color" />
<description>
- Adds a [code][fgcolor][/code] tag to the tag stack.
+ Adds a [code skip-lint][fgcolor][/code] tag to the tag stack.
</description>
</method>
<method name="push_font">
@@ -356,7 +356,7 @@
<param index="0" name="font" type="Font" />
<param index="1" name="font_size" type="int" default="0" />
<description>
- Adds a [code][font][/code] tag to the tag stack. Overrides default fonts for its duration.
+ Adds a [code skip-lint][font][/code] tag to the tag stack. Overrides default fonts for its duration.
Passing [code]0[/code] to [param font_size] will use the existing default font size.
</description>
</method>
@@ -364,27 +364,27 @@
<return type="void" />
<param index="0" name="font_size" type="int" />
<description>
- Adds a [code][font_size][/code] tag to the tag stack. Overrides default font size for its duration.
+ Adds a [code skip-lint][font_size][/code] tag to the tag stack. Overrides default font size for its duration.
</description>
</method>
<method name="push_hint">
<return type="void" />
<param index="0" name="description" type="String" />
<description>
- Adds a [code][hint][/code] tag to the tag stack. Same as BBCode [code][hint=something]{text}[/hint][/code].
+ Adds a [code skip-lint][hint][/code] tag to the tag stack. Same as BBCode [code skip-lint][hint=something]{text}[/hint][/code].
</description>
</method>
<method name="push_indent">
<return type="void" />
<param index="0" name="level" type="int" />
<description>
- Adds an [code][indent][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
+ Adds an [code skip-lint][indent][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
</description>
</method>
<method name="push_italics">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with an italics font to the tag stack. This is the same as adding an [code][i][/code] tag if not currently in a [code][b][/code] tag.
+ Adds a [code skip-lint][font][/code] tag with an italics font to the tag stack. This is the same as adding an [code skip-lint][i][/code] tag if not currently in a [code skip-lint][b][/code] tag.
</description>
</method>
<method name="push_language">
@@ -401,40 +401,40 @@
<param index="2" name="capitalize" type="bool" />
<param index="3" name="bullet" type="String" default="&quot;•&quot;" />
<description>
- Adds [code][ol][/code] or [code][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
+ Adds [code skip-lint][ol][/code] or [code skip-lint][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
</description>
</method>
<method name="push_meta">
<return type="void" />
<param index="0" name="data" type="Variant" />
<description>
- Adds a meta tag to the tag stack. Similar to the BBCode [code][url=something]{text}[/url][/code], but supports non-[String] metadata types.
+ Adds a meta tag to the tag stack. Similar to the BBCode [code skip-lint][url=something]{text}[/url][/code], but supports non-[String] metadata types.
</description>
</method>
<method name="push_mono">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a monospace font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a monospace font to the tag stack.
</description>
</method>
<method name="push_normal">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a normal font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a normal font to the tag stack.
</description>
</method>
<method name="push_outline_color">
<return type="void" />
<param index="0" name="color" type="Color" />
<description>
- Adds a [code][outline_color][/code] tag to the tag stack. Adds text outline for its duration.
+ Adds a [code skip-lint][outline_color][/code] tag to the tag stack. Adds text outline for its duration.
</description>
</method>
<method name="push_outline_size">
<return type="void" />
<param index="0" name="outline_size" type="int" />
<description>
- Adds a [code][outline_size][/code] tag to the tag stack. Overrides default text outline size for its duration.
+ Adds a [code skip-lint][outline_size][/code] tag to the tag stack. Overrides default text outline size for its duration.
</description>
</method>
<method name="push_paragraph">
@@ -446,13 +446,13 @@
<param index="4" name="justification_flags" type="int" enum="TextServer.JustificationFlag" is_bitfield="true" default="163" />
<param index="5" name="tab_stops" type="PackedFloat32Array" default="PackedFloat32Array()" />
<description>
- Adds a [code][p][/code] tag to the tag stack.
+ Adds a [code skip-lint][p][/code] tag to the tag stack.
</description>
</method>
<method name="push_strikethrough">
<return type="void" />
<description>
- Adds a [code][s][/code] tag to the tag stack.
+ Adds a [code skip-lint][s][/code] tag to the tag stack.
</description>
</method>
<method name="push_table">
@@ -461,13 +461,13 @@
<param index="1" name="inline_align" type="int" enum="InlineAlignment" default="0" />
<param index="2" name="align_to_row" type="int" default="-1" />
<description>
- Adds a [code][table=columns,inline_align][/code] tag to the tag stack.
+ Adds a [code skip-lint][table=columns,inline_align][/code] tag to the tag stack.
</description>
</method>
<method name="push_underline">
<return type="void" />
<description>
- Adds a [code][u][/code] tag to the tag stack.
+ Adds a [code skip-lint][u][/code] tag to the tag stack.
</description>
</method>
<method name="remove_paragraph">
@@ -589,13 +589,13 @@
If [code]true[/code], the label's minimum size will be automatically updated to fit its content, matching the behavior of [Label].
</member>
<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
- If [code]true[/code], the label underlines hint tags such as [code][hint=description]{text}[/hint][/code].
+ If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
<member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true">
- If [code]true[/code], the label underlines meta tags such as [code][url]{text}[/url][/code].
+ If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code].
</member>
<member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000">
The delay after which the loading progress bar is displayed, in milliseconds. Set to [code]-1[/code] to disable progress bar entirely.
@@ -624,7 +624,7 @@
</member>
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
The label's text in BBCode format. Is not representative of manual modifications to the internal tag stack. Erases changes made by other methods when edited.
- [b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [code]text[/code] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
+ [b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [member text] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
</member>
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
Base text writing direction.
@@ -653,7 +653,7 @@
<signal name="meta_clicked">
<param index="0" name="meta" type="Variant" />
<description>
- Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack.
+ Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code skip-lint][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack.
</description>
</signal>
<signal name="meta_hover_ended">
@@ -800,7 +800,7 @@
The default text font size.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- The background used when the [RichTextLabel] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ The background used when the [RichTextLabel] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
The normal background for the [RichTextLabel].
diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml
index 69cfd2bb07..c507a7c39a 100644
--- a/doc/classes/RigidBody3D.xml
+++ b/doc/classes/RigidBody3D.xml
@@ -182,7 +182,7 @@
</member>
<member name="inertia" type="Vector3" setter="set_inertia" getter="get_inertia" default="Vector3(0, 0, 0)">
The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body on each axis. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value.
- If set to [code]Vector3.ZERO[/code], inertia is automatically computed (default value).
+ If set to [constant Vector3.ZERO], inertia is automatically computed (default value).
[b]Note:[/b] This value does not change when inertia is automatically computed. Use [PhysicsServer3D] to get the computed inertia.
[codeblocks]
[gdscript]
diff --git a/doc/classes/Slider.xml b/doc/classes/Slider.xml
index efb646b7ae..0471b8c275 100644
--- a/doc/classes/Slider.xml
+++ b/doc/classes/Slider.xml
@@ -42,7 +42,7 @@
Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position.
</theme_item>
<theme_item name="grabber_offset" data_type="constant" type="int" default="0">
- Vertical/horizontal offset of the grabber.
+ Vertical or horizontal offset of the grabber.
</theme_item>
<theme_item name="grabber" data_type="icon" type="Texture2D">
The texture for the grabber (the draggable element).
@@ -57,13 +57,13 @@
The texture for the ticks, visible when [member Slider.tick_count] is greater than 0.
</theme_item>
<theme_item name="grabber_area" data_type="style" type="StyleBox">
- The background of the area to the left/bottom of the grabber.
+ The background of the area to the left or bottom of the grabber.
</theme_item>
<theme_item name="grabber_area_highlight" data_type="style" type="StyleBox">
- The background of the area to the left/bottom of the grabber that displays when it's being hovered or focused.
+ The background of the area to the left or bottom of the grabber that displays when it's being hovered or focused.
</theme_item>
<theme_item name="slider" data_type="style" type="StyleBox">
- The background for the whole slider. Determines the height/width of the [code]grabber_area[/code].
+ The background for the whole slider. Affects the height or width of the [theme_item grabber_area].
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml
index 217d0947db..0671bd5967 100644
--- a/doc/classes/SpinBox.xml
+++ b/doc/classes/SpinBox.xml
@@ -49,13 +49,13 @@
Changes the alignment of the underlying [LineEdit].
</member>
<member name="custom_arrow_step" type="float" setter="set_custom_arrow_step" getter="get_custom_arrow_step" default="0.0">
- If not [code]0[/code], [code]value[/code] will always be rounded to a multiple of [code]custom_arrow_step[/code] when interacting with the arrow buttons of the [SpinBox].
+ If not [code]0[/code], [member Range.value] will always be rounded to a multiple of [member custom_arrow_step] when interacting with the arrow buttons of the [SpinBox].
</member>
<member name="editable" type="bool" setter="set_editable" getter="is_editable" default="true">
If [code]true[/code], the [SpinBox] will be editable. Otherwise, it will be read only.
</member>
<member name="prefix" type="String" setter="set_prefix" getter="get_prefix" default="&quot;&quot;">
- Adds the specified [code]prefix[/code] string before the numerical value of the [SpinBox].
+ Adds the specified prefix string before the numerical value of the [SpinBox].
</member>
<member name="select_all_on_focus" type="bool" setter="set_select_all_on_focus" getter="is_select_all_on_focus" default="false">
If [code]true[/code], the [SpinBox] will select the whole text when the [LineEdit] gains focus. Clicking the up and down arrows won't trigger this behavior.
@@ -63,7 +63,7 @@
<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="1" />
<member name="step" type="float" setter="set_step" getter="get_step" overrides="Range" default="1.0" />
<member name="suffix" type="String" setter="set_suffix" getter="get_suffix" default="&quot;&quot;">
- Adds the specified [code]suffix[/code] string after the numerical value of the [SpinBox].
+ Adds the specified suffix string after the numerical value of the [SpinBox].
</member>
<member name="update_on_text_changed" type="bool" setter="set_update_on_text_changed" getter="get_update_on_text_changed" default="false">
Sets the value of the [Range] for this [SpinBox] when the [LineEdit] text is [i]changed[/i] instead of [i]submitted[/i]. See [signal LineEdit.text_changed] and [signal LineEdit.text_submitted].
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index d22e646dd0..1898b02166 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1463,7 +1463,7 @@
Sets a custom [Texture2D] for tab text characters.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- Sets the [StyleBox] when in focus. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ Sets the [StyleBox] when in focus. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
Sets the [StyleBox] of this [TextEdit].
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 95601d77af..cfd6d3b281 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -194,7 +194,7 @@
<param index="1" name="size" type="int" />
<param index="2" name="index" type="int" />
<description>
- Returns outline contours of the glyph as a [code]Dictionary[/code] with the following contents:
+ Returns outline contours of the glyph as a [Dictionary] with the following contents:
[code]points[/code] - [PackedVector3Array], containing outline points. [code]x[/code] and [code]y[/code] are point coordinates. [code]z[/code] is the type of the point, using the [enum ContourPointTag] values.
[code]contours[/code] - [PackedInt32Array], containing indices the end points of each contour.
[code]orientation[/code] - [bool], contour orientation. If [code]true[/code], clockwise contours must be filled.
@@ -382,7 +382,7 @@
<return type="Vector2i[]" />
<param index="0" name="font_rid" type="RID" />
<description>
- Returns list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size.
+ Returns list of the font sizes in the cache. Each size is [Vector2i] with font size and outline size.
</description>
</method>
<method name="font_get_spacing" qualifiers="const">
@@ -1998,8 +1998,7 @@
BiDi override for email.
</constant>
<constant name="STRUCTURED_TEXT_LIST" value="4" enum="StructuredTextParser">
- BiDi override for lists.
- Structured text options: list separator [code]String[/code].
+ BiDi override for lists. Structured text options: list separator [String].
</constant>
<constant name="STRUCTURED_TEXT_GDSCRIPT" value="5" enum="StructuredTextParser">
BiDi override for GDScript.
diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml
index 23cacf481f..0c1e9adf71 100644
--- a/doc/classes/TextureProgressBar.xml
+++ b/doc/classes/TextureProgressBar.xml
@@ -71,13 +71,13 @@
[Texture2D] that draws under the progress bar. The bar's background.
</member>
<member name="tint_over" type="Color" setter="set_tint_over" getter="get_tint_over" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_over[/code] texture. The effect is similar to [member CanvasItem.modulate], except it only affects this specific texture instead of the entire node.
+ Multiplies the color of the bar's [member texture_over] texture. The effect is similar to [member CanvasItem.modulate], except it only affects this specific texture instead of the entire node.
</member>
<member name="tint_progress" type="Color" setter="set_tint_progress" getter="get_tint_progress" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_progress[/code] texture.
+ Multiplies the color of the bar's [member texture_progress] texture.
</member>
<member name="tint_under" type="Color" setter="set_tint_under" getter="get_tint_under" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_under[/code] texture.
+ Multiplies the color of the bar's [member texture_under] texture.
</member>
</members>
<constants>
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 8c2a690ec9..37f4cc5c0b 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -168,7 +168,7 @@
<method name="get_next_source_id" qualifiers="const">
<return type="int" />
<description>
- Returns a new unused source ID. This generated ID is the same that a call to [code]add_source[/code] would return.
+ Returns a new unused source ID. This generated ID is the same that a call to [method add_source] would return.
</description>
</method>
<method name="get_occlusion_layer_light_mask" qualifiers="const">
diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml
index f478f37500..755c266cd9 100644
--- a/doc/classes/TileSetAtlasSource.xml
+++ b/doc/classes/TileSetAtlasSource.xml
@@ -39,7 +39,7 @@
<method name="get_atlas_grid_size" qualifiers="const">
<return type="Vector2i" />
<description>
- Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the Texture's size, the atlas [code]margins[/code] the tiles' [code]texture_region_size[/code].
+ Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the [member texture]'s size, the atlas [member margins], and the tiles' [member texture_region_size].
</description>
</method>
<method name="get_next_alternative_tile_id" qualifiers="const">
diff --git a/doc/classes/Time.xml b/doc/classes/Time.xml
index 8ed17f6d8a..9dc567562a 100644
--- a/doc/classes/Time.xml
+++ b/doc/classes/Time.xml
@@ -48,8 +48,8 @@
<param index="0" name="datetime" type="String" />
<param index="1" name="weekday" type="bool" />
<description>
- Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
- If [param weekday] is [code]false[/code], then the [code]weekday[/code] entry is excluded (the calculation is relatively expensive).
+ Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code skip-lint]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
+ If [param weekday] is [code]false[/code], then the [code skip-lint]weekday[/code] entry is excluded (the calculation is relatively expensive).
[b]Note:[/b] Any decimal fraction in the time string will be ignored silently.
</description>
</method>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index 6295412692..abb81b95cb 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -133,7 +133,7 @@
<return type="bool" />
<param index="0" name="xform" type="Transform2D" />
<description>
- Returns [code]true[/code] if this transform and [param xform] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
+ Returns [code]true[/code] if this transform and [param xform] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
</description>
</method>
<method name="is_finite" qualifiers="const">
diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml
index 7bca99d697..c96be5124e 100644
--- a/doc/classes/Transform3D.xml
+++ b/doc/classes/Transform3D.xml
@@ -80,7 +80,7 @@
<return type="bool" />
<param index="0" name="xform" type="Transform3D" />
<description>
- Returns [code]true[/code] if this transform and [param xform] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
+ Returns [code]true[/code] if this transform and [param xform] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
</description>
</method>
<method name="is_finite" qualifiers="const">
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index d85678cc81..e322e3adc0 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -458,7 +458,7 @@
<param index="1" name="column" type="int" />
<param index="2" name="selected" type="bool" />
<description>
- Emitted instead of [code]item_selected[/code] if [code]select_mode[/code] is [constant SELECT_MULTI].
+ Emitted instead of [signal item_selected] if [member select_mode] is set to [constant SELECT_MULTI].
</description>
</signal>
<signal name="nothing_selected">
diff --git a/doc/classes/Variant.xml b/doc/classes/Variant.xml
index dc3e25fa9b..2734800642 100644
--- a/doc/classes/Variant.xml
+++ b/doc/classes/Variant.xml
@@ -28,7 +28,7 @@
[/codeblocks]
Godot tracks all scripting API variables within Variants. Without even realizing it, you use Variants all the time. When a particular language enforces its own rules for keeping data typed, then that language is applying its own custom logic over the base Variant scripting API.
- GDScript automatically wrap values in them. It keeps all data in plain Variants by default and then optionally enforces custom static typing rules on variable types.
- - C# is statically typed, but uses its own implementation of the [code]Variant[/code] type in place of Godot's Variant class when it needs to represent a dynamic value. A [code]Variant[/code] can be assigned any compatible type implicitly but converting requires an explicit cast.
+ - C# is statically typed, but uses its own implementation of the Variant type in place of Godot's [Variant] class when it needs to represent a dynamic value. C# Variant can be assigned any compatible type implicitly but converting requires an explicit cast.
The global [method @GlobalScope.typeof] function returns the enumerated value of the Variant type stored in the current variable (see [enum Variant.Type]).
[codeblocks]
[gdscript]
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index b4718d96a3..fdaaf239f8 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -157,7 +157,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index 840e2ba61a..92fbdf6850 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -131,7 +131,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml
index 6f99234991..e5ad5631e8 100644
--- a/doc/classes/Vector4.xml
+++ b/doc/classes/Vector4.xml
@@ -85,7 +85,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/VisualShaderNode.xml b/doc/classes/VisualShaderNode.xml
index baf9eded56..cc6394e2da 100644
--- a/doc/classes/VisualShaderNode.xml
+++ b/doc/classes/VisualShaderNode.xml
@@ -67,28 +67,28 @@
</members>
<constants>
<constant name="PORT_TYPE_SCALAR" value="0" enum="PortType">
- Floating-point scalar. Translated to [code]float[/code] type in shader code.
+ Floating-point scalar. Translated to [code skip-lint]float[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SCALAR_INT" value="1" enum="PortType">
- Integer scalar. Translated to [code]int[/code] type in shader code.
+ Integer scalar. Translated to [code skip-lint]int[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SCALAR_UINT" value="2" enum="PortType">
- Unsigned integer scalar. Translated to [code]uint[/code] type in shader code.
+ Unsigned integer scalar. Translated to [code skip-lint]uint[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_2D" value="3" enum="PortType">
- 2D vector of floating-point values. Translated to [code]vec2[/code] type in shader code.
+ 2D vector of floating-point values. Translated to [code skip-lint]vec2[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_3D" value="4" enum="PortType">
- 3D vector of floating-point values. Translated to [code]vec3[/code] type in shader code.
+ 3D vector of floating-point values. Translated to [code skip-lint]vec3[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_4D" value="5" enum="PortType">
- 4D vector of floating-point values. Translated to [code]vec4[/code] type in shader code.
+ 4D vector of floating-point values. Translated to [code skip-lint]vec4[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_BOOLEAN" value="6" enum="PortType">
- Boolean type. Translated to [code]bool[/code] type in shader code.
+ Boolean type. Translated to [code skip-lint]bool[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_TRANSFORM" value="7" enum="PortType">
- Transform type. Translated to [code]mat4[/code] type in shader code.
+ Transform type. Translated to [code skip-lint]mat4[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SAMPLER" value="8" enum="PortType">
Sampler type. Translated to reference of sampler uniform in shader code. Can only be used for input ports in non-uniform nodes.
diff --git a/doc/classes/VisualShaderNodeBooleanConstant.xml b/doc/classes/VisualShaderNodeBooleanConstant.xml
index d32af17940..d2a7ff3d45 100644
--- a/doc/classes/VisualShaderNodeBooleanConstant.xml
+++ b/doc/classes/VisualShaderNodeBooleanConstant.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Has only one output port and no inputs.
- Translated to [code]bool[/code] in the shader language.
+ Translated to [code skip-lint]bool[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeFloatConstant.xml b/doc/classes/VisualShaderNodeFloatConstant.xml
index 9b57a9a240..2436663f1f 100644
--- a/doc/classes/VisualShaderNodeFloatConstant.xml
+++ b/doc/classes/VisualShaderNodeFloatConstant.xml
@@ -4,7 +4,7 @@
A scalar floating-point constant to be used within the visual shader graph.
</brief_description>
<description>
- Translated to [code]float[/code] in the shader language.
+ Translated to [code skip-lint]float[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeIntConstant.xml b/doc/classes/VisualShaderNodeIntConstant.xml
index bde1aaab5d..f942bf299f 100644
--- a/doc/classes/VisualShaderNodeIntConstant.xml
+++ b/doc/classes/VisualShaderNodeIntConstant.xml
@@ -4,7 +4,7 @@
A scalar integer constant to be used within the visual shader graph.
</brief_description>
<description>
- Translated to [code]int[/code] in the shader language.
+ Translated to [code skip-lint]int[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeSwitch.xml b/doc/classes/VisualShaderNodeSwitch.xml
index 9b9266c478..a44ca3f0b3 100644
--- a/doc/classes/VisualShaderNodeSwitch.xml
+++ b/doc/classes/VisualShaderNodeSwitch.xml
@@ -4,7 +4,7 @@
A selector function for use within the visual shader graph.
</brief_description>
<description>
- Returns an associated value of the [code]op_type[/code] type if the provided boolean value is [code]true[/code] or [code]false[/code].
+ Returns an associated value of the [member op_type] type if the provided boolean value is [code]true[/code] or [code]false[/code].
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/WorldBoundaryShape2D.xml b/doc/classes/WorldBoundaryShape2D.xml
index 018e6289a3..ee1aa8d224 100644
--- a/doc/classes/WorldBoundaryShape2D.xml
+++ b/doc/classes/WorldBoundaryShape2D.xml
@@ -14,7 +14,7 @@
In the scalar equation of the line [code]ax + by = d[/code], this is [code]d[/code], while the [code](a, b)[/code] coordinates are represented by the [member normal] property.
</member>
<member name="normal" type="Vector2" setter="set_normal" getter="get_normal" default="Vector2(0, -1)">
- The line's normal, typically a unit vector. Its direction indicates the non-colliding half-plane. Can be of any length but zero. Defaults to [code]Vector2.UP[/code].
+ The line's normal, typically a unit vector. Its direction indicates the non-colliding half-plane. Can be of any length but zero. Defaults to [constant Vector2.UP].
</member>
</members>
</class>
diff --git a/doc/classes/float.xml b/doc/classes/float.xml
index 7d74e3d832..3ffaa60c32 100644
--- a/doc/classes/float.xml
+++ b/doc/classes/float.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
The [float] built-in type is a 64-bit double-precision floating-point number, equivalent to [code]double[/code] in C++. This type has 14 reliable decimal digits of precision. The maximum value of [float] is approximately [code]1.79769e308[/code], and the minimum is approximately [code]-1.79769e308[/code].
- Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to [code]float[/code] in C++, which have 6 reliable decimal digits of precision. For data structures such as [Vector2] and [Vector3], Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the [code]precision=double[/code] option.
+ Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to [code skip-lint]float[/code] in C++, which have 6 reliable decimal digits of precision. For data structures such as [Vector2] and [Vector3], Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the [code]precision=double[/code] option.
Math done using the [float] type is not guaranteed to be exact and will often result in small errors. You should usually use the [method @GlobalScope.is_equal_approx] and [method @GlobalScope.is_zero_approx] methods instead of [code]==[/code] to compare [float] values for equality.
</description>
<tutorials>
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 54bad7cf05..e62b3d56d7 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -401,6 +401,15 @@ class State:
self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0].lower()))
+class TagState:
+ def __init__(self, raw: str, name: str, arguments: List[str], closing: bool) -> None:
+ self.raw = raw
+
+ self.name = name
+ self.arguments = arguments
+ self.closing = closing
+
+
class TypeName:
def __init__(self, type_name: str, enum: Optional[str] = None, is_bitfield: bool = False) -> None:
self.type_name = type_name
@@ -694,6 +703,8 @@ def main() -> None:
print("")
+ # Print out warnings and errors, or lack thereof, and exit with an appropriate code.
+
if state.num_warnings >= 2:
print(
f'{STYLES["yellow"]}{state.num_warnings} warnings were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
@@ -703,19 +714,20 @@ def main() -> None:
f'{STYLES["yellow"]}1 warning was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
)
- if state.num_errors == 0:
- print(f'{STYLES["green"]}No errors found in the class reference XML.{STYLES["reset"]}')
+ if state.num_errors >= 2:
+ print(
+ f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+ )
+ elif state.num_errors == 1:
+ print(
+ f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+ )
+
+ if state.num_warnings == 0 and state.num_errors == 0:
+ print(f'{STYLES["green"]}No warnings or errors found in the class reference XML.{STYLES["reset"]}')
if not args.dry_run:
print(f"Wrote reStructuredText files for each class to: {args.output}")
else:
- if state.num_errors >= 2:
- print(
- f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
- else:
- print(
- f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
exit(1)
@@ -1562,8 +1574,20 @@ def make_rst_index(grouped_classes: Dict[str, List[str]], dry_run: bool, output_
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"]
+RESERVED_LAYOUT_TAGS = ["codeblocks"]
+RESERVED_CODEBLOCK_TAGS = ["codeblock", "gdscript", "csharp"]
+RESERVED_CROSSLINK_TAGS = [
+ "method",
+ "constructor",
+ "operator",
+ "member",
+ "signal",
+ "constant",
+ "enum",
+ "annotation",
+ "theme_item",
+ "param",
+]
def is_in_tagset(tag_text: str, tagset: List[str]) -> bool:
@@ -1581,6 +1605,35 @@ def is_in_tagset(tag_text: str, tagset: List[str]) -> bool:
return False
+def get_tag_and_args(tag_text: str) -> TagState:
+ tag_name = tag_text
+ arguments: List[str] = []
+
+ assign_pos = tag_text.find("=")
+ if assign_pos >= 0:
+ tag_name = tag_text[:assign_pos]
+ arguments = [tag_text[assign_pos + 1 :].strip()]
+ else:
+ space_pos = tag_text.find(" ")
+ if space_pos >= 0:
+ tag_name = tag_text[:space_pos]
+ arguments = [tag_text[space_pos + 1 :].strip()]
+
+ closing = False
+ if tag_name.startswith("/"):
+ tag_name = tag_name[1:]
+ closing = True
+
+ return TagState(tag_text, tag_name, arguments, closing)
+
+
+def parse_link_target(link_target: str, state: State, context_name: str) -> List[str]:
+ if link_target.find(".") != -1:
+ return link_target.split(".")
+ else:
+ return [state.current_class, link_target]
+
+
def format_text_block(
text: str,
context: Union[DefinitionBase, None],
@@ -1603,11 +1656,15 @@ def format_text_block(
# Handle codeblocks
if (
post_text.startswith("[codeblock]")
+ or post_text.startswith("[codeblock ")
or post_text.startswith("[gdscript]")
+ or post_text.startswith("[gdscript ")
or post_text.startswith("[csharp]")
+ or post_text.startswith("[csharp ")
):
- block_type = post_text[1:].split("]")[0]
- result = format_codeblock(block_type, post_text, indent_level, state)
+ tag_text = post_text[1:].split("]", 1)[0]
+ tag_state = get_tag_and_args(tag_text)
+ result = format_codeblock(tag_state, post_text, indent_level, state)
if result is None:
return ""
text = f"{pre_text}{result[0]}"
@@ -1627,6 +1684,8 @@ def format_text_block(
inside_code = False
inside_code_tag = ""
inside_code_tabs = False
+ ignore_code_warnings = False
+
pos = 0
tag_depth = 0
while True:
@@ -1657,33 +1716,34 @@ def format_text_block(
# Tag is a cross-reference or a formatting directive.
else:
- cmd = tag_text
- space_pos = tag_text.find(" ")
+ tag_state = get_tag_and_args(tag_text)
# 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":
+ if tag_state.closing and tag_state.name == inside_code_tag:
+ if is_in_tagset(tag_state.name, RESERVED_CODEBLOCK_TAGS):
tag_text = ""
tag_depth -= 1
inside_code = False
+ ignore_code_warnings = False
# Strip newline if the tag was alone on one
if pre_text[-1] == "\n":
pre_text = pre_text[:-1]
- elif cmd == "/code":
+ elif is_in_tagset(tag_state.name, ["code"]):
tag_text = "``"
tag_depth -= 1
inside_code = False
+ ignore_code_warnings = False
escape_post = True
else:
- if cmd.startswith("/"):
+ if not ignore_code_warnings and tag_state.closing:
print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string that looks like a closing tag "[{cmd}]" in {context_name}.',
+ f'{state.current_class}.xml: Found a code string that looks like a closing tag "[{tag_state.raw}]" in {context_name}.',
state,
)
@@ -1691,26 +1751,27 @@ def format_text_block(
# 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
+ elif tag_state.name == "codeblocks":
+ if tag_state.closing:
+ tag_depth -= 1
+ tag_text = ""
+ inside_code_tabs = False
+ else:
+ tag_depth += 1
+ tag_text = "\n.. tabs::"
+ inside_code_tabs = True
- elif cmd == "codeblock" or cmd == "gdscript" or cmd == "csharp":
+ elif is_in_tagset(tag_state.name, RESERVED_CODEBLOCK_TAGS):
tag_depth += 1
- if cmd == "gdscript":
+ if tag_state.name == "gdscript":
if not inside_code_tabs:
print_error(
f"{state.current_class}.xml: GDScript code block is used outside of [codeblocks] in {context_name}.",
state,
)
tag_text = "\n .. code-tab:: gdscript\n"
- elif cmd == "csharp":
+ elif tag_state.name == "csharp":
if not inside_code_tabs:
print_error(
f"{state.current_class}.xml: C# code block is used outside of [codeblocks] in {context_name}.",
@@ -1721,17 +1782,19 @@ def format_text_block(
tag_text = "\n::\n"
inside_code = True
- inside_code_tag = cmd
+ inside_code_tag = tag_state.name
+ ignore_code_warnings = "skip-lint" in tag_state.arguments
- elif cmd == "code":
+ elif is_in_tagset(tag_state.name, ["code"]):
tag_text = "``"
tag_depth += 1
+
inside_code = True
- inside_code_tag = cmd
+ inside_code_tag = "code"
+ ignore_code_warnings = "skip-lint" in tag_state.arguments
escape_pre = True
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if valid_context:
+ if not ignore_code_warnings:
endcode_pos = text.find("[/code]", endq_pos + 1)
if endcode_pos == -1:
print_error(
@@ -1741,110 +1804,173 @@ def format_text_block(
break
inside_code_text = text[endq_pos + 1 : endcode_pos]
- context_params: List[ParameterDef] = context.parameters # type: ignore
- for param_def in context_params:
- if param_def.name == inside_code_text:
+ if inside_code_text.endswith("()"):
+ # It's formatted like a call for some reason, may still be a mistake.
+ inside_code_text = inside_code_text[:-2]
+
+ if inside_code_text in state.classes:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the known classes in {context_name}.',
+ state,
+ )
+
+ target_class_name, target_name, *rest = parse_link_target(inside_code_text, state, context_name)
+ if len(rest) == 0 and target_class_name in state.classes:
+ class_def = state.classes[target_class_name]
+
+ if target_name in class_def.methods:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} method in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.constructors:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constructor in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.operators:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} operator in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.properties:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} member in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.signals:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} signal in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.annotations:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} annotation in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.theme_items:
print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string "{inside_code_text}" that matches one of the parameters in {context_name}.',
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} theme item in {context_name}.',
state,
)
- break
+
+ elif target_name in class_def.constants:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constant in {context_name}.',
+ state,
+ )
+
+ else:
+ for enum in class_def.enums.values():
+ if target_name in enum.values:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} enum value in {context_name}.',
+ state,
+ )
+ break
+
+ valid_param_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
+ if valid_param_context:
+ context_params: List[ParameterDef] = context.parameters # type: ignore
+ for param_def in context_params:
+ if param_def.name == inside_code_text:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the parameters in {context_name}.',
+ state,
+ )
+ break
# Cross-references to items in this or other class documentation pages.
- elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS):
- link_type: str = ""
- link_target: str = ""
- if space_pos >= 0:
- link_type = tag_text[:space_pos]
- link_target = tag_text[space_pos + 1 :].strip()
+ elif is_in_tagset(tag_state.name, RESERVED_CROSSLINK_TAGS):
+ link_target: str = tag_state.arguments[0] if len(tag_state.arguments) > 0 else ""
if link_target == "":
print_error(
- f'{state.current_class}.xml: Empty cross-reference link "{cmd}" in {context_name}.',
+ f'{state.current_class}.xml: Empty cross-reference link "[{tag_state.raw}]" in {context_name}.',
state,
)
tag_text = ""
else:
if (
- cmd.startswith("method")
- or cmd.startswith("constructor")
- or cmd.startswith("operator")
- or cmd.startswith("member")
- or cmd.startswith("signal")
- or cmd.startswith("annotation")
- or cmd.startswith("theme_item")
- or cmd.startswith("constant")
+ tag_state.name == "method"
+ or tag_state.name == "constructor"
+ or tag_state.name == "operator"
+ or tag_state.name == "member"
+ or tag_state.name == "signal"
+ or tag_state.name == "annotation"
+ or tag_state.name == "theme_item"
+ or tag_state.name == "constant"
):
- if link_target.find(".") != -1:
- ss = link_target.split(".")
- if len(ss) > 2:
- print_error(
- f'{state.current_class}.xml: Bad reference "{link_target}" in {context_name}.',
- state,
- )
- class_param, method_param = ss
-
- else:
- class_param = state.current_class
- method_param = link_target
+ target_class_name, target_name, *rest = parse_link_target(link_target, state, context_name)
+ if len(rest) > 0:
+ print_error(
+ f'{state.current_class}.xml: Bad reference "{link_target}" in {context_name}.',
+ state,
+ )
# Default to the tag command name. This works by default for most tags,
# but member and theme_item have special cases.
- ref_type = "_{}".format(link_type)
- if link_type == "member":
+ ref_type = "_{}".format(tag_state.name)
+ if tag_state.name == "member":
ref_type = "_property"
- if class_param in state.classes:
- class_def = state.classes[class_param]
+ if target_class_name in state.classes:
+ class_def = state.classes[target_class_name]
- if cmd.startswith("method") and method_param not in class_def.methods:
+ if tag_state.name == "method" and target_name not in class_def.methods:
print_error(
f'{state.current_class}.xml: Unresolved method reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("constructor") and method_param not in class_def.constructors:
+ elif tag_state.name == "constructor" and target_name not in class_def.constructors:
print_error(
f'{state.current_class}.xml: Unresolved constructor reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("operator") and method_param not in class_def.operators:
+ elif tag_state.name == "operator" and target_name not in class_def.operators:
print_error(
f'{state.current_class}.xml: Unresolved operator reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("member") and method_param not in class_def.properties:
+ elif tag_state.name == "member" and target_name not in class_def.properties:
print_error(
f'{state.current_class}.xml: Unresolved member reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("signal") and method_param not in class_def.signals:
+ elif tag_state.name == "signal" and target_name not in class_def.signals:
print_error(
f'{state.current_class}.xml: Unresolved signal reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("annotation") and method_param not in class_def.annotations:
+ elif tag_state.name == "annotation" and target_name not in class_def.annotations:
print_error(
f'{state.current_class}.xml: Unresolved annotation reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("theme_item"):
- if method_param not in class_def.theme_items:
+ elif tag_state.name == "theme_item":
+ if target_name not in class_def.theme_items:
print_error(
f'{state.current_class}.xml: Unresolved theme item reference "{link_target}" in {context_name}.',
state,
)
else:
# Needs theme data type to be properly linked, which we cannot get without a class.
- name = class_def.theme_items[method_param].data_name
+ name = class_def.theme_items[target_name].data_name
ref_type = f"_theme_{name}"
- elif cmd.startswith("constant"):
+ elif tag_state.name == "constant":
found = False
# Search in the current class
@@ -1855,14 +1981,14 @@ def format_text_block(
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
+ if target_name in search_class_def.constants:
+ target_class_name = 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
+ if target_name in enum.values:
+ target_class_name = search_class_def.name
found = True
break
@@ -1874,25 +2000,25 @@ def format_text_block(
else:
print_error(
- f'{state.current_class}.xml: Unresolved type reference "{class_param}" in method reference "{link_target}" in {context_name}.',
+ f'{state.current_class}.xml: Unresolved type reference "{target_class_name}" in method reference "{link_target}" in {context_name}.',
state,
)
- repl_text = method_param
- if class_param != state.current_class:
- repl_text = f"{class_param}.{method_param}"
- tag_text = f":ref:`{repl_text}<class_{class_param}{ref_type}_{method_param}>`"
+ repl_text = target_name
+ if target_class_name != state.current_class:
+ repl_text = f"{target_class_name}.{target_name}"
+ tag_text = f":ref:`{repl_text}<class_{target_class_name}{ref_type}_{target_name}>`"
escape_pre = True
escape_post = True
- elif cmd.startswith("enum"):
+ elif tag_state.name == "enum":
tag_text = make_enum(link_target, False, state)
escape_pre = True
escape_post = True
- elif cmd.startswith("param"):
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if not valid_context:
+ elif tag_state.name == "param":
+ valid_param_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
+ if not valid_param_context:
print_error(
f'{state.current_class}.xml: Argument reference "{link_target}" used outside of method, signal, or annotation context in {context_name}.',
state,
@@ -1916,11 +2042,17 @@ def format_text_block(
# 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:]
+ elif is_in_tagset(tag_state.name, ["url"]):
+ url_target = tag_state.arguments[0] if len(tag_state.arguments) > 0 else ""
+
+ if url_target == "":
+ print_error(
+ f'{state.current_class}.xml: Misformatted [url] tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
+ else:
+ # Unlike other tags, URLs are handled in full here, as we need to extract
+ # the optional link title to use `make_link`.
endurl_pos = text.find("[/url]", endq_pos + 1)
if endurl_pos == -1:
print_error(
@@ -1929,7 +2061,7 @@ def format_text_block(
)
break
link_title = text[endq_pos + 1 : endurl_pos]
- tag_text = make_link(link_url, link_title)
+ tag_text = make_link(url_target, link_title)
pre_text = text[:pos]
post_text = text[endurl_pos + 6 :]
@@ -1942,28 +2074,23 @@ def format_text_block(
text = pre_text + tag_text + post_text
pos = len(pre_text) + len(tag_text)
continue
- else:
- print_error(
- f'{state.current_class}.xml: Misformatted [url] tag "{cmd}" in {context_name}.',
- state,
- )
- elif cmd == "br":
+ elif tag_state.name == "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":
+ elif tag_state.name == "center":
+ if tag_state.closing:
tag_depth -= 1
else:
tag_depth += 1
tag_text = ""
- elif cmd == "i" or cmd == "/i":
- if cmd == "/i":
+ elif tag_state.name == "i":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1971,8 +2098,8 @@ def format_text_block(
escape_pre = True
tag_text = "*"
- elif cmd == "b" or cmd == "/b":
- if cmd == "/b":
+ elif tag_state.name == "b":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1980,8 +2107,8 @@ def format_text_block(
escape_pre = True
tag_text = "**"
- elif cmd == "u" or cmd == "/u":
- if cmd == "/u":
+ elif tag_state.name == "u":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1989,9 +2116,9 @@ def format_text_block(
escape_pre = True
tag_text = ""
- elif cmd == "kbd" or cmd == "/kbd":
+ elif tag_state.name == "kbd":
tag_text = "`"
- if cmd == "/kbd":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1999,18 +2126,24 @@ def format_text_block(
tag_depth += 1
escape_pre = True
- # Invalid syntax checks.
- elif cmd.startswith("/"):
- print_error(f'{state.current_class}.xml: Unrecognized closing tag "{cmd}" in {context_name}.', state)
-
- tag_text = f"[{tag_text}]"
-
+ # Invalid syntax.
else:
- print_error(f'{state.current_class}.xml: Unrecognized opening tag "{cmd}" in {context_name}.', state)
+ if tag_state.closing:
+ print_error(
+ f'{state.current_class}.xml: Unrecognized closing tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
- tag_text = f"``{tag_text}``"
- escape_pre = True
- escape_post = True
+ tag_text = f"[{tag_text}]"
+ else:
+ print_error(
+ f'{state.current_class}.xml: Unrecognized opening tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
+
+ tag_text = f"``{tag_text}``"
+ escape_pre = True
+ escape_post = True
# Properly escape things like `[Node]s`
if escape_pre and pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT:
@@ -2092,13 +2225,22 @@ def escape_rst(text: str, until_pos: int = -1) -> str:
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 + "]")
+def format_codeblock(
+ tag_state: TagState, post_text: str, indent_level: int, state: State
+) -> Union[Tuple[str, int], None]:
+ end_pos = post_text.find("[/" + tag_state.name + "]")
if end_pos == -1:
- print_error(f"{state.current_class}.xml: [{code_type}] without a closing tag.", state)
+ print_error(
+ f"{state.current_class}.xml: Tag depth mismatch for [{tag_state.name}]: no closing [/{tag_state.name}].",
+ state,
+ )
return None
- code_text = post_text[len(f"[{code_type}]") : end_pos]
+ opening_formatted = tag_state.name
+ if len(tag_state.arguments) > 0:
+ opening_formatted += " " + " ".join(tag_state.arguments)
+
+ code_text = post_text[len(f"[{opening_formatted}]") : end_pos]
post_text = post_text[end_pos:]
# Remove extraneous tabs
@@ -2114,7 +2256,7 @@ def format_codeblock(code_type: str, post_text: str, indent_level: int, state: S
if to_skip > indent_level:
print_error(
- f"{state.current_class}.xml: Four spaces should be used for indentation within [{code_type}].",
+ f"{state.current_class}.xml: Four spaces should be used for indentation within [{tag_state.name}].",
state,
)
@@ -2124,7 +2266,7 @@ def format_codeblock(code_type: str, post_text: str, indent_level: int, state: S
else:
code_text = f"{code_text[:code_pos]}\n {code_text[code_pos + to_skip + 1 :]}"
code_pos += 5 - to_skip
- return (f"\n[{code_type}]{code_text}{post_text}", len(f"\n[{code_type}]{code_text}"))
+ return (f"\n[{opening_formatted}]{code_text}{post_text}", len(f"\n[{opening_formatted}]{code_text}"))
def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_columns: bool = False) -> None:
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index 11321b2121..e553b2b522 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -5054,6 +5054,7 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_
}
Shader *shader = shader_owner.get_or_null(id);
+ ERR_FAIL_NULL_V(shader, RID());
shader->vertex_input_mask = vertex_input_mask;
shader->fragment_output_mask = fragment_output_mask;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 208253d617..31659d4d4e 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -835,35 +835,9 @@ ConnectDialog::~ConnectDialog() {
//////////////////////////////////////////
-// Originally copied and adapted from EditorProperty, try to keep style in sync.
Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
- // `p_text` is expected to be something like this:
- // - `class|Control||Control brief description.`;
- // - `signal|gui_input|(event: InputEvent)|gui_input description.`;
- // - `../../.. :: _on_gui_input()`.
- // Note that the description can be empty or contain `|`.
- PackedStringArray slices = p_text.split("|", true, 3);
- if (slices.size() < 4) {
- return nullptr; // Use default tooltip instead.
- }
-
- String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:");
- String item_name = slices[1].strip_edges();
- String item_params = slices[2].strip_edges();
- String item_descr = slices[3].strip_edges();
-
- String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n";
- if (item_descr.is_empty()) {
- text += "[i]" + TTR("No description.") + "[/i]";
- } else {
- text += item_descr;
- }
-
- EditorHelpBit *help_bit = memnew(EditorHelpBit);
- help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
- help_bit->set_text(text);
-
- return help_bit;
+ // If it's not a doc tooltip, fallback to the default one.
+ return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text));
}
struct _ConnectionsDockMethodInfoSort {
@@ -1341,7 +1315,6 @@ void ConnectionsDock::update_tree() {
while (native_base != StringName()) {
String class_name;
String doc_class_name;
- String class_brief;
Ref<Texture2D> class_icon;
List<MethodInfo> class_signals;
@@ -1355,21 +1328,8 @@ void ConnectionsDock::update_tree() {
if (doc_class_name.is_empty()) {
doc_class_name = script_base->get_path().trim_prefix("res://").quote();
}
-
- // For a script class, the cache is filled each time.
- if (!doc_class_name.is_empty()) {
- if (descr_cache.has(doc_class_name)) {
- descr_cache[doc_class_name].clear();
- }
- HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
- if (F) {
- class_brief = F->value.brief_description;
- for (int i = 0; i < F->value.signals.size(); i++) {
- descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description;
- }
- } else {
- doc_class_name = String();
- }
+ if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) {
+ doc_class_name = String();
}
class_icon = editor_data.get_script_icon(script_base);
@@ -1398,18 +1358,9 @@ void ConnectionsDock::update_tree() {
script_base = base;
} else {
class_name = native_base;
- doc_class_name = class_name;
-
- HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
- if (F) {
- class_brief = DTR(F->value.brief_description);
- // For a native class, the cache is filled once.
- if (!descr_cache.has(doc_class_name)) {
- for (int i = 0; i < F->value.signals.size(); i++) {
- descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description);
- }
- }
- } else {
+ doc_class_name = native_base;
+
+ if (!doc_data->class_list.find(doc_class_name)) {
doc_class_name = String();
}
@@ -1434,8 +1385,8 @@ void ConnectionsDock::update_tree() {
section_item = tree->create_item(root);
section_item->set_text(0, class_name);
- // `|` separators used in `make_custom_tooltip()` for formatting.
- section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief);
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ section_item->set_tooltip_text(0, "class|" + doc_class_name + "||");
section_item->set_icon(0, class_icon);
section_item->set_selectable(0, false);
section_item->set_editable(0, false);
@@ -1466,22 +1417,8 @@ void ConnectionsDock::update_tree() {
sinfo["args"] = argnames;
signal_item->set_metadata(0, sinfo);
signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal")));
-
- // Set tooltip with the signal's documentation.
- {
- String descr;
-
- HashMap<StringName, HashMap<StringName, String>>::ConstIterator G = descr_cache.find(doc_class_name);
- if (G) {
- HashMap<StringName, String>::ConstIterator F = G->value.find(signal_name);
- if (F) {
- descr = F->value;
- }
- }
-
- // `|` separators used in `make_custom_tooltip()` for formatting.
- signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr);
- }
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name));
// List existing connections.
List<Object::Connection> existing_connections;
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index b07b08ecc7..7316a770ec 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -231,8 +231,6 @@ class ConnectionsDock : public VBoxContainer {
PopupMenu *slot_menu = nullptr;
LineEdit *search_box = nullptr;
- HashMap<StringName, HashMap<StringName, String>> descr_cache;
-
void _filter_changed(const String &p_text);
void _make_or_edit_connection();
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 1c8226e5e1..0e025b4430 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -500,10 +500,11 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
to_select->select(0);
search_options->scroll_to_item(to_select, p_center_on_item);
- if (EditorHelp::get_doc_data()->class_list.has(p_type) && !DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description).is_empty()) {
+ String text = help_bit->get_class_description(p_type);
+ if (!text.is_empty()) {
// Display both class name and description, since the help bit may be displayed
// far away from the location (especially if the dialog was resized to be taller).
- help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description)));
+ help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text));
help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
} else {
// Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index bca30b2d2d..c3087d797a 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -646,24 +646,21 @@ void EditorBuildProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int build_option_id = md;
- String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id));
-
- description_bit->set_text(TTRGET(build_option_description));
- return;
- } else {
- return;
+ String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
}
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 50406bea6a..5a44ae1aba 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -555,21 +555,22 @@ void EditorFeatureProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int feature_id = md;
- String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature(feature_id));
+ String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- description_bit->set_text(TTRGET(feature_description));
return;
} else {
return;
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index b3bcb9f014..5d07ba7568 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -38,6 +38,7 @@
#include "doc_data_compressed.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
+#include "editor/editor_property_name_processor.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -216,6 +217,12 @@ void EditorHelp::_class_desc_select(const String &p_select) {
if (tag == "method") {
topic = "class_method";
table = &this->method_line;
+ } else if (tag == "constructor") {
+ topic = "class_method";
+ table = &this->method_line;
+ } else if (tag == "operator") {
+ topic = "class_method";
+ table = &this->method_line;
} else if (tag == "member") {
topic = "class_property";
table = &this->property_line;
@@ -410,8 +417,10 @@ String EditorHelp::_fix_constant(const String &p_constant) const {
class_desc->add_text(" (" + TTR("Experimental") + ")"); \
class_desc->pop();
-void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview) {
- method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description
+void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override) {
+ if (p_override) {
+ method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
+ }
const bool is_vararg = p_method.qualifiers.contains("vararg");
@@ -582,7 +591,7 @@ Error EditorHelp::_goto_desc(const String &p_class) {
return OK;
}
-void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods) {
+void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type) {
class_desc->add_newline();
_push_code_font();
@@ -628,7 +637,8 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods)
class_desc->pop(); // cell
}
- _add_method(m[i], true);
+ // For constructors always point to the first one.
+ _add_method(m[i], true, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
}
any_previous = !m.is_empty();
@@ -660,7 +670,8 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc p_classdoc,
for (int i = 0; i < methods_filtered.size(); i++) {
_push_code_font();
- _add_method(methods_filtered[i], false);
+ // For constructors always point to the first one.
+ _add_method(methods_filtered[i], false, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
_pop_code_font();
class_desc->add_newline();
@@ -1151,7 +1162,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Constructors"));
_pop_title_font();
- _update_method_list(cd.constructors);
+ _update_method_list(cd.constructors, METHOD_TYPE_CONSTRUCTOR);
}
if (!methods.is_empty()) {
@@ -1164,7 +1175,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Methods"));
_pop_title_font();
- _update_method_list(methods);
+ _update_method_list(methods, METHOD_TYPE_METHOD);
}
if (!cd.operators.is_empty()) {
@@ -1177,7 +1188,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Operators"));
_pop_title_font();
- _update_method_list(cd.operators);
+ _update_method_list(cd.operators, METHOD_TYPE_OPERATOR);
}
// Theme properties
@@ -1991,10 +2002,10 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
// Select the correct code examples.
switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) {
case 0: // GDScript
- bbcode = bbcode.replace("[gdscript]", "[codeblock]");
+ bbcode = bbcode.replace("[gdscript", "[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
- for (int pos = bbcode.find("[csharp]"); pos != -1; pos = bbcode.find("[csharp]")) {
+ for (int pos = bbcode.find("[csharp"); pos != -1; pos = bbcode.find("[csharp")) {
int end_pos = bbcode.find("[/csharp]");
if (end_pos == -1) {
WARN_PRINT("Unclosed [csharp] block or parse fail in code (search for tag errors)");
@@ -2008,10 +2019,10 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
}
break;
case 1: // C#
- bbcode = bbcode.replace("[csharp]", "[codeblock]");
+ bbcode = bbcode.replace("[csharp", "[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
- for (int pos = bbcode.find("[gdscript]"); pos != -1; pos = bbcode.find("[gdscript]")) {
+ for (int pos = bbcode.find("[gdscript"); pos != -1; pos = bbcode.find("[gdscript")) {
int end_pos = bbcode.find("[/gdscript]");
if (end_pos == -1) {
WARN_PRINT("Unclosed [gdscript] block or parse fail in code (search for tag errors)");
@@ -2025,8 +2036,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
}
break;
case 2: // GDScript and C#
- bbcode = bbcode.replace("[csharp]", "[b]C#:[/b]\n[codeblock]");
- bbcode = bbcode.replace("[gdscript]", "[b]GDScript:[/b]\n[codeblock]");
+ bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock"); // Tag can have extra arguments.
+ bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
@@ -2041,6 +2052,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
// Remove extra new lines around code blocks.
bbcode = bbcode.replace("[codeblock]\n", "[codeblock]");
+ bbcode = bbcode.replace("[codeblock skip-lint]\n", "[codeblock skip-lint]"); // Extra argument to silence validation warnings.
bbcode = bbcode.replace("\n[/codeblock]", "[/codeblock]");
List<String> tag_stack;
@@ -2114,7 +2126,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
p_rt->add_text("[");
pos = brk_pos + 1;
- } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
+ } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
const int tag_end = tag.find(" ");
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -2125,7 +2137,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
p_rt->push_font_size(doc_code_font_size);
Color target_color = link_color;
- if (link_tag == "method") {
+ if (link_tag == "method" || link_tag == "constructor" || link_tag == "operator") {
target_color = link_method_color;
} else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme property") {
target_color = link_property_color;
@@ -2196,7 +2208,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "code") {
+ } else if (tag == "code" || tag.begins_with("code ")) {
// Use monospace font with darkened background color to make code easier to distinguish from other text.
p_rt->push_font(doc_code_font);
p_rt->push_font_size(doc_code_font_size);
@@ -2205,8 +2217,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
- } else if (tag == "codeblock") {
+ tag_stack.push_front("code");
+ } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
// Use monospace font with darkened background color to make code easier to distinguish from other text.
// Use a single-column table with cell row background color instead of `[bgcolor]`.
// This makes the background color highlight cover the entire block, rather than individual lines.
@@ -2221,7 +2233,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
codeblock_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("codeblock");
} else if (tag == "kbd") {
// Use keyboard font with custom color and background color.
p_rt->push_font(doc_kbd_font);
@@ -2587,7 +2599,7 @@ DocTools *EditorHelp::get_doc_data() {
return doc;
}
-//// EditorHelpBit ///
+/// EditorHelpBit ///
void EditorHelpBit::_go_to_help(String p_what) {
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
@@ -2620,6 +2632,179 @@ void EditorHelpBit::_meta_clicked(String p_select) {
}
}
+String EditorHelpBit::get_class_description(const StringName &p_class_name) const {
+ if (doc_class_cache.has(p_class_name)) {
+ return doc_class_cache[p_class_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native class shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ description = is_native ? DTR(E->value.brief_description) : E->value.brief_description;
+
+ if (is_native) {
+ doc_class_cache[p_class_name] = description;
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const {
+ if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
+ return doc_property_cache[p_class_name][p_property_name];
+ }
+
+ String description;
+ // Non-native properties shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ if (E) {
+ for (int i = 0; i < E->value.properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.properties[i].description) : E->value.properties[i].description;
+
+ const Vector<String> class_enum = E->value.properties[i].enumeration.split(".");
+ const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
+ if (!enum_name.is_empty()) {
+ // Classes can use enums from other classes, so check from which it came.
+ HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_enum[0]);
+ if (enum_class) {
+ for (DocData::ConstantDoc val : enum_class->value.constants) {
+ // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
+ if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
+ const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
+ const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " ";
+ const String enum_description = is_native ? DTR(val.description) : val.description;
+
+ // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
+ description_current = description_current.trim_prefix("\n") + vformat("\n[b]%s:[/b] %s", enum_value.trim_prefix(enum_prefix), enum_description.is_empty() ? ("[i]" + DTR("No description available.") + "[/i]") : enum_description);
+ }
+ }
+ }
+ }
+
+ if (E->value.properties[i].name == p_property_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_property_cache[p_class_name][E->value.properties[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const {
+ if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
+ return doc_method_cache[p_class_name][p_method_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native methods shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.methods.size(); i++) {
+ String description_current = is_native ? DTR(E->value.methods[i].description) : E->value.methods[i].description;
+
+ if (E->value.methods[i].name == p_method_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_method_cache[p_class_name][E->value.methods[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const {
+ if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
+ return doc_signal_cache[p_class_name][p_signal_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native signals shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.signals.size(); i++) {
+ String description_current = is_native ? DTR(E->value.signals[i].description) : E->value.signals[i].description;
+
+ if (E->value.signals[i].name == p_signal_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_signal_cache[p_class_name][E->value.signals[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const {
+ if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
+ return doc_theme_item_cache[p_class_name][p_theme_item_name];
+ }
+
+ String description;
+ bool found = false;
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ while (E) {
+ // Non-native theme items shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.theme_properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.theme_properties[i].description) : E->value.theme_properties[i].description;
+
+ if (E->value.theme_properties[i].name == p_theme_item_name) {
+ description = description_current;
+ found = true;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_theme_item_cache[p_class_name][E->value.theme_properties[i].name] = description_current;
+ }
+ }
+
+ if (found || E->value.inherits.is_empty()) {
+ break;
+ }
+ // Check for inherited theme items.
+ E = dd->class_list.find(E->value.inherits);
+ }
+
+ return description;
+}
+
void EditorHelpBit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text);
ADD_SIGNAL(MethodInfo("request_hide"));
@@ -2650,7 +2835,73 @@ EditorHelpBit::EditorHelpBit() {
set_custom_minimum_size(Size2(0, 50 * EDSCALE));
}
-//// FindBar ///
+/// EditorHelpTooltip ///
+
+void EditorHelpTooltip::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ if (!tooltip_text.is_empty()) {
+ parse_tooltip(tooltip_text);
+ }
+ } break;
+ }
+}
+
+// `p_text` is expected to be something like these:
+// - `class|Control||`;
+// - `property|Control|size|`;
+// - `signal|Control|gui_input|(event: InputEvent)`
+void EditorHelpTooltip::parse_tooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ PackedStringArray slices = p_text.split("|", true, 3);
+ ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'.");
+
+ String type = slices[0];
+ String class_name = slices[1];
+ String property_name = slices[2];
+ String property_args = slices[3];
+
+ String title;
+ String description;
+ String formatted_text;
+
+ if (type == "class") {
+ title = class_name;
+ description = get_class_description(class_name);
+ formatted_text = TTR("Class:");
+ } else {
+ title = property_name;
+
+ if (type == "property") {
+ description = get_property_description(class_name, property_name);
+ formatted_text = TTR("Property:");
+ } else if (type == "method") {
+ description = get_method_description(class_name, property_name);
+ formatted_text = TTR("Method:");
+ } else if (type == "signal") {
+ description = get_signal_description(class_name, property_name);
+ formatted_text = TTR("Signal:");
+ } else if (type == "theme_item") {
+ description = get_theme_item_description(class_name, property_name);
+ formatted_text = TTR("Theme Item:");
+ } else {
+ ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'.");
+ }
+ }
+
+ formatted_text += " [u][b]" + title + "[/b][/u]" + property_args + "\n";
+ formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description;
+ set_text(formatted_text);
+}
+
+EditorHelpTooltip::EditorHelpTooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
+}
+
+/// FindBar ///
FindBar::FindBar() {
search_text = memnew(LineEdit);
diff --git a/editor/editor_help.h b/editor/editor_help.h
index 1f1528945b..0ca3942e0b 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -157,7 +157,7 @@ class EditorHelp : public VBoxContainer {
//void _button_pressed(int p_idx);
void _add_type(const String &p_type, const String &p_enum = String(), bool p_is_bitfield = false);
void _add_type_icon(const String &p_type, int p_size = 0, const String &p_fallback = "");
- void _add_method(const DocData::MethodDoc &p_method, bool p_overview = true);
+ void _add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override = true);
void _add_bulletpoint();
@@ -177,7 +177,7 @@ class EditorHelp : public VBoxContainer {
Error _goto_desc(const String &p_class);
//void _update_history_buttons();
- void _update_method_list(const Vector<DocData::MethodDoc> p_methods);
+ void _update_method_list(const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type);
void _update_method_descriptions(const DocData::ClassDoc p_classdoc, const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type);
void _update_doc();
@@ -232,6 +232,12 @@ public:
class EditorHelpBit : public MarginContainer {
GDCLASS(EditorHelpBit, MarginContainer);
+ inline static HashMap<StringName, String> doc_class_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache;
+
RichTextLabel *rich_text = nullptr;
void _go_to_help(String p_what);
void _meta_clicked(String p_select);
@@ -243,9 +249,30 @@ protected:
void _notification(int p_what);
public:
+ String get_class_description(const StringName &p_class_name) const;
+ String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const;
+ String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const;
+ String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const;
+ String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const;
+
RichTextLabel *get_rich_text() { return rich_text; }
void set_text(const String &p_text);
+
EditorHelpBit();
};
+class EditorHelpTooltip : public EditorHelpBit {
+ GDCLASS(EditorHelpTooltip, EditorHelpBit);
+
+ String tooltip_text;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void parse_tooltip(const String &p_text);
+
+ EditorHelpTooltip(const String &p_text = String());
+};
+
#endif // EDITOR_HELP_H
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 382b182e0e..91a3181747 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -905,47 +905,17 @@ void EditorProperty::_update_pin_flags() {
}
}
-static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) {
- // `p_text` is expected to be something like this:
- // `item_name|Item description.`.
- // Note that the description can be empty or contain `|`.
- PackedStringArray slices = p_text.split("|", true, 1);
- if (slices.size() < 2) {
- return nullptr; // Use default tooltip instead.
- }
-
- String item_name = slices[0].strip_edges();
- String item_descr = slices[1].strip_edges();
-
- String text;
- if (!p_item_type.is_empty()) {
- text = p_item_type + " ";
- }
- text += "[u][b]" + item_name + "[/b][/u]\n";
- if (item_descr.is_empty()) {
- text += "[i]" + TTR("No description.") + "[/i]";
- } else {
- text += item_descr;
- }
- if (!p_warning.is_empty()) {
- text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]";
- }
-
- EditorHelpBit *help_bit = memnew(EditorHelpBit);
- help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
- help_bit->set_text(text);
-
- return help_bit;
-}
-
Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
- String warn;
- Color warn_color;
+ EditorHelpTooltip *tooltip = memnew(EditorHelpTooltip(p_text));
+
if (object->has_method("_get_property_warning")) {
- warn = object->call("_get_property_warning", property);
- warn_color = get_theme_color(SNAME("warning_color"));
+ String warn = object->call("_get_property_warning", property);
+ if (!warn.is_empty()) {
+ tooltip->set_text(tooltip->get_rich_text()->get_text() + "\n[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
+ }
}
- return make_help_bit(TTR("Property:"), p_text, warn, warn_color);
+
+ return tooltip;
}
void EditorProperty::menu_option(int p_option) {
@@ -1178,7 +1148,8 @@ void EditorInspectorCategory::_notification(int p_what) {
}
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
- return make_help_bit(TTR("Class:"), p_text, String(), Color());
+ // Far from perfect solution, as there's nothing that prevents a category from having a name that starts with that.
+ return p_text.begins_with("class|") ? memnew(EditorHelpTooltip(p_text)) : nullptr;
}
Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -2883,24 +2854,8 @@ void EditorInspector::update_tree() {
category->doc_class_name = doc_name;
if (use_doc_hints) {
- String descr = "";
- // Sets the category tooltip to show documentation.
- if (!class_descr_cache.has(doc_name)) {
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
- if (E) {
- descr = E->value.brief_description;
- }
- if (ClassDB::class_exists(doc_name)) {
- descr = DTR(descr); // Do not translate the class description of scripts.
- class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts.
- }
- } else {
- descr = class_descr_cache[doc_name];
- }
-
- // `|` separator used in `make_help_bit()` for formatting.
- category->set_tooltip_text(p.name + "|" + descr);
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ category->set_tooltip_text("class|" + doc_name + "||");
}
// Add editors at the start of a category.
@@ -3195,13 +3150,12 @@ void EditorInspector::update_tree() {
restart_request_props.insert(p.name);
}
- PropertyDocInfo doc_info;
+ String doc_path;
+ String theme_item_name;
+ StringName classname = doc_name;
+ // Build the doc hint, to use as tooltip.
if (use_doc_hints) {
- // Build the doc hint, to use as tooltip.
-
- // Get the class name.
- StringName classname = doc_name;
if (!object_class.is_empty()) {
classname = object_class;
} else if (Object::cast_to<MultiNodeEdit>(object)) {
@@ -3231,83 +3185,55 @@ void EditorInspector::update_tree() {
classname = get_edited_object()->get_class();
}
- // Search for the property description in the cache.
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname);
+ // Search for the doc path in the cache.
+ HashMap<StringName, HashMap<StringName, String>>::Iterator E = doc_path_cache.find(classname);
if (E) {
- HashMap<StringName, PropertyDocInfo>::Iterator F = E->value.find(propname);
+ HashMap<StringName, String>::Iterator F = E->value.find(propname);
if (F) {
found = true;
- doc_info = F->value;
+ doc_path = F->value;
}
}
if (!found) {
+ DocTools *dd = EditorHelp::get_doc_data();
+ // Do not cache the doc path information of scripts.
bool is_native_class = ClassDB::class_exists(classname);
- // Build the property description String and add it to the cache.
- DocTools *dd = EditorHelp::get_doc_data();
HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);
- while (F && doc_info.description.is_empty()) {
- for (int i = 0; i < F->value.properties.size(); i++) {
- if (F->value.properties[i].name == propname.operator String()) {
- doc_info.description = F->value.properties[i].description;
- if (is_native_class) {
- doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts.
- }
-
- const Vector<String> class_enum = F->value.properties[i].enumeration.split(".");
- const String class_name = class_enum[0];
- const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
- if (!enum_name.is_empty()) {
- HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_name);
- if (enum_class) {
- for (DocData::ConstantDoc val : enum_class->value.constants) {
- // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
- if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
- const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
- // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
- String desc = val.description;
- if (is_native_class) {
- desc = DTR(desc); // Do not translate the enum value description of scripts.
- }
- desc = desc.trim_prefix("\n");
- doc_info.description += vformat(
- "\n[b]%s:[/b] %s",
- enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
- desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc);
- }
- }
- }
- }
-
- doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
- break;
- }
- }
-
+ while (F) {
Vector<String> slices = propname.operator String().split("/");
+ // Check if it's a theme item first.
if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
for (int i = 0; i < F->value.theme_properties.size(); i++) {
+ String doc_path_current = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
if (F->value.theme_properties[i].name == slices[1]) {
- doc_info.description = F->value.theme_properties[i].description;
- if (is_native_class) {
- doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts.
- }
- doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
- break;
+ doc_path = doc_path_current;
+ theme_item_name = F->value.theme_properties[i].name;
}
}
- }
- if (!F->value.inherits.is_empty()) {
- F = dd->class_list.find(F->value.inherits);
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
} else {
- break;
+ for (int i = 0; i < F->value.properties.size(); i++) {
+ String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
+ if (F->value.properties[i].name == propname.operator String()) {
+ doc_path = doc_path_current;
+ }
+
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
+ }
}
- }
- if (is_native_class) {
- doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
+ if (!doc_path.is_empty() || F->value.inherits.is_empty()) {
+ break;
+ }
+ // Couldn't find the doc path in the class itself, try its super class.
+ F = dd->class_list.find(F->value.inherits);
}
}
}
@@ -3346,11 +3272,11 @@ void EditorInspector::update_tree() {
if (properties.size()) {
if (properties.size() == 1) {
- //since it's one, associate:
+ // Since it's one, associate:
ep->property = properties[0];
ep->property_path = property_prefix + properties[0];
ep->property_usage = p.usage;
- //and set label?
+ // And set label?
}
if (!editors[i].label.is_empty()) {
ep->set_label(editors[i].label);
@@ -3398,9 +3324,17 @@ void EditorInspector::update_tree() {
ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
- // `|` separator used in `make_help_bit()` for formatting.
- ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description);
- ep->set_doc_path(doc_info.path);
+
+ if (use_doc_hints) {
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ if (theme_item_name.is_empty()) {
+ ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|");
+ } else {
+ ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|");
+ }
+ }
+
+ ep->set_doc_path(doc_path);
ep->update_property();
ep->_update_pin_flags();
ep->update_editor_property_status();
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 4393922f52..b5f0cec80b 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -501,13 +501,7 @@ class EditorInspector : public ScrollContainer {
int property_focusable;
int update_scroll_request;
- struct PropertyDocInfo {
- String description;
- String path;
- };
-
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>> doc_info_cache;
- HashMap<StringName, String> class_descr_cache;
+ HashMap<StringName, HashMap<StringName, String>> doc_path_cache;
HashSet<StringName> restart_request_props;
HashMap<ObjectID, int> scroll_cache;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index e69dcb2278..0037b356d0 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4473,7 +4473,7 @@ String EditorNode::_get_system_info() const {
}
if (driver_name == "vulkan") {
driver_name = "Vulkan";
- } else if (driver_name == "opengl3") {
+ } else if (driver_name.begins_with("opengl3")) {
driver_name = "GLES3";
}
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 9c6dbd333f..0be23fa3cc 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -47,6 +47,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "editor/project_settings_editor.h"
#include "editor/property_selector.h"
+#include "editor/scene_tree_dock.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/3d/fog_volume.h"
#include "scene/3d/gpu_particles_3d.h"
@@ -2769,7 +2770,7 @@ EditorPropertyColor::EditorPropertyColor() {
void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
assign->set_disabled(p_read_only);
- clear->set_disabled(p_read_only);
+ menu->set_disabled(p_read_only);
};
Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
@@ -2817,9 +2818,79 @@ void EditorPropertyNodePath::_node_assign() {
scene_tree->popup_scenetree_dialog();
}
-void EditorPropertyNodePath::_node_clear() {
- emit_changed(get_edited_property(), Variant());
- update_property();
+void EditorPropertyNodePath::_update_menu() {
+ const NodePath &np = _get_node_path();
+
+ menu->get_popup()->set_item_disabled(ACTION_CLEAR, np.is_empty());
+ menu->get_popup()->set_item_disabled(ACTION_COPY, np.is_empty());
+
+ Node *edited_node = Object::cast_to<Node>(get_edited_object());
+ menu->get_popup()->set_item_disabled(ACTION_SELECT, !edited_node || !edited_node->has_node(np));
+}
+
+void EditorPropertyNodePath::_menu_option(int p_idx) {
+ switch (p_idx) {
+ case ACTION_CLEAR: {
+ emit_changed(get_edited_property(), NodePath());
+ update_property();
+ } break;
+
+ case ACTION_COPY: {
+ DisplayServer::get_singleton()->clipboard_set(_get_node_path());
+ } break;
+
+ case ACTION_EDIT: {
+ assign->hide();
+ menu->hide();
+
+ const NodePath &np = _get_node_path();
+ edit->set_text(np);
+ edit->show();
+ callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
+ } break;
+
+ case ACTION_SELECT: {
+ const Node *edited_node = get_base_node();
+ ERR_FAIL_NULL(edited_node);
+
+ const NodePath &np = _get_node_path();
+ Node *target_node = edited_node->get_node_or_null(np);
+ ERR_FAIL_NULL(target_node);
+
+ SceneTreeDock::get_singleton()->set_selected(target_node);
+ } break;
+ }
+}
+
+void EditorPropertyNodePath::_accept_text() {
+ _text_submitted(edit->get_text());
+}
+
+void EditorPropertyNodePath::_text_submitted(const String &p_text) {
+ NodePath np = p_text;
+ emit_changed(get_edited_property(), np);
+ edit->hide();
+ assign->show();
+ menu->show();
+}
+
+const NodePath EditorPropertyNodePath::_get_node_path() const {
+ const Node *base_node = const_cast<EditorPropertyNodePath *>(this)->get_base_node();
+
+ Variant val = get_edited_property_value();
+ Node *n = Object::cast_to<Node>(val);
+ if (n) {
+ if (!n->is_inside_tree()) {
+ return NodePath();
+ }
+ if (base_node) {
+ return base_node->get_path_to(n);
+ } else {
+ return get_tree()->get_edited_scene_root()->get_path_to(n);
+ }
+ } else {
+ return val;
+ }
}
bool EditorPropertyNodePath::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
@@ -2865,26 +2936,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const
}
void EditorPropertyNodePath::update_property() {
- Node *base_node = get_base_node();
-
- NodePath p;
- Variant val = get_edited_object()->get(get_edited_property());
- Node *n = Object::cast_to<Node>(val);
- if (n) {
- if (!n->is_inside_tree()) {
- return;
- }
- if (base_node) {
- p = base_node->get_path_to(n);
- } else {
- p = get_tree()->get_edited_scene_root()->get_path_to(n);
- }
- } else {
- p = get_edited_property_value();
- }
-
+ const Node *base_node = get_base_node();
+ const NodePath &p = _get_node_path();
assign->set_tooltip_text(p);
- if (p == NodePath()) {
+
+ if (p.is_empty()) {
assign->set_icon(Ref<Texture2D>());
assign->set_text(TTR("Assign..."));
assign->set_flat(false);
@@ -2898,7 +2954,7 @@ void EditorPropertyNodePath::update_property() {
return;
}
- Node *target_node = base_node->get_node(p);
+ const Node *target_node = base_node->get_node(p);
ERR_FAIL_NULL(target_node);
if (String(target_node->get_name()).contains("@")) {
@@ -2922,14 +2978,15 @@ void EditorPropertyNodePath::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- Ref<Texture2D> t = get_editor_theme_icon(SNAME("Clear"));
- clear->set_icon(t);
+ menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+ menu->get_popup()->set_item_icon(ACTION_CLEAR, get_editor_theme_icon(SNAME("Clear")));
+ menu->get_popup()->set_item_icon(ACTION_COPY, get_editor_theme_icon(SNAME("ActionCopy")));
+ menu->get_popup()->set_item_icon(ACTION_EDIT, get_editor_theme_icon(SNAME("Edit")));
+ menu->get_popup()->set_item_icon(ACTION_SELECT, get_editor_theme_icon(SNAME("ExternalLink")));
} break;
}
}
-void EditorPropertyNodePath::_bind_methods() {
-}
Node *EditorPropertyNodePath::get_base_node() {
if (!base_hint.is_empty() && get_tree()->get_root()->has_node(base_hint)) {
return get_tree()->get_root()->get_node(base_hint);
@@ -2974,12 +3031,23 @@ EditorPropertyNodePath::EditorPropertyNodePath() {
SET_DRAG_FORWARDING_CD(assign, EditorPropertyNodePath);
hbc->add_child(assign);
- clear = memnew(Button);
- clear->set_flat(true);
- clear->connect("pressed", callable_mp(this, &EditorPropertyNodePath::_node_clear));
- hbc->add_child(clear);
-
- scene_tree = nullptr; //do not allocate unnecessarily
+ menu = memnew(MenuButton);
+ menu->set_flat(true);
+ menu->connect(SNAME("about_to_popup"), callable_mp(this, &EditorPropertyNodePath::_update_menu));
+ hbc->add_child(menu);
+
+ menu->get_popup()->add_item(TTR("Clear"), ACTION_CLEAR);
+ menu->get_popup()->add_item(TTR("Copy as Text"), ACTION_COPY);
+ menu->get_popup()->add_item(TTR("Edit"), ACTION_EDIT);
+ menu->get_popup()->add_item(TTR("Show Node in Tree"), ACTION_SELECT);
+ menu->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &EditorPropertyNodePath::_menu_option));
+
+ edit = memnew(LineEdit);
+ edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ edit->hide();
+ edit->connect(SNAME("focus_exited"), callable_mp(this, &EditorPropertyNodePath::_accept_text));
+ edit->connect(SNAME("text_submitted"), callable_mp(this, &EditorPropertyNodePath::_text_submitted));
+ hbc->add_child(edit);
}
///////////////////// RID /////////////////////////
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 5feb40b3d7..ff9d47627a 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -40,6 +40,7 @@ class EditorFileDialog;
class EditorLocaleDialog;
class EditorResourcePicker;
class EditorSpinSlider;
+class MenuButton;
class PropertySelector;
class SceneTreeDialog;
class TextEdit;
@@ -649,8 +650,18 @@ public:
class EditorPropertyNodePath : public EditorProperty {
GDCLASS(EditorPropertyNodePath, EditorProperty);
+
+ enum {
+ ACTION_CLEAR,
+ ACTION_COPY,
+ ACTION_EDIT,
+ ACTION_SELECT,
+ };
+
Button *assign = nullptr;
- Button *clear = nullptr;
+ MenuButton *menu = nullptr;
+ LineEdit *edit = nullptr;
+
SceneTreeDialog *scene_tree = nullptr;
NodePath base_hint;
bool use_path_from_scene_root = false;
@@ -659,8 +670,12 @@ class EditorPropertyNodePath : public EditorProperty {
Vector<StringName> valid_types;
void _node_selected(const NodePath &p_path);
void _node_assign();
- void _node_clear();
Node *get_base_node();
+ void _update_menu();
+ void _menu_option(int p_idx);
+ void _accept_text();
+ void _text_submitted(const String &p_text);
+ const NodePath _get_node_path() const;
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
@@ -670,7 +685,6 @@ class EditorPropertyNodePath : public EditorProperty {
protected:
virtual void _set_read_only(bool p_read_only) override;
- static void _bind_methods();
void _notification(int p_what);
public:
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 950a1e1c4d..3fad85c95c 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -255,7 +255,7 @@ void EditorPropertyArray::update_property() {
array_type_name = vformat("%s[%s]", array_type_name, type_name);
}
- if (array.get_type() == Variant::NIL) {
+ if (!array.is_array()) {
edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
edit->set_pressed(false);
if (container) {
@@ -287,6 +287,7 @@ void EditorPropertyArray::update_property() {
if (!container) {
container = memnew(MarginContainer);
container->set_theme_type_variation("MarginContainer4px");
+ container->set_mouse_filter(MOUSE_FILTER_STOP);
add_child(container);
set_bottom_editor(container);
diff --git a/editor/icons/CodeRegionFoldDownArrow.svg b/editor/icons/CodeRegionFoldDownArrow.svg
index 3bc4f3f73b..744ea7197d 100644
--- a/editor/icons/CodeRegionFoldDownArrow.svg
+++ b/editor/icons/CodeRegionFoldDownArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/editor/icons/CodeRegionFoldedRightArrow.svg b/editor/icons/CodeRegionFoldedRightArrow.svg
index a9b81d54f3..245371b5a1 100644
--- a/editor/icons/CodeRegionFoldedRightArrow.svg
+++ b/editor/icons/CodeRegionFoldedRightArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 7b48e6fbe9..75c8ac11d0 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -112,6 +112,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
last_active = player->is_playing();
+
updating = false;
} break;
@@ -942,11 +943,6 @@ void AnimationPlayerEditor::_update_player() {
onion_toggle->set_disabled(no_anims_found);
onion_skinning->set_disabled(no_anims_found);
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_skinning->set_disabled(true);
- }
-
_update_animation_list_icons();
updating = false;
@@ -1150,33 +1146,33 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay)
float alpha_step = 1.0 / (onion.steps + 1);
- int cidx = 0;
+ uint32_t capture_idx = 0;
if (onion.past) {
- float alpha = 0;
+ float alpha = 0.0f;
do {
alpha += alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < onion.steps);
+ capture_idx++;
+ } while (capture_idx < onion.steps);
}
if (onion.future) {
- float alpha = 1;
- int base_cidx = cidx;
+ float alpha = 1.0f;
+ uint32_t base_cidx = capture_idx;
do {
alpha -= alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
+ capture_idx++;
+ } while (capture_idx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
}
}
@@ -1266,7 +1262,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
if (!p_timeline_only) {
if (player->is_valid() && !p_set) {
- double delta = pos - player->get_current_animation_position();
+ double delta = player->get_current_animation_position();
player->seek(pos, true, true);
player->seek(pos + delta, true, true);
} else {
@@ -1394,7 +1390,10 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.enabled = !onion.enabled;
if (onion.enabled) {
- _start_onion_skinning();
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ EditorNode::get_singleton()->show_warning(TTR("Onion skinning requires a RESET animation."));
+ }
+ _start_onion_skinning(); // It will check for RESET animation anyway.
} else {
_stop_onion_skinning();
}
@@ -1416,7 +1415,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
- menu->set_item_checked(one_frame_idx + i, onion.steps == i + 1);
+ menu->set_item_checked(one_frame_idx + i, (int)onion.steps == i + 1);
}
} break;
case ONION_SKINNING_DIFFERENCES_ONLY: {
@@ -1475,15 +1474,15 @@ void AnimationPlayerEditor::_editor_visibility_changed() {
bool AnimationPlayerEditor::_are_onion_layers_valid() {
ERR_FAIL_COND_V(!onion.past && !onion.future, false);
- Point2 capture_size = get_tree()->get_root()->get_size();
- return onion.captures.size() == onion.get_needed_capture_count() && onion.capture_size == capture_size;
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
+ return onion.captures.size() == onion.get_capture_count() && onion.capture_size == capture_size;
}
void AnimationPlayerEditor::_allocate_onion_layers() {
_free_onion_layers();
- int captures = onion.get_needed_capture_count();
- Point2 capture_size = get_tree()->get_root()->get_size();
+ int captures = onion.get_capture_count();
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
onion.captures.resize(captures);
onion.captures_valid.resize(captures);
@@ -1492,7 +1491,7 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
bool is_present = onion.differences_only && i == captures - 1;
// Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
- onion.captures.write[i] = RS::get_singleton()->viewport_create();
+ onion.captures[i] = RS::get_singleton()->viewport_create();
RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
@@ -1502,13 +1501,13 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
// Reset the capture canvas item to the current root viewport texture (defensive).
RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
- RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), capture_size), get_tree()->get_root()->get_texture()->get_rid());
+ RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), Point2(capture_size.x, -capture_size.y)), get_tree()->get_root()->get_texture()->get_rid());
onion.capture_size = capture_size;
}
void AnimationPlayerEditor::_free_onion_layers() {
- for (int i = 0; i < onion.captures.size(); i++) {
+ for (uint32_t i = 0; i < onion.captures.size(); i++) {
if (onion.captures[i].is_valid()) {
RS::get_singleton()->free(onion.captures[i]);
}
@@ -1524,7 +1523,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
return;
}
- if (!onion.enabled || !is_processing() || !is_visible() || !get_player()) {
+ if (!onion.enabled || !is_visible() || !get_player() || !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
_stop_onion_skinning();
return;
}
@@ -1540,14 +1539,10 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
}
// And go to next step afterwards.
- call_deferred(SNAME("_prepare_onion_layers_2"));
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_prolog).call_deferred();
}
-void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() {
- call_deferred(SNAME("_prepare_onion_layers_1"));
-}
-
-void AnimationPlayerEditor::_prepare_onion_layers_2() {
+void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
if (!anim.is_valid()) {
return;
@@ -1558,21 +1553,20 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Hide superfluous elements that would make the overlay unnecessary cluttered.
- Dictionary canvas_edit_state;
- Dictionary spatial_edit_state;
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- spatial_edit_state = Node3DEditor::get_singleton()->get_state();
- Dictionary new_state = spatial_edit_state.duplicate();
+ onion.temp.spatial_edit_state = Node3DEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.spatial_edit_state.duplicate();
new_state["show_grid"] = false;
new_state["show_origin"] = false;
- Array orig_vp = spatial_edit_state["viewports"];
+ Array orig_vp = onion.temp.spatial_edit_state["viewports"];
Array vp;
vp.resize(4);
for (int i = 0; i < vp.size(); i++) {
Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
d["use_environment"] = false;
d["doppler"] = false;
+ d["listener"] = false;
d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
d["information"] = false;
vp[i] = d;
@@ -1580,23 +1574,27 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
new_state["viewports"] = vp;
// TODO: Save/restore only affected entries.
Node3DEditor::get_singleton()->set_state(new_state);
- } else { // CanvasItemEditor
- // 2D
- canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
- Dictionary new_state = canvas_edit_state.duplicate();
+ } else {
+ // CanvasItemEditor.
+ onion.temp.canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.canvas_edit_state.duplicate();
+ new_state["show_origin"] = false;
new_state["show_grid"] = false;
new_state["show_rulers"] = false;
new_state["show_guides"] = false;
new_state["show_helpers"] = false;
new_state["show_zoom_control"] = false;
+ new_state["show_edit_locks"] = false;
+ new_state["grid_visibility"] = 2; // TODO: Expose CanvasItemEditor::GRID_VISIBILITY_HIDE somehow and use it.
+ new_state["show_transformation_gizmos"] = onion.include_gizmos ? new_state["gizmos"] : Variant(false);
// TODO: Save/restore only affected entries.
CanvasItemEditor::get_singleton()->set_state(new_state);
}
// Tweak the root viewport to ensure it's rendered before our target.
RID root_vp = get_tree()->get_root()->get_viewport_rid();
- Rect2 root_vp_screen_rect = Rect2(Vector2(), get_tree()->get_root()->get_size());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2());
+ onion.temp.screen_rect = Rect2(Vector2(), DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID));
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2(), DisplayServer::INVALID_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
RID present_rid;
@@ -1611,8 +1609,8 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
- Ref<AnimatedValuesBackup> backup_current = player->make_backup();
- float cpos = player->get_current_animation_position();
+ onion.temp.anim_values_backup = player->make_backup();
+ onion.temp.anim_player_position = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@@ -1620,55 +1618,94 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
-
- int step_off_a = onion.past ? -onion.steps : 0;
- int step_off_b = onion.future ? onion.steps : 0;
- int cidx = 0;
onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color")));
- for (int step_off = step_off_a; step_off <= step_off_b; step_off++) {
- if (step_off == 0) {
- // Skip present step and switch to the color of future.
- if (!onion.force_white_modulate) {
- onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
- }
- continue;
- }
- float pos = cpos + step_off * anim->get_step();
+ uint32_t p_capture_idx = 0;
+ int first_step_offset = onion.past ? -(int)onion.steps : 0;
+ _prepare_onion_layers_2_step_prepare(first_step_offset, p_capture_idx);
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx) {
+ uint32_t next_capture_idx = p_capture_idx;
+ if (p_step_offset == 0) {
+ // Skip present step and switch to the color of future.
+ if (!onion.force_white_modulate) {
+ onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
+ }
+ } else {
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+ double pos = onion.temp.anim_player_position + p_step_offset * anim->get_step();
bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
- onion.captures_valid.write[cidx] = valid;
+ onion.captures_valid[p_capture_idx] = valid;
if (valid) {
player->seek(pos, true);
- get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
-
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
- RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
- RS::get_singleton()->draw(false);
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], false);
+ OS::get_singleton()->get_main_loop()->process(0);
+ // This is the key: process the frame and let all callbacks/updates/notifications happen
+ // so everything (transforms, skeletons, etc.) is up-to-date visually.
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).bind(p_step_offset, p_capture_idx).call_deferred();
+ return;
+ } else {
+ next_capture_idx++;
}
+ }
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, next_capture_idx);
+ } else {
+ _prepare_onion_layers_2_epilog();
+ }
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx) {
+ DEV_ASSERT(p_step_offset != 0);
+ DEV_ASSERT(onion.captures_valid[p_capture_idx]);
- cidx++;
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], true);
+ RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[p_capture_idx]);
+ RS::get_singleton()->draw(false);
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], false);
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, p_capture_idx + 1);
+ } else {
+ _prepare_onion_layers_2_epilog();
}
+}
+void AnimationPlayerEditor::_prepare_onion_layers_2_epilog() {
// Restore root viewport.
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, root_vp_screen_rect);
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, onion.temp.screen_rect, DisplayServer::MAIN_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
- // Restore animation state
- // (Seeking with update=true wouldn't do the trick because the current value of the properties
- // may not match their value for the current point in the animation).
- player->seek(cpos, false);
- player->restore(backup_current);
+ // Restore animation state.
+ // Here we're combine the power of seeking back to the original position and
+ // restoring the values backup. In most cases they will bring the same value back,
+ // but there are cases handled by one that the other can't.
+ // Namely:
+ // - Seeking won't restore any values that may have been modified by the user
+ // in the node after the last time the AnimationPlayer updated it.
+ // - Restoring the backup won't account for values that are not directly involved
+ // in the animation but a consequence of them (e.g., SkeletonModification2DLookAt).
+ // FIXME: Since backup of values is based on the reset animation, only values
+ // backed by a proper reset animation will work correctly with onion
+ // skinning and the possibility to restore the values mentioned in the
+ // first point above is gone. Still good enough.
+ player->seek(onion.temp.anim_player_position, true, true);
+ player->restore(onion.temp.anim_values_backup);
// Restore state of main editors.
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- Node3DEditor::get_singleton()->set_state(spatial_edit_state);
+ Node3DEditor::get_singleton()->set_state(onion.temp.spatial_edit_state);
} else { // CanvasItemEditor
// 2D
- CanvasItemEditor::get_singleton()->set_state(canvas_edit_state);
+ CanvasItemEditor::get_singleton()->set_state(onion.temp.canvas_edit_state);
}
// Update viewports with skin layers overlaid for the actual engine loop render.
@@ -1677,21 +1714,26 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
void AnimationPlayerEditor::_start_onion_skinning() {
- // FIXME: Using "process_frame" makes onion layers update one frame behind the current.
- if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ onion.enabled = false;
+ onion_toggle->set_pressed_no_signal(false);
+ return;
+ }
+ if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
}
}
void AnimationPlayerEditor::_stop_onion_skinning() {
- if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
_free_onion_layers();
- // Clean up the overlay.
+ // Clean up.
onion.can_overlay = false;
plugin->update_overlays();
+ onion.temp = {};
}
}
@@ -1773,8 +1815,6 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed);
ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1914,16 +1954,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
hb->add_child(onion_skinning);
- // FIXME: Onion skinning disabled for now as it's broken and triggers fast
- // flickering red/blue modulation (GH-53870).
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
-
- onion_skinning->set_disabled(true);
- onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
- }
-
hb->add_child(memnew(VSeparator));
pin = memnew(Button);
@@ -2013,24 +2043,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
- onion.enabled = false;
- onion.past = true;
- onion.future = false;
- onion.steps = 1;
- onion.differences_only = false;
- onion.force_white_modulate = false;
- onion.include_gizmos = false;
-
- onion.last_frame = 0;
- onion.can_overlay = false;
- onion.capture_size = Size2();
onion.capture.canvas = RS::get_singleton()->canvas_create();
onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
- onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+ onion.capture.material.instantiate();
- onion.capture.shader = Ref<Shader>(memnew(Shader));
+ onion.capture.shader.instantiate();
onion.capture.shader->set_code(R"(
// Animation editor onion skinning shader.
@@ -2047,10 +2066,15 @@ float zero_if_equal(vec4 a, vec4 b) {
void fragment() {
vec4 capture_samp = texture(TEXTURE, UV);
- vec4 present_samp = texture(present, UV);
float bkg_mask = zero_if_equal(capture_samp, bkg_color);
- float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
- diff_mask = min(1.0, diff_mask + float(!differences_only));
+ float diff_mask = 1.0;
+ if (differences_only) {
+ // FIXME: If Y-flips across render target, canvas item, etc. was handled correctly,
+ // this would not be as convoluted in the shader.
+ vec4 capture_samp2 = texture(TEXTURE, vec2(UV.x, 1.0 - UV.y));
+ vec4 present_samp = texture(present, vec2(UV.x, 1.0 - UV.y));
+ diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
+ }
COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
}
)");
@@ -2061,6 +2085,7 @@ AnimationPlayerEditor::~AnimationPlayerEditor() {
_free_onion_layers();
RS::get_singleton()->free(onion.capture.canvas);
RS::get_singleton()->free(onion.capture.canvas_item);
+ onion.capture = {};
}
void AnimationPlayerEditorPlugin::_notification(int p_what) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4763a008fe..6751933839 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -136,20 +136,18 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationTrackEditor *track_editor = nullptr;
static AnimationPlayerEditor *singleton;
- bool hack_disable_onion_skinning = true; // Temporary hack for GH-53870.
-
// Onion skinning.
struct {
// Settings.
bool enabled = false;
- bool past = false;
+ bool past = true;
bool future = false;
- int steps = 0;
+ uint32_t steps = 1;
bool differences_only = false;
bool force_white_modulate = false;
bool include_gizmos = false;
- int get_needed_capture_count() const {
+ uint32_t get_capture_count() const {
// 'Differences only' needs a capture of the present.
return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0);
}
@@ -158,14 +156,23 @@ class AnimationPlayerEditor : public VBoxContainer {
int64_t last_frame = 0;
int can_overlay = 0;
Size2 capture_size;
- Vector<RID> captures;
- Vector<bool> captures_valid;
+ LocalVector<RID> captures;
+ LocalVector<bool> captures_valid;
struct {
RID canvas;
RID canvas_item;
Ref<ShaderMaterial> material;
Ref<Shader> shader;
} capture;
+
+ // Cross-call state.
+ struct {
+ double anim_player_position = 0.0;
+ Ref<AnimatedValuesBackup> anim_values_backup;
+ Rect2 screen_rect;
+ Dictionary canvas_edit_state;
+ Dictionary spatial_edit_state;
+ } temp;
} onion;
void _select_anim_by_name(const String &p_anim);
@@ -215,8 +222,10 @@ class AnimationPlayerEditor : public VBoxContainer {
void _allocate_onion_layers();
void _free_onion_layers();
void _prepare_onion_layers_1();
- void _prepare_onion_layers_1_deferred();
- void _prepare_onion_layers_2();
+ void _prepare_onion_layers_2_prolog();
+ void _prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_epilog();
void _start_onion_skinning();
void _stop_onion_skinning();
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index 5228db03b9..8c0a5b999a 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -370,46 +370,15 @@ void PropertySelector::_item_selected() {
class_type = instance->get_class();
}
- DocTools *dd = EditorHelp::get_doc_data();
String text;
- if (properties) {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.properties.size(); i++) {
- if (E->value.properties[i].name == name) {
- text = DTR(E->value.properties[i].description);
- break;
- }
- }
- }
-
- if (!text.is_empty()) {
- break;
- }
-
- // The property may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
+ while (!class_type.is_empty()) {
+ text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name);
+ if (!text.is_empty()) {
+ break;
}
- } else {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.methods.size(); i++) {
- if (E->value.methods[i].name == name) {
- text = DTR(E->value.methods[i].description);
- break;
- }
- }
- }
- if (!text.is_empty()) {
- break;
- }
-
- // The method may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
- }
+ // It may be from a parent class, keep looking.
+ class_type = ClassDB::get_parent_class(class_type);
}
if (!text.is_empty()) {
diff --git a/main/main.cpp b/main/main.cpp
index 5df44c9ddf..8e217575c1 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1746,9 +1746,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
{
String driver_hints = "";
String driver_hints_angle = "";
+ String driver_hints_egl = "";
#ifdef GLES3_ENABLED
driver_hints = "opengl3";
driver_hints_angle = "opengl3,opengl3_angle";
+ driver_hints_egl = "opengl3,opengl3_es";
#endif
String default_driver = driver_hints.get_slice(",", 0);
@@ -1759,11 +1761,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_RST("rendering/gl_compatibility/driver", default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.windows", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver);
- GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints), default_driver);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints_egl), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.web", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.android", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.ios", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.macos", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver_macos);
+
GLOBAL_DEF_RST("rendering/gl_compatibility/nvidia_disable_threaded_optimization", true);
GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_angle", true);
@@ -1841,7 +1844,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Set a default renderer if none selected. Try to choose one that matches the driver.
if (rendering_method.is_empty()) {
- if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle" || rendering_driver == "opengl3_es") {
rendering_method = "gl_compatibility";
} else {
rendering_method = "forward_plus";
@@ -1860,6 +1863,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (rendering_method == "gl_compatibility") {
available_drivers.push_back("opengl3");
available_drivers.push_back("opengl3_angle");
+ available_drivers.push_back("opengl3_es");
}
#endif
if (available_drivers.is_empty()) {
@@ -2120,7 +2124,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/submit_depth_buffer", false);
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
- // XR project extensions settings.
+ // OpenXR project extensions settings.
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true);
GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
#ifdef TOOLS_ENABLED
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 806ab1e3df..4fec745999 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -601,8 +601,8 @@
@icon("res://path/to/class/icon.svg")
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
- [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance.
- [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported).
+ [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
+ [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">
@@ -653,7 +653,7 @@
@tool
extends Node
[/codeblock]
- [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance.
+ [b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance.
</description>
</annotation>
<annotation name="@warning_ignore" qualifiers="vararg">
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 9bb4fd153b..907511d140 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -206,17 +206,19 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to build.
- using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1);
-
- pr.Step("Building project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1))
+ {
+ pr.Step("Building project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to build project");
- return false;
}
- return true;
+ return success;
}
private static bool CleanProjectBlocking(BuildInfo buildInfo)
@@ -224,32 +226,36 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to clean.
- using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1);
-
- pr.Step("Cleaning project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1))
+ {
+ pr.Step("Cleaning project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to clean project");
- return false;
}
- return true;
+ return success;
}
private static bool PublishProjectBlocking(BuildInfo buildInfo)
{
- using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1);
-
- pr.Step("Running dotnet publish", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1))
+ {
+ pr.Step("Running dotnet publish", 0);
+ success = Publish(buildInfo);
+ }
- if (!Publish(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to publish .NET project");
- return false;
}
- return true;
+ return success;
}
private static BuildInfo CreateBuildInfo(
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 48e654c286..a00c812c79 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -65,6 +65,7 @@ namespace GodotTools
private bool CreateProjectSolution()
{
+ string errorMessage = null;
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
{
pr.Step("Generating C# project...".TTR());
@@ -96,22 +97,23 @@ namespace GodotTools
}
catch (IOException e)
{
- ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message);
- return false;
+ errorMessage = "Failed to save solution. Exception message: ".TTR() + e.Message;
}
-
- pr.Step("Done".TTR());
-
- // Here, after all calls to progress_task_step
- CallDeferred(nameof(_ShowDotnetFeatures));
}
else
{
- ShowErrorDialog("Failed to create C# project.".TTR());
+ errorMessage = "Failed to create C# project.".TTR();
}
+ }
- return true;
+ if (!string.IsNullOrEmpty(errorMessage))
+ {
+ ShowErrorDialog(errorMessage);
+ return false;
}
+
+ _ShowDotnetFeatures();
+ return true;
}
private void _ShowDotnetFeatures()
@@ -161,14 +163,14 @@ namespace GodotTools
{
_errorDialog.Title = title;
_errorDialog.DialogText = message;
- _errorDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_errorDialog);
}
public void ShowConfirmCreateSlnDialog()
{
_confirmCreateSlnDialog.Title = "C# solution already exists. This will override the existing C# project file, any manual changes will be lost.".TTR();
_confirmCreateSlnDialog.DialogText = "Create C# solution".TTR();
- _confirmCreateSlnDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_confirmCreateSlnDialog);
}
private static string _vsCodePath = string.Empty;
@@ -483,11 +485,11 @@ namespace GodotTools
_editorSettings = EditorInterface.Singleton.GetEditorSettings();
_errorDialog = new AcceptDialog();
- editorBaseControl.AddChild(_errorDialog);
+ _errorDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog = new ConfirmationDialog();
+ _confirmCreateSlnDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog.Confirmed += () => CreateProjectSolution();
- editorBaseControl.AddChild(_confirmCreateSlnDialog);
MSBuildPanel = new MSBuildPanel();
MSBuildPanel.BuildStateChanged += BuildStateChanged;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index ccfcf2a87c..36fdda4625 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -264,7 +264,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
} else if (code_tag) {
xml_output.append("[");
pos = brk_pos + 1;
- } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
+ } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
const int tag_end = tag.find(" ");
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -298,6 +298,12 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
if (link_tag == "method") {
_append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts);
+ } else if (link_tag == "constructor") {
+ // TODO: Support constructors?
+ _append_xml_undeclared(xml_output, link_target);
+ } else if (link_tag == "operator") {
+ // TODO: Support operators?
+ _append_xml_undeclared(xml_output, link_target);
} else if (link_tag == "member") {
_append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts);
} else if (link_tag == "signal") {
@@ -401,29 +407,29 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "code") {
+ } else if (tag == "code" || tag.begins_with("code ")) {
xml_output.append("<c>");
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
- } else if (tag == "codeblock") {
+ tag_stack.push_front("code");
+ } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
xml_output.append("<code>");
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("codeblock");
} else if (tag == "codeblocks") {
line_del = true;
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "csharp") {
+ } else if (tag == "csharp" || tag.begins_with("csharp ")) {
xml_output.append("<code>");
line_del = false;
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("csharp");
} else if (tag == "kbd") {
// keyboard combinations are not supported in xml comments
pos = brk_end + 1;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
index 48b47b166a..bf8b2f10dc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
@@ -7,6 +7,8 @@ using System.ComponentModel;
namespace Godot;
+#pragma warning disable CS1734 // XML comment on 'X' has a paramref tag for 'Y', but there is no parameter by that name.
+
partial class AnimationNode
{
/// <inheritdoc cref="BlendInput(int, double, bool, bool, float, FilterAction, bool, bool)"/>
@@ -24,6 +26,44 @@ partial class AnimationNode
}
}
+partial class AnimationPlayer
+{
+ /// <inheritdoc cref="CallbackModeMethod"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationMethodCallMode MethodCallMode
+ {
+ get => (AnimationMethodCallMode)CallbackModeMethod;
+ set => CallbackModeMethod = (AnimationCallbackModeMethod)value;
+ }
+
+ /// <inheritdoc cref="Active"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool PlaybackActive
+ {
+ get => Active;
+ set => Active = value;
+ }
+
+ /// <inheritdoc cref="CallbackModeProcess"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationProcessCallback PlaybackProcessMode
+ {
+ get => (AnimationProcessCallback)CallbackModeProcess;
+ set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
+ }
+}
+
+partial class AnimationTree
+{
+ /// <inheritdoc cref="CallbackModeProcess"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationProcessCallback ProcessCallback
+ {
+ get => (AnimationProcessCallback)CallbackModeProcess;
+ set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
+ }
+}
+
partial class CodeEdit
{
/// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant}, int)"/>
@@ -36,7 +76,7 @@ partial class CodeEdit
partial class Geometry3D
{
- /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Collections.Array{Plane})"/>
+ /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Godot.Collections.Array{Plane})"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static Vector3[] SegmentIntersectsConvex(Vector3 from, Vector3 to, Godot.Collections.Array planes)
{
@@ -44,6 +84,51 @@ partial class Geometry3D
}
}
+partial class GraphEdit
+{
+ /// <inheritdoc cref="ShowArrangeButton"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ArrangeNodesButtonHidden
+ {
+ get => !ShowArrangeButton;
+ set => ShowArrangeButton = !value;
+ }
+
+ /// <inheritdoc cref="GetMenuHBox()"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public HBoxContainer GetZoomHBox()
+ {
+ return GetMenuHBox();
+ }
+
+ /// <inheritdoc cref="SnappingDistance"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SnapDistance
+ {
+ get => SnappingDistance;
+ set => SnappingDistance = value;
+ }
+
+ /// <inheritdoc cref="SnappingEnabled"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool UseSnap
+ {
+ get => SnappingEnabled;
+ set => SnappingEnabled = value;
+ }
+}
+
+partial class GraphNode
+{
+ /// <inheritdoc cref="DeleteRequest"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event Action CloseRequest
+ {
+ add => DeleteRequest += value;
+ remove => DeleteRequest -= value;
+ }
+}
+
partial class MeshInstance3D
{
/// <inheritdoc cref="CreateMultipleConvexCollisions(MeshConvexDecompositionSettings)"/>
@@ -108,6 +193,19 @@ partial class SurfaceTool
}
}
+partial class TileMap
+{
+ /// <summary>
+ /// The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int CellQuadrantSize
+ {
+ get => RenderingQuadrantSize;
+ set => RenderingQuadrantSize = value;
+ }
+}
+
partial class Tree
{
/// <inheritdoc cref="EditSelected(bool)"/>
@@ -127,3 +225,5 @@ partial class UndoRedo
CreateAction(name, mergeMode, backwardUndoOps: false);
}
}
+
+#pragma warning restore CS1734
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index c92b2b08d0..caf97ca2e0 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -32,6 +32,7 @@
#include "../openxr_api.h"
+#include "core/config/project_settings.h"
#include "core/string/print_string.h"
#include "servers/xr_server.h"
@@ -59,7 +60,6 @@ HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions()
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
- request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &hand_tracking_aim_state_ext;
return request_extensions;
}
@@ -106,17 +106,11 @@ void OpenXRHandTrackingExtension::on_state_ready() {
}
// Setup our hands and reset data
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
// we'll do this later
hand_trackers[i].is_initialized = false;
hand_trackers[i].hand_tracker = XR_NULL_HANDLE;
- hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } };
- hand_trackers[i].aimState.pinchStrengthIndex = 0.0;
- hand_trackers[i].aimState.pinchStrengthMiddle = 0.0;
- hand_trackers[i].aimState.pinchStrengthRing = 0.0;
- hand_trackers[i].aimState.pinchStrengthLittle = 0.0;
-
hand_trackers[i].locations.isActive = false;
for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; j++) {
@@ -141,7 +135,7 @@ void OpenXRHandTrackingExtension::on_process() {
XrResult result;
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) {
XrHandTrackerCreateInfoEXT createInfo = {
XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type
@@ -157,18 +151,6 @@ void OpenXRHandTrackingExtension::on_process() {
hand_trackers[i].is_initialized = false;
} else {
void *next_pointer = nullptr;
- if (hand_tracking_aim_state_ext) {
- hand_trackers[i].aimState.type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
- hand_trackers[i].aimState.next = next_pointer;
- hand_trackers[i].aimState.status = 0;
- hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } };
- hand_trackers[i].aimState.pinchStrengthIndex = 0.0;
- hand_trackers[i].aimState.pinchStrengthMiddle = 0.0;
- hand_trackers[i].aimState.pinchStrengthRing = 0.0;
- hand_trackers[i].aimState.pinchStrengthLittle = 0.0;
-
- next_pointer = &hand_trackers[i].aimState;
- }
hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
hand_trackers[i].velocities.next = next_pointer;
@@ -219,20 +201,6 @@ void OpenXRHandTrackingExtension::on_process() {
!hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) {
hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive
}
-
- /* TODO change this to managing the controller from openxr_interface
- if (hand_tracking_aim_state_ext && hand_trackers[i].locations.isActive && check_bit(XR_HAND_TRACKING_AIM_VALID_BIT_FB, hand_trackers[i].aimState.status)) {
- // Controllers are updated based on the aim state's pose and pinches' strength
- if (hand_trackers[i].aim_state_godot_controller == -1) {
- hand_trackers[i].aim_state_godot_controller =
- arvr_api->godot_arvr_add_controller(
- const_cast<char *>(hand_controller_names[i]),
- i + HAND_CONTROLLER_ID_OFFSET,
- true,
- true);
- }
- }
- */
}
}
}
@@ -246,7 +214,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) {
xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker);
@@ -260,25 +228,25 @@ bool OpenXRHandTrackingExtension::get_active() {
return handTrackingSystemProperties.supportsHandTracking;
}
-const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(uint32_t p_hand) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, nullptr);
+const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(HandTrackedHands p_hand) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, nullptr);
return &hand_trackers[p_hand];
}
-XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(uint32_t p_hand) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT);
+XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTrackedHands p_hand) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT);
return hand_trackers[p_hand].motion_range;
}
-void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range) {
- ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS);
+void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range) {
+ ERR_FAIL_UNSIGNED_INDEX(p_hand, OPENXR_MAX_TRACKED_HANDS);
hand_trackers[p_hand].motion_range = p_motion_range;
}
-Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Quaternion());
+Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion());
if (!hand_trackers[p_hand].is_initialized) {
@@ -289,8 +257,8 @@ Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand,
return Quaternion(location.pose.orientation.x, location.pose.orientation.y, location.pose.orientation.z, location.pose.orientation.w);
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
@@ -301,8 +269,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, Xr
return Vector3(location.pose.position.x, location.pose.position.y, location.pose.position.z);
}
-float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, 0.0);
+float OpenXRHandTrackingExtension::get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, 0.0);
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, 0.0);
if (!hand_trackers[p_hand].is_initialized) {
@@ -312,8 +280,8 @@ float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHand
return hand_trackers[p_hand].joint_locations[p_joint].radius;
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
@@ -324,8 +292,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_h
return Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z);
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index 99d315c525..5ca0ff60d3 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -35,10 +35,14 @@
#include "core/math/quaternion.h"
#include "openxr_extension_wrapper.h"
-#define MAX_OPENXR_TRACKED_HANDS 2
-
class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper {
public:
+ enum HandTrackedHands {
+ OPENXR_TRACKED_LEFT_HAND,
+ OPENXR_TRACKED_RIGHT_HAND,
+ OPENXR_MAX_TRACKED_HANDS
+ };
+
struct HandTracker {
bool is_initialized = false;
XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
@@ -47,7 +51,6 @@ public:
XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT];
XrHandJointVelocityEXT joint_velocities[XR_HAND_JOINT_COUNT_EXT];
- XrHandTrackingAimStateFB aimState;
XrHandJointVelocitiesEXT velocities;
XrHandJointLocationsEXT locations;
};
@@ -69,29 +72,28 @@ public:
virtual void on_state_stopping() override;
bool get_active();
- const HandTracker *get_hand_tracker(uint32_t p_hand) const;
+ const HandTracker *get_hand_tracker(HandTrackedHands p_hand) const;
- XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const;
- void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range);
+ XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const;
+ void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range);
- Quaternion get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const;
- float get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
private:
static OpenXRHandTrackingExtension *singleton;
// state
XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties;
- HandTracker hand_trackers[MAX_OPENXR_TRACKED_HANDS]; // Fixed for left and right hand
+ HandTracker hand_trackers[OPENXR_MAX_TRACKED_HANDS]; // Fixed for left and right hand
// related extensions
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
- bool hand_tracking_aim_state_ext = false;
// functions
void cleanup_hand_tracking();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 67a459adb8..d0b01c5771 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -34,8 +34,6 @@
#include "core/io/resource_saver.h"
#include "servers/rendering/rendering_server_globals.h"
-#include "extensions/openxr_hand_tracking_extension.h"
-
#include "extensions/openxr_eye_gaze_interaction.h"
void OpenXRInterface::_bind_methods() {
@@ -909,6 +907,24 @@ RID OpenXRInterface::get_depth_texture() {
}
}
+void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand) {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ OpenXRInterface::Tracker *tracker = find_tracker(p_path);
+ if (tracker && tracker->positional_tracker.is_valid()) {
+ // TODO add in confidence! Requires PR #82715
+
+ Transform3D transform;
+ transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT));
+ transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT);
+ Vector3 linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+ Vector3 angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+
+ tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, XRPose::XR_TRACKING_CONFIDENCE_HIGH);
+ }
+ }
+}
+
void OpenXRInterface::process() {
if (openxr_api) {
// do our normal process
@@ -916,8 +932,8 @@ void OpenXRInterface::process() {
Transform3D t;
Vector3 linear_velocity;
Vector3 angular_velocity;
- XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity);
- if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
+ head_confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity);
+ if (head_confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
// Only update our transform if we have one to update it with
// note that poses are stored without world scale and reference frame applied!
head_transform = t;
@@ -939,14 +955,14 @@ void OpenXRInterface::process() {
handle_tracker(trackers[i]);
}
}
+
+ // Handle hand tracking
+ handle_hand_tracking("/user/hand/left", OpenXRHandTrackingExtension::OPENXR_TRACKED_LEFT_HAND);
+ handle_hand_tracking("/user/hand/right", OpenXRHandTrackingExtension::OPENXR_TRACKED_RIGHT_HAND);
}
if (head.is_valid()) {
- // TODO figure out how to get our velocities
-
- head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity);
-
- // TODO set confidence on pose once we support tracking this..
+ head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence);
}
}
@@ -1143,7 +1159,7 @@ void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange
break;
}
- hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range);
+ hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), xr_motion_range);
}
}
@@ -1152,7 +1168,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand));
+ XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand));
switch (xr_motion_range) {
case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT:
@@ -1170,7 +1186,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_rotation(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Quaternion();
@@ -1179,7 +1195,7 @@ Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_jo
Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_position(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
@@ -1188,7 +1204,7 @@ Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint
float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_radius(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return 0.0;
@@ -1197,7 +1213,7 @@ float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) co
Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_linear_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
@@ -1206,7 +1222,7 @@ Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints
Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_angular_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index a2cc2b27ff..8e24c8dce9 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -33,6 +33,7 @@
#include "action_map/openxr_action_map.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
+#include "extensions/openxr_hand_tracking_extension.h"
#include "openxr_api.h"
#include "servers/xr/xr_interface.h"
@@ -55,6 +56,7 @@ private:
Transform3D head_transform;
Vector3 head_linear_velocity;
Vector3 head_angular_velocity;
+ XRPose::TrackingConfidence head_confidence;
Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet
void _load_action_map();
@@ -97,6 +99,8 @@ private:
void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye);
+ void handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand);
+
protected:
static void _bind_methods();
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 09a064b9a9..544932bdeb 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -105,20 +105,24 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
#endif
// register our other extensions
- if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) {
- OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
- }
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension));
- OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension));
+
+ // register gated extensions
+ if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
+ }
+ if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension));
+ }
}
if (OpenXRAPI::openxr_is_enabled()) {
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index bedc8874d6..c48fac8055 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -113,7 +113,7 @@ void OpenXRHand::_set_motion_range() {
break;
}
- hand_tracking_ext->set_motion_range(hand, xr_motion_range);
+ hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(hand), xr_motion_range);
}
Skeleton3D *OpenXRHand::get_skeleton() {
@@ -204,7 +204,7 @@ void OpenXRHand::_update_skeleton() {
Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT];
Vector3 positions[XR_HAND_JOINT_COUNT_EXT];
- const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand);
+ const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(OpenXRHandTrackingExtension::HandTrackedHands(hand));
const float ws = XRServer::get_singleton()->get_world_scale();
if (hand_tracker->is_initialized && hand_tracker->locations.isActive) {
@@ -243,26 +243,27 @@ void OpenXRHand::_update_skeleton() {
// Get our target quaternion
Quaternion q = quaternions[i];
+ // Get our target position
+ Vector3 p = positions[i];
+
// get local translation, parent should already be processed
if (parent == -1) {
// use our palm location here, that is what we are tracking
q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q;
+ p = inv_quaternions[XR_HAND_JOINT_PALM_EXT].xform(p - positions[XR_HAND_JOINT_PALM_EXT]);
} else {
int found = false;
for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) {
if (bones[b] == parent) {
q = inv_quaternions[b] * q;
+ p = inv_quaternions[b].xform(p - positions[b]);
found = true;
}
}
}
- // And get the movement from our rest position
- // Transform3D rest = skeleton->get_bone_rest(bones[i]);
- // q = rest.basis.get_quaternion().inverse() * q;
-
// and set our pose
- // skeleton->set_bone_pose_position(bones[i], v);
+ skeleton->set_bone_pose_position(bones[i], p);
skeleton->set_bone_pose_rotation(bones[i], q);
}
}
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index e9f55faf7f..cf4354139a 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -142,36 +142,40 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}
-void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) {
+void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
DBusMessageIter arr_iter;
const char *filters_key = "filters";
+ ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size());
+
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() == 2) {
- DBusMessageIter struct_iter;
- DBusMessageIter array_iter;
- DBusMessageIter array_struct_iter;
- dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
- append_dbus_string(&struct_iter, tokens[0]);
-
- dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ for (int i = 0; i < p_filter_names.size(); i++) {
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, p_filter_names[i]);
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ String flt = p_filter_exts[i];
+ int filter_slice_count = flt.get_slice_count(",");
+ for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ String str = (flt.get_slice(",", j).strip_edges());
{
const unsigned nil = 0;
dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil);
}
- append_dbus_string(&array_struct_iter, tokens[1]);
+ append_dbus_string(&array_struct_iter, str);
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
- dbus_message_iter_close_container(&struct_iter, &array_iter);
- dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
dbus_message_iter_close_container(&var_iter, &arr_iter);
dbus_message_iter_close_container(&dict_iter, &var_iter);
@@ -219,7 +223,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}
-bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) {
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
dbus_uint32_t resp_code;
@@ -243,7 +247,22 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
DBusMessageIter var_iter;
dbus_message_iter_recurse(&iter, &var_iter);
- if (strcmp(key, "uris") == 0) {
+ if (strcmp(key, "current_filter") == 0) { // (sa(us))
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter struct_iter;
+ dbus_message_iter_recurse(&var_iter, &struct_iter);
+ while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {
+ const char *value;
+ dbus_message_iter_get_basic(&struct_iter, &value);
+ String name = String::utf8(value);
+
+ r_index = p_names.find(name);
+ if (!dbus_message_iter_next(&struct_iter)) {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(key, "uris") == 0) { // as
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter uri_iter;
dbus_message_iter_recurse(&var_iter, &uri_iter);
@@ -271,6 +290,30 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
return FAILED;
}
+ ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ Vector<String> filter_names;
+ Vector<String> filter_exts;
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ if (!flt.is_empty()) {
+ if (tokens.size() == 2) {
+ filter_exts.push_back(flt);
+ filter_names.push_back(tokens[1]);
+ } else {
+ filter_exts.push_back(flt);
+ filter_names.push_back(flt);
+ }
+ }
+ }
+ }
+ if (filter_names.is_empty()) {
+ filter_exts.push_back("*.*");
+ filter_names.push_back(RTR("All Files"));
+ }
+
DBusError err;
dbus_error_init(&err);
@@ -278,6 +321,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
FileDialogData fd;
fd.callback = p_callback;
fd.prev_focus = p_window_id;
+ fd.filter_names = filter_names;
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
@@ -308,16 +352,10 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
// Generate FileChooser message.
const char *method = nullptr;
- switch (p_mode) {
- case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: {
- method = "SaveFile";
- } break;
- case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: {
- method = "OpenFile";
- } break;
+ if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
+ method = "SaveFile";
+ } else {
+ method = "OpenFile";
}
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);
@@ -334,7 +372,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
append_dbus_dict_string(&arr_iter, "handle_token", token);
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
- append_dbus_dict_filters(&arr_iter, p_filters);
+ append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
@@ -409,13 +447,15 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
- file_chooser_parse_response(&iter, cancel, uris);
+ int index = 0;
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
if (fd.callback.is_valid()) {
Variant v_status = !cancel;
Variant v_files = uris;
- Variant *v_args[2] = { &v_status, &v_files };
- fd.callback.call_deferredp((const Variant **)&v_args, 2);
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ fd.callback.call_deferredp((const Variant **)&v_args, 3);
}
if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index 6ffb3e7b04..503c382207 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -49,12 +49,13 @@ private:
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
- static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters);
+ static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
- static bool file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
struct FileDialogData {
+ Vector<String> filter_names;
DBusConnection *connection = nullptr;
DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
diff --git a/platform/linuxbsd/platform_gl.h b/platform/linuxbsd/platform_gl.h
index 1c19c4518a..5d4c2d8978 100644
--- a/platform/linuxbsd/platform_gl.h
+++ b/platform/linuxbsd/platform_gl.h
@@ -35,6 +35,10 @@
#define GL_API_ENABLED // Allow using desktop GL.
#endif
+#ifndef GLES_API_ENABLED
+#define GLES_API_ENABLED // Allow using GLES.
+#endif
+
#include "thirdparty/glad/glad/egl.h"
#include "thirdparty/glad/glad/gl.h"
diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub
index a4890391ce..bbfaaf10d1 100644
--- a/platform/linuxbsd/x11/SCsub
+++ b/platform/linuxbsd/x11/SCsub
@@ -25,7 +25,9 @@ if env["vulkan"]:
if env["opengl3"]:
env.Append(CPPDEFINES=["GLAD_GLX_NO_X11"])
- source_files.append(["gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"])
+ source_files.append(
+ ["gl_manager_x11_egl.cpp", "gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"]
+ )
objects = []
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 678ec340c0..31846c80a2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1495,6 +1495,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
if (gl_manager) {
gl_manager->window_destroy(p_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(p_id);
+ }
#endif
if (wd.xic) {
@@ -1534,6 +1537,9 @@ int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, Win
if (gl_manager) {
return (int64_t)gl_manager->get_glx_context(p_window);
}
+ if (gl_manager_egl) {
+ return (int64_t)gl_manager_egl->get_context(p_window);
+ }
return 0;
}
#endif
@@ -1711,6 +1717,9 @@ void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_i
if (gl_manager) {
gl_manager->window_make_current(p_window_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_make_current(p_window_id);
+ }
#endif
}
@@ -2012,6 +2021,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
if (gl_manager) {
gl_manager->window_resize(p_window, xwa.width, xwa.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);
+ }
#endif
}
@@ -3721,6 +3733,9 @@ void DisplayServerX11::_window_changed(XEvent *event) {
if (gl_manager) {
gl_manager->window_resize(window_id, wd.size.width, wd.size.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);
+ }
#endif
if (!wd.rect_changed_callback.is_null()) {
@@ -4855,6 +4870,9 @@ void DisplayServerX11::release_rendering_thread() {
if (gl_manager) {
gl_manager->release_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->release_current();
+ }
#endif
}
@@ -4863,6 +4881,9 @@ void DisplayServerX11::make_rendering_thread() {
if (gl_manager) {
gl_manager->make_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->make_current();
+ }
#endif
}
@@ -4871,6 +4892,9 @@ void DisplayServerX11::swap_buffers() {
if (gl_manager) {
gl_manager->swap_buffers();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->swap_buffers();
+ }
#endif
}
@@ -5024,6 +5048,9 @@ void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mo
if (gl_manager) {
gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
+ }
#endif
}
@@ -5038,6 +5065,9 @@ DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_wind
if (gl_manager) {
return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
}
+ if (gl_manager_egl) {
+ return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
+ }
#endif
return DisplayServer::VSYNC_ENABLED;
}
@@ -5050,6 +5080,7 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() {
#endif
#ifdef GLES3_ENABLED
drivers.push_back("opengl3");
+ drivers.push_back("opengl3_es");
#endif
return drivers;
@@ -5092,6 +5123,21 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");
vi_selected = true;
}
+ if (gl_manager_egl) {
+ XVisualInfo visual_info_template;
+ int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);
+ ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");
+
+ visual_info_template.visualid = (VisualID)visual_id;
+
+ int number_of_visuals = 0;
+ XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);
+ ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);
+
+ visualInfo = vi_list[0];
+
+ XFree(vi_list);
+ }
#endif
if (!vi_selected) {
@@ -5364,8 +5410,12 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
if (gl_manager) {
Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");
- window_set_vsync_mode(p_vsync_mode, id);
}
+ if (gl_manager_egl) {
+ Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");
+ }
+ window_set_vsync_mode(p_vsync_mode, id);
#endif
//set_class_hint(x11_display, wd.x11_window);
@@ -5766,7 +5816,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
#endif
// Initialize context and rendering device.
#if defined(GLES3_ENABLED)
- if (rendering_driver == "opengl3") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {
if (getenv("DRI_PRIME") == nullptr) {
int use_prime = -1;
@@ -5807,7 +5857,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
setenv("DRI_PRIME", "1", 1);
}
}
-
+ }
+ if (rendering_driver == "opengl3") {
GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE;
gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type));
@@ -5820,14 +5871,20 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
driver_found = true;
- if (true) {
- RasterizerGLES3::make_current(true);
- } else {
- memdelete(gl_manager);
- gl_manager = nullptr;
+ RasterizerGLES3::make_current(true);
+ }
+ if (rendering_driver == "opengl3_es") {
+ gl_manager_egl = memnew(GLManagerEGL_X11);
+
+ if (gl_manager_egl->initialize() != OK) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
+ driver_found = true;
+
+ RasterizerGLES3::make_current(false);
}
#endif
if (!driver_found) {
@@ -6044,6 +6101,9 @@ DisplayServerX11::~DisplayServerX11() {
if (gl_manager) {
gl_manager->window_destroy(E.key);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(E.key);
+ }
#endif
WindowData &wd = E.value;
@@ -6094,6 +6154,10 @@ DisplayServerX11::~DisplayServerX11() {
memdelete(gl_manager);
gl_manager = nullptr;
}
+ if (gl_manager_egl) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
+ }
#endif
if (xrandr_handle) {
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 71beddce76..9706a4aa11 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -54,6 +54,7 @@
#if defined(GLES3_ENABLED)
#include "x11/gl_manager_x11.h"
+#include "x11/gl_manager_x11_egl.h"
#endif
#if defined(VULKAN_ENABLED)
@@ -138,6 +139,7 @@ class DisplayServerX11 : public DisplayServer {
#if defined(GLES3_ENABLED)
GLManager_X11 *gl_manager = nullptr;
+ GLManagerEGL_X11 *gl_manager_egl = nullptr;
#endif
#if defined(VULKAN_ENABLED)
VulkanContextX11 *context_vulkan = nullptr;
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.cpp b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
new file mode 100644
index 0000000000..544684ebf1
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gl_manager_x11_egl.h"
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include <stdio.h>
+#include <stdlib.h>
+
+const char *GLManagerEGL_X11::_get_platform_extension_name() const {
+ return "EGL_KHR_platform_x11";
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_extension_enum() const {
+ return EGL_PLATFORM_X11_KHR;
+}
+
+Vector<EGLAttrib> GLManagerEGL_X11::_get_platform_display_attributes() const {
+ return Vector<EGLAttrib>();
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_api_enum() const {
+ return EGL_OPENGL_ES_API;
+}
+
+Vector<EGLint> GLManagerEGL_X11::_get_platform_context_attribs() const {
+ Vector<EGLint> ret;
+ ret.push_back(EGL_CONTEXT_CLIENT_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_NONE);
+
+ return ret;
+}
+
+#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.h b/platform/linuxbsd/x11/gl_manager_x11_egl.h
new file mode 100644
index 0000000000..b9c96619c4
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.h
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GL_MANAGER_X11_EGL_H
+#define GL_MANAGER_X11_EGL_H
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include "core/error/error_list.h"
+#include "core/os/os.h"
+#include "core/templates/local_vector.h"
+#include "drivers/egl/egl_manager.h"
+#include "servers/display_server.h"
+
+#include <X11/Xlib.h>
+
+class GLManagerEGL_X11 : public EGLManager {
+private:
+ virtual const char *_get_platform_extension_name() const override;
+ virtual EGLenum _get_platform_extension_enum() const override;
+ virtual EGLenum _get_platform_api_enum() const override;
+ virtual Vector<EGLAttrib> _get_platform_display_attributes() const override;
+ virtual Vector<EGLint> _get_platform_context_attribs() const override;
+
+public:
+ void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {}
+
+ GLManagerEGL_X11(){};
+ ~GLManagerEGL_X11(){};
+};
+
+#endif // X11_ENABLED && GLES3_ENABLED
+
+#endif // GL_MANAGER_X11_EGL_H
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 7dbe6a5970..2ca9e493b7 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -139,7 +139,14 @@ private:
NSMenu *apple_menu = nullptr;
NSMenu *dock_menu = nullptr;
- HashMap<String, NSMenu *> submenu;
+ struct MenuData {
+ Callable open;
+ Callable close;
+ NSMenu *menu = nullptr;
+ bool is_open = false;
+ };
+ HashMap<String, MenuData> submenu;
+ HashMap<NSMenu *, String> submenu_inv;
struct WarpEvent {
NSTimeInterval timestamp;
@@ -197,6 +204,7 @@ private:
const NSMenu *_get_menu_root(const String &p_menu_root) const;
NSMenu *_get_menu_root(const String &p_menu_root);
+ bool _is_menu_opened(NSMenu *p_menu) const;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
void _update_window_style(WindowData p_wd);
@@ -223,6 +231,8 @@ private:
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
+ void menu_open(NSMenu *p_menu);
+ void menu_close(NSMenu *p_menu);
bool has_window(WindowID p_window) const;
WindowData &get_window(WindowID p_window);
@@ -254,6 +264,8 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
@@ -277,6 +289,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
@@ -288,11 +301,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override;
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override;
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override;
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override;
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override;
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index bcd8f5c4e5..67d6f4214f 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -75,7 +75,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
} else {
// Submenu.
if (submenu.has(p_menu_root)) {
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
}
if (menu == apple_menu) {
@@ -99,9 +99,10 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
[n_menu setAutoenablesItems:NO];
[n_menu setDelegate:menu_delegate];
- submenu[p_menu_root] = n_menu;
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
}
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
if (menu == apple_menu) {
// Do not allow to change Apple menu.
@@ -609,6 +610,30 @@ NSMenu *DisplayServerMacOS::get_dock_menu() const {
return dock_menu;
}
+void DisplayServerMacOS::menu_open(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = true;
+ if (md.open.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.open.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
+void DisplayServerMacOS::menu_close(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = false;
+ if (md.close.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.close.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
void DisplayServerMacOS::menu_callback(id p_sender) {
if (![p_sender representedObject]) {
return;
@@ -816,6 +841,24 @@ bool DisplayServerMacOS::_has_help_menu() const {
}
}
+bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
+ if (submenu_inv.has(p_menu)) {
+ const MenuData &md = submenu[submenu_inv[p_menu]];
+ if (md.is_open) {
+ return true;
+ }
+ }
+ for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
+ const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if ([menu_item submenu]) {
+ if (_is_menu_opened([menu_item submenu])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
@@ -999,6 +1042,23 @@ int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_roo
return out;
}
+void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") {
+ // Submenu.
+ if (!submenu.has(p_menu_root)) {
+ NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ [n_menu setAutoenablesItems:NO];
+ [n_menu setDelegate:menu_delegate];
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
+ }
+ submenu[p_menu_root].open = p_open_callback;
+ submenu[p_menu_root].close = p_close_callback;
+ }
+}
+
int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
@@ -1252,13 +1312,9 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- const NSMenu *sub_menu = [menu_item submenu];
- if (sub_menu) {
- for (const KeyValue<String, NSMenu *> &E : submenu) {
- if (E.value == sub_menu) {
- return E.key;
- }
- }
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu && submenu_inv.has(sub_menu)) {
+ return submenu_inv[sub_menu];
}
}
}
@@ -1319,6 +1375,24 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
return false;
}
+bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item isHidden];
+ }
+ }
+ return false;
+}
+
String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
@@ -1498,6 +1572,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
+ obj->hover_callback = p_callback;
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
_THREAD_SAFE_METHOD_
@@ -1557,6 +1650,23 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu && p_submenu.is_empty()) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
+ [menu setSubmenu:nil forItem:menu_item];
+ }
+ return;
+ }
+
NSMenu *sub_menu = _get_menu_root(p_submenu);
if (menu && sub_menu) {
if (sub_menu == menu) {
@@ -1591,9 +1701,13 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
- [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (p_keycode == Key::NONE) {
+ [menu_item setKeyEquivalent:@""];
+ } else {
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
+ String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
+ [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
}
}
}
@@ -1615,6 +1729,23 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setHidden:p_hidden];
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
_THREAD_SAFE_METHOD_
@@ -1742,6 +1873,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeItemAtIndex:p_idx];
}
}
@@ -1751,6 +1887,10 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ if (_is_menu_opened(menu)) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeAllItems];
// Restore Apple menu.
if (menu == [NSApp mainMenu]) {
@@ -1758,6 +1898,7 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
[menu setSubmenu:apple_menu forItem:menu_item];
}
if (submenu.has(p_menu_root)) {
+ submenu_inv.erase(submenu[p_menu_root].menu);
submenu.erase(p_menu_root);
}
}
@@ -1871,179 +2012,275 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect
return OK;
}
-Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- _THREAD_SAFE_METHOD_
+@interface FileDialogDropdown : NSObject {
+ NSSavePanel *dialog;
+ NSMutableArray *allowed_types;
+ int cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types;
+- (void)popupAction:(id)sender;
+- (int)getIndex;
+
+@end
+
+@implementation FileDialogDropdown
+
+- (int)getIndex {
+ return cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types {
+ if ((self = [super init])) {
+ dialog = p_dialog;
+ allowed_types = p_allowed_types;
+ cur_index = 0;
+ }
+ return self;
+}
+
+- (void)popupAction:(id)sender {
+ NSUInteger index = [sender indexOfSelectedItem];
+ if (index < [allowed_types count]) {
+ [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ cur_index = index;
+ } else {
+ [dialog setAllowedFileTypes:@[]];
+ cur_index = -1;
+ }
+}
+
+@end
+
+FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) {
+ NSView *group = [[NSView alloc] initWithFrame:NSZeroRect];
+ group.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
+ label.translatesAutoresizingMaskIntoConstraints = NO;
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+ [group addSubview:label];
+
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ popup.translatesAutoresizingMaskIntoConstraints = NO;
- NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
NSMutableArray *allowed_types = [[NSMutableArray alloc] init];
bool allow_other = false;
for (int i = 0; i < p_filters.size(); i++) {
Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() > 0) {
- if (tokens[0].strip_edges() == "*.*") {
- allow_other = true;
- } else {
- [allowed_types addObject:[NSString stringWithUTF8String:tokens[0].replace("*.", "").strip_edges().utf8().get_data()]];
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
+ allow_other = true;
+ } else if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
+ [allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
}
}
}
+ FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types];
+ popup.target = handler;
+ popup.action = @selector(popupAction:);
- WindowID prev_focus = last_focused_window;
+ [group addSubview:popup];
- Callable callback = p_callback; // Make a copy for async completion handler.
- switch (p_mode) {
- case FILE_DIALOG_MODE_SAVE_FILE: {
- NSSavePanel *panel = [NSSavePanel savePanel];
+ NSView *view = [[NSView alloc] initWithFrame:NSZeroRect];
+ view.translatesAutoresizingMaskIntoConstraints = NO;
+ [view addSubview:group];
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
+ NSMutableArray *constraints = [NSMutableArray array];
+ [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]];
+ [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]];
+ [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]];
+ [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]];
+ [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]];
+ [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]];
+ [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]];
+ [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]];
+ [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]];
+ [NSLayoutConstraint activateConstraints:constraints];
+
+ [p_panel setAllowsOtherFileTypes:allow_other];
+ if ([allowed_types count] > 0) {
+ [p_panel setAccessoryView:view];
+ [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]];
+ }
+
+ return handler;
+}
+
+Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
+ FileDialogDropdown *handler = nullptr;
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ WindowID prev_focus = last_focused_window;
+
+ Callable callback = p_callback; // Make a copy for async completion handler.
+ if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ bool skip = false;
+ for (id bookmark in bookmarks) {
+ NSError *error = nil;
+ BOOL isStale = NO;
+ NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
+ if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ NSError *error = nil;
+ NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ if (!error) {
+ NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ }
+ }
+ // Callback.
+ Vector<String> files;
+ String url;
+ url.parse_utf8([[[panel URL] path] UTF8String]);
+ files.push_back(url);
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
+ } else {
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
+ [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+ [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ NSArray *urls = [(NSOpenPanel *)panel URLs];
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
bool skip = false;
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
skip = true;
break;
}
}
if (!skip) {
NSError *error = nil;
- NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
if (!error) {
- NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ [new_bookmarks addObject:bookmark];
}
}
}
- // Callback.
- Vector<String> files;
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ // Callback.
+ Vector<String> files;
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
String url;
- url.parse_utf8([[[panel URL] path] UTF8String]);
+ url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
files.push_back(url);
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
}
- if (prev_focus != INVALID_WINDOW_ID) {
- callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
}
- }];
- } break;
- case FILE_DIALOG_MODE_OPEN_ANY:
- case FILE_DIALOG_MODE_OPEN_FILE:
- case FILE_DIALOG_MODE_OPEN_FILES:
- case FILE_DIALOG_MODE_OPEN_DIR: {
- NSOpenPanel *panel = [NSOpenPanel openPanel];
-
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
- [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
- [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
-
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- NSArray *urls = [(NSOpenPanel *)panel URLs];
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
- NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- bool skip = false;
- for (id bookmark in bookmarks) {
- NSError *error = nil;
- BOOL isStale = NO;
- NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
- skip = true;
- break;
- }
- }
- if (!skip) {
- NSError *error = nil;
- NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
- if (!error) {
- [new_bookmarks addObject:bookmark];
- }
- }
- }
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
- }
- // Callback.
- Vector<String> files;
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- String url;
- url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
- files.push_back(url);
- }
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- }
- if (prev_focus != INVALID_WINDOW_ID) {
- callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
}
- }];
- } break;
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
}
return OK;
@@ -4188,15 +4425,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
nsappname = [[NSProcessInfo processInfo] processName];
}
+ menu_delegate = [[GodotMenuDelegate alloc] init];
+
// Setup Dock menu.
dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
[dock_menu setAutoenablesItems:NO];
+ [dock_menu setDelegate:menu_delegate];
// Setup Apple menu.
apple_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
[apple_menu setAutoenablesItems:NO];
+ [apple_menu setDelegate:menu_delegate];
[apple_menu addItem:[NSMenuItem separatorItem]];
@@ -4226,8 +4467,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[main_menu setSubmenu:apple_menu forItem:menu_item];
[main_menu setAutoenablesItems:NO];
- menu_delegate = [[GodotMenuDelegate alloc] init];
-
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
rendering_driver = p_rendering_driver;
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 99187fe60e..85fefe65c0 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -66,7 +66,7 @@
Array of the additional command line arguments passed to the code signing tool.
</member>
<member name="codesign/entitlements/address_book" type="bool" setter="" getter="">
- Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [code]privacy/address_book_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url].
+ Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [member privacy/address_book_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url].
</member>
<member name="codesign/entitlements/allow_dyld_environment_variables" type="bool" setter="" getter="">
Allows app to use dynamic linker environment variables to inject code. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables]com.apple.security.cs.allow-dyld-environment-variables[/url].
@@ -115,13 +115,13 @@
Enable to allow app to send Apple events to other apps. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_automation_apple-events]com.apple.security.automation.apple-events[/url].
</member>
<member name="codesign/entitlements/audio_input" type="bool" setter="" getter="">
- Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [code]privacy/microphone_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url].
+ Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [member privacy/microphone_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url].
</member>
<member name="codesign/entitlements/calendars" type="bool" setter="" getter="">
- Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [code]privacy/calendar_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url].
+ Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [member privacy/calendar_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url].
</member>
<member name="codesign/entitlements/camera" type="bool" setter="" getter="">
- Enable if you need to use the camera, if it's enabled you should also provide usage message in the [code]privacy/camera_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url].
+ Enable if you need to use the camera, if it's enabled you should also provide usage message in the [member privacy/camera_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url].
</member>
<member name="codesign/entitlements/custom_file" type="String" setter="" getter="">
Custom entitlements [code].plist[/code] file, if specified the rest of entitlements in the export config are ignored.
@@ -133,10 +133,10 @@
Allows app to load arbitrary libraries and frameworks (not signed with the same Team ID as the main executable or by Apple). Enable it if you are using GDExtension add-ons or ad-hoc signing, or want to support user-provided external add-ons. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation]com.apple.security.cs.disable-library-validation[/url].
</member>
<member name="codesign/entitlements/location" type="bool" setter="" getter="">
- Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [code]privacy/location_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url].
+ Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [member privacy/location_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url].
</member>
<member name="codesign/entitlements/photos_library" type="bool" setter="" getter="">
- Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [code]privacy/photos_library_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url].
+ Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [member privacy/photos_library_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url].
</member>
<member name="codesign/identity" type="String" setter="" getter="">
The "Full Name", "Common Name" or SHA-1 hash of the signing identity used to sign [code].app[/code] bundle.
diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm
index ebfe8b1f6d..1bfcfa7d9e 100644
--- a/platform/macos/godot_menu_delegate.mm
+++ b/platform/macos/godot_menu_delegate.mm
@@ -39,6 +39,34 @@
- (void)doNothing:(id)sender {
}
+- (void)menuNeedsUpdate:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_open(menu);
+ }
+}
+
+- (void)menuDidClose:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_close(menu);
+ }
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
+ if (item) {
+ GodotMenuItem *value = [item representedObject];
+ if (value && value->hover_callback != Callable()) {
+ // If custom callback is set, use it.
+ Variant tag = value->meta;
+ Variant *tagp = &tag;
+ Variant ret;
+ Callable::CallError ce;
+ value->hover_callback.callp((const Variant **)&tagp, 1, ret, ce);
+ }
+ }
+}
+
- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString];
NSUInteger ev_modifiers = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 8876ceae6a..b816ea1b3a 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,6 +46,7 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Callable key_callback;
+ Callable hover_callback;
Variant meta;
GlobalMenuCheckType checkable_type;
int max_states;
diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js
index cbe59230ee..1764c9a026 100644
--- a/platform/web/js/libs/library_godot_javascript_singleton.js
+++ b/platform/web/js/libs/library_godot_javascript_singleton.js
@@ -210,7 +210,7 @@ const GodotJSWrapper = {
// This is safe! JavaScript is single threaded (and using it in threads is not a good idea anyway).
GodotJSWrapper.cb_ret = null;
const args = Array.from(arguments);
- const argsProxy = GodotJSWrapper.MyProxy(args);
+ const argsProxy = new GodotJSWrapper.MyProxy(args);
func(p_ref, argsProxy.get_id(), args.length);
argsProxy.unref();
const ret = GodotJSWrapper.cb_ret;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index ed52c5eb92..ded80ba5f1 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -222,18 +222,37 @@ void DisplayServerWindows::tts_stop() {
Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
_THREAD_SAFE_METHOD_
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
Vector<Char16String> filter_names;
Vector<Char16String> filter_exts;
for (const String &E : p_filters) {
Vector<String> tokens = E.split(";");
- if (tokens.size() == 2) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[1].strip_edges().utf16());
- } else if (tokens.size() == 1) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[0].strip_edges().utf16());
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+ Vector<String> exts;
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ exts.push_back(str);
+ }
+ }
+ if (!exts.is_empty()) {
+ String str = String(";").join(exts);
+ filter_exts.push_back(str.utf16());
+ if (tokens.size() == 2) {
+ filter_names.push_back(tokens[1].strip_edges().utf16());
+ } else {
+ filter_names.push_back(str.utf16());
+ }
+ }
}
}
+ if (filter_names.is_empty()) {
+ filter_exts.push_back(String("*.*").utf16());
+ filter_names.push_back(RTR("All Files").utf16());
+ }
Vector<COMDLG_FILTERSPEC> filters;
for (int i = 0; i < filter_names.size(); i++) {
@@ -287,6 +306,9 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
}
hr = pfd->Show(windows[window_id].hWnd);
+ UINT index = 0;
+ pfd->GetFileTypeIndex(&index);
+
if (SUCCEEDED(hr)) {
Vector<String> file_names;
@@ -326,19 +348,21 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
if (!p_callback.is_null()) {
Variant v_status = true;
Variant v_files = file_names;
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
} else {
if (!p_callback.is_null()) {
Variant v_status = false;
Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
}
pfd->Release();
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 19063981bf..fa94712396 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -447,9 +447,12 @@ void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
lock_callback();
set_block_transform_notify(true); // don't want notify (would feedback loop)
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_block_transform_notify(false); // want it back
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 973d1cde58..a5f5ae6e61 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -502,9 +502,12 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
lock_callback();
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
@@ -2935,9 +2938,12 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
}
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index e1455f7a67..be05273a09 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -86,7 +86,7 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
if (p_ok) {
if (p_files.size() > 0) {
String f = p_files[0];
@@ -103,6 +103,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
}
file->set_text(f);
dir->set_text(f.get_base_dir());
+ _filter_selected(p_filter);
}
} else {
file->set_text("");
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 1a87b79fdd..8ae84fc9dc 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -159,7 +159,7 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
bool _is_open_should_be_disabled();
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 13a42d0407..371d6c69af 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -202,52 +202,6 @@ void MenuBar::_popup_visibility_changed(bool p_visible) {
}
}
-void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
- int count = p_child->get_item_count();
- global_menus.insert(p_menu_name);
- for (int i = 0; i < count; i++) {
- if (p_child->is_item_separator(i)) {
- DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name);
- } else if (!p_child->get_item_submenu(i).is_empty()) {
- Node *n = p_child->get_node_or_null(p_child->get_item_submenu(i));
- ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + p_child->get_item_submenu(i) + "'.");
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- ERR_FAIL_NULL_MSG(pm, "Item subnode is not a PopupMenu: '" + p_child->get_item_submenu(i) + "'.");
-
- DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, atr(p_child->get_item_text(i)), p_menu_name + "/" + itos(i));
- _update_submenu(p_menu_name + "/" + itos(i), pm);
- } else {
- int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, atr(p_child->get_item_text(i)), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i);
-
- if (p_child->is_item_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
- }
- if (p_child->is_item_radio_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true);
- }
- DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i));
- DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i));
- DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i));
- DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i));
- DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i));
- DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i));
- DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i));
- if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) {
- Array events = p_child->get_item_shortcut(i)->get_events();
- for (int j = 0; j < events.size(); j++) {
- Ref<InputEventKey> ie = events[j];
- if (ie.is_valid()) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers());
- break;
- }
- }
- } else if (p_child->get_item_accelerator(i) != Key::NONE) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i));
- }
- }
- }
-}
-
bool MenuBar::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -258,52 +212,67 @@ bool MenuBar::is_native_menu() const {
return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native);
}
-void MenuBar::_clear_menu() {
+String MenuBar::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
- return;
+ return String();
}
- // Remove root menu items.
- int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main");
- for (int i = count - 1; i >= 0; i--) {
- if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", i);
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound.
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__MenuBar#" + itos(get_instance_id());
+
+ int global_start_idx = -1;
+ int count = ds->global_menu_get_item_count("_main");
+ String prev_tag;
+ for (int i = 0; i < count; i++) {
+ String tag = ds->global_menu_get_item_tag("_main", i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ if (i >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- // Erase submenu contents.
- for (const String &E : global_menus) {
- DisplayServer::get_singleton()->global_menu_clear(E);
+ if (global_start_idx == -1) {
+ global_start_idx = count;
}
- global_menus.clear();
-}
-void MenuBar::_update_menu() {
- _clear_menu();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ String submenu_name = popups[i]->bind_global_menu();
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
+
+ return global_menu_name;
+}
- if (!is_visible_in_tree()) {
+void MenuBar::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
return;
}
- int index = start_index;
- if (is_native_menu()) {
- Vector<PopupMenu *> popups = _get_popups();
- String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">";
- for (int i = 0; i < popups.size(); i++) {
- if (menu_cache[i].hidden) {
- continue;
- }
- String menu_name = atr(String(popups[i]->get_meta("_menu_name", popups[i]->get_name())));
-
- index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index);
- if (menu_cache[i].disabled) {
- DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true);
- }
- _update_submenu(root_name + "/" + itos(i), popups[i]);
- index++;
- }
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int global_start = _find_global_start_index();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = menu_cache.size() - 1; i >= 0; i--) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
}
- update_minimum_size();
- queue_redraw();
+
+ global_menu_name = String();
}
void MenuBar::_notification(int p_what) {
@@ -312,25 +281,43 @@ void MenuBar::_notification(int p_what) {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
+ if (is_native_menu()) {
+ bind_global_menu();
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
- _clear_menu();
+ unbind_global_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
selected_menu = -1;
queue_redraw();
} break;
- case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
+ }
+ } break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
- _update_menu();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- _update_menu();
+ if (is_native_menu()) {
+ if (is_visible_in_tree()) {
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
+ }
+ }
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
@@ -512,14 +499,20 @@ void MenuBar::shape(Menu &p_menu) {
}
void MenuBar::_refresh_menu_names() {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
}
}
- _update_menu();
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
@@ -560,11 +553,14 @@ void MenuBar::add_child_notify(Node *p_child) {
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
}
void MenuBar::move_child_notify(Node *p_child) {
@@ -586,9 +582,20 @@ void MenuBar::move_child_notify(Node *p_child) {
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
- menu_cache.insert(get_menu_idx_from_control(pm), menu);
+ int new_idx = get_menu_idx_from_control(pm);
+ menu_cache.insert(new_idx, menu);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
+ }
}
void MenuBar::remove_child_notify(Node *p_child) {
@@ -603,15 +610,17 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
+ if (!global_menu_name.is_empty()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
+
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
-
- _update_menu();
}
void MenuBar::_bind_methods() {
@@ -699,7 +708,8 @@ void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -710,7 +720,8 @@ Control::TextDirection MenuBar::get_text_direction() const {
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -732,7 +743,10 @@ bool MenuBar::is_flat() const {
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ unbind_global_menu();
+ bind_global_menu();
+ }
}
}
@@ -742,11 +756,12 @@ int MenuBar::get_start_index() const {
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (is_native != p_enabled) {
+ is_native = p_enabled;
if (is_native) {
- _clear_menu();
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
}
- is_native = p_enabled;
- _update_menu();
}
}
@@ -790,7 +805,9 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
+ }
}
String MenuBar::get_menu_title(int p_menu) const {
@@ -802,7 +819,10 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
- menu_cache.write[p_menu].name = p_tooltip;
+ menu_cache.write[p_menu].tooltip = p_tooltip;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip);
+ }
}
String MenuBar::get_menu_tooltip(int p_menu) const {
@@ -813,7 +833,9 @@ String MenuBar::get_menu_tooltip(int p_menu) const {
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled);
+ }
}
bool MenuBar::is_menu_disabled(int p_menu) const {
@@ -824,7 +846,9 @@ bool MenuBar::is_menu_disabled(int p_menu) const {
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
+ }
}
bool MenuBar::is_menu_hidden(int p_menu) const {
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 4d6e76d9b6..ba4df5f229 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -66,7 +66,6 @@ class MenuBar : public Control {
}
};
Vector<Menu> menu_cache;
- HashSet<String> global_menus;
int focused_menu = -1;
int selected_menu = -1;
@@ -114,9 +113,23 @@ class MenuBar : public Control {
void _open_popup(int p_index, bool p_focus_item = false);
void _popup_visibility_changed(bool p_visible);
- void _update_submenu(const String &p_menu_name, PopupMenu *p_child);
- void _clear_menu();
- void _update_menu();
+
+ String global_menu_name;
+
+ int _find_global_start_index() {
+ if (global_menu_name.is_empty()) {
+ return -1;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int count = ds->global_menu_get_item_count("_main");
+ for (int i = 0; i < count; i++) {
+ if (ds->global_menu_get_item_tag("_main", i).operator String().begins_with(global_menu_name)) {
+ return i;
+ }
+ }
+ return -1;
+ }
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
@@ -130,6 +143,9 @@ protected:
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 54fd8b8745..e3b0a18325 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,86 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+String PopupMenu::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
+ if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
+ return String();
+ }
+
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (item.separator) {
+ ds->global_menu_add_separator(global_menu_name);
+ } else {
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ item.submenu_bound = true;
+ }
+ }
+ if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) {
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) {
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+ ds->global_menu_set_item_checked(global_menu_name, index, item.checked);
+ ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled);
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent);
+ ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ } else if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+ }
+ return global_menu_name;
+}
+
+void PopupMenu::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
+ return;
+ }
+
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+
+ global_menu_name = String();
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -821,11 +901,17 @@ void PopupMenu::_menu_changed() {
void PopupMenu::add_child_notify(Node *p_child) {
Window::add_child_notify(p_child);
- PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
- if (!pm) {
- return;
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(node_name));
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name);
+ items.write[i].submenu_bound = true;
+ }
+ }
}
- p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
@@ -836,7 +922,16 @@ void PopupMenu::remove_child_notify(Node *p_child) {
if (!pm) {
return;
}
- p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String());
+ items.write[i].submenu_bound = false;
+ }
+ }
+ pm->unbind_global_menu();
+ }
_menu_changed();
}
@@ -857,9 +952,15 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
for (int i = 0; i < items.size(); i++) {
- items.write[i].xl_text = atr(items[i].text);
- items.write[i].dirty = true;
+ Item &item = items.write[i];
+ item.xl_text = atr(item.text);
+ item.dirty = true;
+ if (is_global) {
+ ds->global_menu_set_item_text(global_menu_name, i, item.xl_text);
+ }
_shape_item(i);
}
@@ -1031,6 +1132,14 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1045,6 +1154,15 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1059,10 +1177,20 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1073,10 +1201,22 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
+ _menu_changed();
}
void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
@@ -1085,10 +1225,20 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1099,10 +1249,21 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1113,11 +1274,22 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.state = p_default_state;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
+ notify_property_list_changed();
}
#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
@@ -1135,10 +1307,26 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1148,10 +1336,27 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1161,10 +1366,27 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1175,10 +1397,28 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1188,10 +1428,27 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1202,10 +1459,28 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1217,10 +1492,22 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.submenu = p_submenu;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu)); // Find first menu with this name.
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ items.write[index].submenu_bound = true;
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1240,6 +1527,10 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].dirty = true;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text);
+ }
_shape_item(p_idx);
control->queue_redraw();
@@ -1284,6 +1575,10 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
items.write[p_idx].icon = p_icon;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1332,6 +1627,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
items.write[p_idx].checked = p_checked;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1349,6 +1648,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
items.write[p_idx].id = p_id;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1367,6 +1670,10 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1383,7 +1690,6 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
}
items.write[p_idx].metadata = p_meta;
- control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@@ -1399,6 +1705,11 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1414,7 +1725,30 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
return;
}
+ if (!global_menu_name.is_empty()) {
+ if (items[p_idx].submenu_bound) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String());
+ pm->unbind_global_menu();
+ }
+ items.write[p_idx].submenu_bound = false;
+ }
+ }
+
items.write[p_idx].submenu = p_submenu;
+
+ if (!global_menu_name.is_empty()) {
+ if (!items[p_idx].submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name);
+ items.write[p_idx].submenu_bound = true;
+ }
+ }
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1423,6 +1757,11 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checked = !items[p_idx].checked;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1569,6 +1908,11 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
}
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1585,6 +1929,11 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
}
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1600,6 +1949,11 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1625,6 +1979,21 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
_ref_shortcut(items[p_idx].shortcut);
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1640,6 +2009,10 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) {
}
items.write[p_idx].indent = p_indent;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1656,6 +2029,11 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
}
items.write[p_idx].state = p_state;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1671,6 +2049,22 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].shortcut_is_disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1686,6 +2080,10 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
items.write[p_idx].state = 0;
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1739,11 +2137,23 @@ void PopupMenu::set_item_count(int p_count) {
return;
}
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+
+ if (is_global && prev_size > p_count) {
+ for (int i = prev_size - 1; i >= p_count; i--) {
+ ds->global_menu_remove_item(global_menu_name, i);
+ }
+ }
+
items.resize(p_count);
if (prev_size < p_count) {
for (int i = prev_size; i < p_count; i++) {
items.write[i].id = i;
+ if (is_global) {
+ ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ }
}
}
@@ -1828,6 +2238,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
return false;
}
+void PopupMenu::_about_to_popup() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("about_to_popup"));
+}
+
+void PopupMenu::_about_to_close() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("popup_hide"));
+}
+
void PopupMenu::activate_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
ERR_FAIL_COND(items[p_idx].separator);
@@ -1890,6 +2310,11 @@ void PopupMenu::remove_item(int p_idx) {
}
items.remove_at(p_idx);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1904,6 +2329,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
sep.xl_text = atr(p_text);
}
items.push_back(sep);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1922,7 +2352,22 @@ void PopupMenu::clear(bool p_free_submenus) {
}
}
}
+
+ if (!global_menu_name.is_empty()) {
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+ }
items.clear();
+
mouse_over = -1;
control->queue_redraw();
child_controls_changed();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index f123d08400..5d5f4a8322 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -75,6 +75,7 @@ class PopupMenu : public Popup {
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
bool allow_echo = false;
+ bool submenu_bound = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
@@ -88,6 +89,8 @@ class PopupMenu : public Popup {
}
};
+ String global_menu_name;
+
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -213,6 +216,9 @@ public:
virtual void _parent_focused() override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
@@ -293,6 +299,9 @@ public:
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
void activate_item(int p_idx);
+ void _about_to_popup();
+ void _about_to_close();
+
void remove_item(int p_idx);
void add_separator(const String &p_text = String(), int p_id = -1);
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index 1483d29384..ddec5e826b 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -498,8 +498,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("executing_line", "CodeEdit", icons["arrow_right"]);
theme->set_icon("can_fold", "CodeEdit", icons["arrow_down"]);
theme->set_icon("folded", "CodeEdit", icons["arrow_right"]);
- theme->set_icon("can_fold_code_region", "CodeEdit", icons["folder_down_arrow"]);
- theme->set_icon("folded_code_region", "CodeEdit", icons["folder_right_arrow"]);
+ theme->set_icon("can_fold_code_region", "CodeEdit", icons["region_unfolded"]);
+ theme->set_icon("folded_code_region", "CodeEdit", icons["region_folded"]);
theme->set_icon("folded_eol_icon", "CodeEdit", icons["text_edit_ellipsis"]);
theme->set_font("font", "CodeEdit", Ref<Font>());
diff --git a/scene/theme/icons/folder_down_arrow.svg b/scene/theme/icons/folder_down_arrow.svg
deleted file mode 100644
index 3bc4f3f73b..0000000000
--- a/scene/theme/icons/folder_down_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/folder_right_arrow.svg b/scene/theme/icons/folder_right_arrow.svg
deleted file mode 100644
index a9b81d54f3..0000000000
--- a/scene/theme/icons/folder_right_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/region_folded.svg b/scene/theme/icons/region_folded.svg
new file mode 100644
index 0000000000..245371b5a1
--- /dev/null
+++ b/scene/theme/icons/region_folded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/scene/theme/icons/region_unfolded.svg b/scene/theme/icons/region_unfolded.svg
new file mode 100644
index 0000000000..744ea7197d
--- /dev/null
+++ b/scene/theme/icons/region_unfolded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index c8516a0966..6459cc7462 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -82,6 +82,10 @@ int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, co
return -1;
}
+void DisplayServer::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callbacs, const Callable &p_close_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
@@ -106,6 +110,10 @@ void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -160,6 +168,11 @@ bool DisplayServer::global_menu_is_item_disabled(const String &p_menu_root, int
return false;
}
+bool DisplayServer::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ WARN_PRINT("Global menus not supported by this display server.");
+ return false;
+}
+
String DisplayServer::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
WARN_PRINT("Global menus not supported by this display server.");
return String();
@@ -217,6 +230,10 @@ void DisplayServer::global_menu_set_item_disabled(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -581,6 +598,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);
+ ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
@@ -604,6 +622,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_get_item_accelerator", "menu_root", "idx"), &DisplayServer::global_menu_get_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_is_item_disabled", "menu_root", "idx"), &DisplayServer::global_menu_is_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_is_item_hidden", "menu_root", "idx"), &DisplayServer::global_menu_is_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_get_item_tooltip", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state);
ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states);
@@ -614,12 +633,14 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_radio_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_radio_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hover_callbacks", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_hover_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_set_item_key_callback", "menu_root", "idx", "key_callback"), &DisplayServer::global_menu_set_item_key_callback);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag);
ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text);
ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_set_item_accelerator", "menu_root", "idx", "keycode"), &DisplayServer::global_menu_set_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_set_item_disabled", "menu_root", "idx", "disabled"), &DisplayServer::global_menu_set_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hidden", "menu_root", "idx", "hidden"), &DisplayServer::global_menu_set_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tooltip", "menu_root", "idx", "tooltip"), &DisplayServer::global_menu_set_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state);
ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states);
diff --git a/servers/display_server.h b/servers/display_server.h
index 71bfd7b607..d2e112d224 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -130,6 +130,8 @@ public:
virtual bool has_feature(Feature p_feature) const = 0;
virtual String get_name() const = 0;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable());
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
@@ -153,6 +155,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const;
@@ -164,11 +167,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback);
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode);
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled);
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden);
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip);
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state);
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states);
@@ -501,7 +506,8 @@ public:
FILE_DIALOG_MODE_OPEN_FILES,
FILE_DIALOG_MODE_OPEN_DIR,
FILE_DIALOG_MODE_OPEN_ANY,
- FILE_DIALOG_MODE_SAVE_FILE
+ FILE_DIALOG_MODE_SAVE_FILE,
+ FILE_DIALOG_MODE_SAVE_MAX
};
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 7eb8cbd02f..b9bda9329e 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -103,8 +103,9 @@ void RendererCompositorRD::begin_frame(double frame_step) {
}
void RendererCompositorRD::end_frame(bool p_swap_buffers) {
- // TODO: Likely pass a bool to swap buffers to avoid display?
- RD::get_singleton()->swap_buffers();
+ if (p_swap_buffers) {
+ RD::get_singleton()->swap_buffers();
+ }
}
void RendererCompositorRD::initialize() {
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index 86631ae9d9..e8e0d2e3eb 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -616,7 +616,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
}
}
-void RendererViewport::draw_viewports() {
+void RendererViewport::draw_viewports(bool p_swap_buffers) {
timestamp_vp_map.clear();
// get our xr interface in case we need it
@@ -732,7 +732,7 @@ void RendererViewport::draw_viewports() {
// commit our eyes
Vector<BlitToScreen> blits = xr_interface->post_draw_viewport(vp->render_target, vp->viewport_to_screen_rect);
if (vp->viewport_to_screen != DisplayServer::INVALID_WINDOW_ID) {
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
if (blits.size() > 0) {
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blits.ptr(), blits.size());
}
@@ -771,7 +771,7 @@ void RendererViewport::draw_viewports() {
blit_to_screen_list[vp->viewport_to_screen] = Vector<BlitToScreen>();
}
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
Vector<BlitToScreen> blit_to_screen_vec;
blit_to_screen_vec.push_back(blit);
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blit_to_screen_vec.ptr(), 1);
@@ -799,11 +799,14 @@ void RendererViewport::draw_viewports() {
total_draw_calls_used = draw_calls_used;
RENDER_TIMESTAMP("< Render Viewports");
- //this needs to be called to make screen swapping more efficient
- RSG::rasterizer->prepare_for_blitting_render_targets();
- for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
- RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ if (p_swap_buffers) {
+ //this needs to be called to make screen swapping more efficient
+ RSG::rasterizer->prepare_for_blitting_render_targets();
+
+ for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
+ RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ }
}
}
diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h
index 44de6d8804..a0ec9e6318 100644
--- a/servers/rendering/renderer_viewport.h
+++ b/servers/rendering/renderer_viewport.h
@@ -299,7 +299,7 @@ public:
void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time);
void set_default_clear_color(const Color &p_color);
- void draw_viewports();
+ void draw_viewports(bool p_swap_buffers);
bool free(RID p_rid);
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b13d33de9e..2c8265b7d7 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -88,10 +88,10 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
RSG::scene->render_probes();
- RSG::viewport->draw_viewports();
+ RSG::viewport->draw_viewports(p_swap_buffers);
RSG::canvas_render->update();
- if (OS::get_singleton()->get_current_rendering_driver_name() != "opengl3" && OS::get_singleton()->get_current_rendering_driver_name() != "opengl3_angle") {
+ if (!OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
// Already called for gl_compatibility renderer.
RSG::rasterizer->end_frame(p_swap_buffers);
}