diff options
233 files changed, 1819 insertions, 1064 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c28692c34f..1abd76b2ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,6 +135,29 @@ If your pull request modifies parts of the code in a non-obvious way, make sure to add comments in the code as well. This helps other people understand the change without having to look at `git blame`. +### Write unit tests + +When fixing a bug or contributing a new feature, we recommend including unit +tests in the same commit as the rest of the pull request. Unit tests are pieces +of code that compare the output to a predetermined *expected result* to detect +regressions. Tests are compiled and run on GitHub Actions for every commit and +pull request. + +Pull requests that include tests are more likely to be merged, since we can have +greater confidence in them not being the target of regressions in the future. + +For bugs, the unit tests should cover the functionality that was previously +broken. If done well, this ensures regressions won't appear in the future +again. For new features, the unit tests should cover the newly added +functionality, testing both the "success" and "expected failure" cases if +applicable. + +Feel free to contribute standalone pull requests to add new tests or improve +existing tests as well. + +See [Unit testing](https://docs.godotengine.org/en/latest/development/cpp/unit_testing.html) +for information on writing tests in Godot's C++ codebase. + ### Be nice to the Git history Try to make simple PRs that handle one specific topic. Just like for reporting diff --git a/core/func_ref.cpp b/core/func_ref.cpp index 893f7f9103..7e062f16d0 100644 --- a/core/func_ref.cpp +++ b/core/func_ref.cpp @@ -92,7 +92,10 @@ void FuncRef::_bind_methods() { ClassDB::bind_method(D_METHOD("call_funcv", "arg_array"), &FuncRef::call_funcv); ClassDB::bind_method(D_METHOD("set_instance", "instance"), &FuncRef::set_instance); + ClassDB::bind_method(D_METHOD("is_valid"), &FuncRef::is_valid); + ClassDB::bind_method(D_METHOD("set_function", "name"), &FuncRef::set_function); ClassDB::bind_method(D_METHOD("get_function"), &FuncRef::get_function); - ClassDB::bind_method(D_METHOD("is_valid"), &FuncRef::is_valid); + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "function"), "set_function", "get_function"); } diff --git a/core/ustring.cpp b/core/ustring.cpp index d5afbc2b47..0033c31e20 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -4484,11 +4484,12 @@ String String::sprintf(const Array &values, bool *error) const { int number_len = str.length(); // Padding. + int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; String pad_char = pad_with_zeroes ? String("0") : String(" "); if (left_justified) { - str = str.rpad(min_chars, pad_char); + str = str.rpad(pad_chars_count, pad_char); } else { - str = str.lpad(min_chars, pad_char); + str = str.lpad(pad_chars_count, pad_char); } // Sign. diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index faa380ff33..c547563a6e 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -10,6 +10,8 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="Advanced vector math">https://docs.godotengine.org/en/latest/tutorials/math/vectors_advanced.html</link> </tutorials> <methods> <method name="AABB"> diff --git a/doc/classes/AnimatedSprite2D.xml b/doc/classes/AnimatedSprite2D.xml index e23a4fe9f8..39228eab79 100644 --- a/doc/classes/AnimatedSprite2D.xml +++ b/doc/classes/AnimatedSprite2D.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="2D Sprite animation">https://docs.godotengine.org/en/latest/tutorials/2d/2d_sprite_animation.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="is_playing" qualifiers="const"> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 9529c60771..ceef16f158 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -692,6 +692,17 @@ Sets the update mode (see [enum UpdateMode]) of a value track. </description> </method> + <method name="value_track_interpolate" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="track_idx" type="int"> + </argument> + <argument index="1" name="time_sec" type="float"> + </argument> + <description> + Returns the interpolated value at the given time (in seconds). The [code]track_idx[/code] must be the index of a value track. + </description> + </method> </methods> <members> <member name="length" type="float" setter="set_length" getter="get_length" default="1.0"> diff --git a/doc/classes/AnimationNodeAdd3.xml b/doc/classes/AnimationNodeAdd3.xml index 94a6ae4221..0e49ac7bbf 100644 --- a/doc/classes/AnimationNodeAdd3.xml +++ b/doc/classes/AnimationNodeAdd3.xml @@ -12,6 +12,7 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AnimationNodeAnimation.xml b/doc/classes/AnimationNodeAnimation.xml index d8ac5413fd..3f0843c112 100644 --- a/doc/classes/AnimationNodeAnimation.xml +++ b/doc/classes/AnimationNodeAnimation.xml @@ -8,6 +8,8 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AnimationNodeBlend2.xml b/doc/classes/AnimationNodeBlend2.xml index e2cd12e685..e509a2df6c 100644 --- a/doc/classes/AnimationNodeBlend2.xml +++ b/doc/classes/AnimationNodeBlend2.xml @@ -8,6 +8,8 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AnimationNodeBlendSpace2D.xml b/doc/classes/AnimationNodeBlendSpace2D.xml index 460c11cc5e..abbc8cb2e6 100644 --- a/doc/classes/AnimationNodeBlendSpace2D.xml +++ b/doc/classes/AnimationNodeBlendSpace2D.xml @@ -10,6 +10,7 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="add_blend_point"> diff --git a/doc/classes/AnimationNodeOneShot.xml b/doc/classes/AnimationNodeOneShot.xml index d5793e5839..65ab363e1f 100644 --- a/doc/classes/AnimationNodeOneShot.xml +++ b/doc/classes/AnimationNodeOneShot.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="get_mix_mode" qualifiers="const"> diff --git a/doc/classes/AnimationNodeOutput.xml b/doc/classes/AnimationNodeOutput.xml index 7640f4dcfa..c4150d7e82 100644 --- a/doc/classes/AnimationNodeOutput.xml +++ b/doc/classes/AnimationNodeOutput.xml @@ -7,6 +7,8 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AnimationNodeTimeScale.xml b/doc/classes/AnimationNodeTimeScale.xml index 334fdb0b95..2ce8309418 100644 --- a/doc/classes/AnimationNodeTimeScale.xml +++ b/doc/classes/AnimationNodeTimeScale.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml index 8338f0d39a..73c7006768 100644 --- a/doc/classes/AnimationNodeTransition.xml +++ b/doc/classes/AnimationNodeTransition.xml @@ -8,6 +8,8 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="get_input_caption" qualifiers="const"> diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index ac91272e00..de2087d930 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -11,6 +11,7 @@ <tutorials> <link title="2D Sprite animation">https://docs.godotengine.org/en/latest/tutorials/2d/2d_sprite_animation.html</link> <link title="Animation tutorial index">https://docs.godotengine.org/en/latest/tutorials/animation/index.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="add_animation"> diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml index cc8e83f509..262b5addb7 100644 --- a/doc/classes/AnimationTree.xml +++ b/doc/classes/AnimationTree.xml @@ -8,7 +8,7 @@ </description> <tutorials> <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> - <link title="Third-person shooter demo code repository">https://github.com/godotengine/tps-demo</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="advance"> diff --git a/doc/classes/Area2D.xml b/doc/classes/Area2D.xml index e98c0af0d9..4f8d102393 100644 --- a/doc/classes/Area2D.xml +++ b/doc/classes/Area2D.xml @@ -8,6 +8,9 @@ </description> <tutorials> <link title="Using Area2D">https://docs.godotengine.org/en/latest/tutorials/physics/using_area_2d.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="2D Pong Demo">https://godotengine.org/asset-library/asset/121</link> + <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> </tutorials> <methods> <method name="get_collision_layer_bit" qualifiers="const"> diff --git a/doc/classes/Area3D.xml b/doc/classes/Area3D.xml index ad9eff3a64..db1c96c8cb 100644 --- a/doc/classes/Area3D.xml +++ b/doc/classes/Area3D.xml @@ -7,6 +7,8 @@ 3D area that detects [CollisionObject3D] nodes overlapping, entering, or exiting. Can also alter or override local physics parameters (gravity, damping). </description> <tutorials> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="GUI in 3D Demo">https://godotengine.org/asset-library/asset/127</link> </tutorials> <methods> <method name="get_collision_layer_bit" qualifiers="const"> diff --git a/doc/classes/AudioEffect.xml b/doc/classes/AudioEffect.xml index 60bf15ce57..955285bd2e 100644 --- a/doc/classes/AudioEffect.xml +++ b/doc/classes/AudioEffect.xml @@ -7,6 +7,7 @@ Base resource for audio bus. Applies an audio effect on the bus that the resource is applied on. </description> <tutorials> + <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AudioEffectRecord.xml b/doc/classes/AudioEffectRecord.xml index 6617d16290..872ddf3b0f 100644 --- a/doc/classes/AudioEffectRecord.xml +++ b/doc/classes/AudioEffectRecord.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="Recording with microphone">https://docs.godotengine.org/en/latest/tutorials/audio/recording_with_microphone.html</link> + <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> </tutorials> <methods> <method name="get_recording" qualifiers="const"> diff --git a/doc/classes/AudioEffectReverb.xml b/doc/classes/AudioEffectReverb.xml index 26eb2be753..fbe68cde0e 100644 --- a/doc/classes/AudioEffectReverb.xml +++ b/doc/classes/AudioEffectReverb.xml @@ -8,6 +8,7 @@ Simulates rooms of different sizes. Its parameters can be adjusted to simulate the sound of a specific room. </description> <tutorials> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index d5fd5d8f00..dafc0065f6 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -8,6 +8,9 @@ </description> <tutorials> <link title="Audio buses">https://docs.godotengine.org/en/latest/tutorials/audio/audio_buses.html</link> + <link title="Audio Device Changer Demo">https://godotengine.org/asset-library/asset/525</link> + <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> + <link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link> </tutorials> <methods> <method name="add_bus"> diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index 58768dc008..bbfec579bb 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -8,6 +8,9 @@ </description> <tutorials> <link title="Audio streams">https://docs.godotengine.org/en/latest/tutorials/audio/audio_streams.html</link> + <link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link> + <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> + <link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link> </tutorials> <methods> <method name="get_length" qualifiers="const"> diff --git a/doc/classes/AudioStreamGenerator.xml b/doc/classes/AudioStreamGenerator.xml index 51254f9305..a273beb5af 100644 --- a/doc/classes/AudioStreamGenerator.xml +++ b/doc/classes/AudioStreamGenerator.xml @@ -5,7 +5,7 @@ <description> </description> <tutorials> - <link title="Audio generator demo project">https://github.com/godotengine/godot-demo-projects/tree/master/audio/generator</link> + <link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml index f928d54526..da75ff206c 100644 --- a/doc/classes/AudioStreamPlayback.xml +++ b/doc/classes/AudioStreamPlayback.xml @@ -7,6 +7,7 @@ Can play, loop, pause a scroll through audio. See [AudioStream] and [AudioStreamOGGVorbis] for usage. </description> <tutorials> + <link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link> </tutorials> <methods> </methods> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index 86048ca8c1..55190c5f9f 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -8,6 +8,11 @@ </description> <tutorials> <link title="Audio streams">https://docs.godotengine.org/en/latest/tutorials/audio/audio_streams.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="Audio Device Changer Demo">https://godotengine.org/asset-library/asset/525</link> + <link title="Audio Generator Demo">https://godotengine.org/asset-library/asset/526</link> + <link title="Audio Mic Record Demo">https://godotengine.org/asset-library/asset/527</link> + <link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link> </tutorials> <methods> <method name="get_playback_position"> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index c614a281ae..faea2f78b8 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -10,8 +10,13 @@ For more information, read the "Matrices and transforms" documentation article. </description> <tutorials> + <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> <link title="Matrices and transforms">https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link> <link title="Using 3D transforms">https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> + <link title="2.5D Demo">https://godotengine.org/asset-library/asset/583</link> </tutorials> <methods> <method name="Basis"> diff --git a/doc/classes/BoxShape3D.xml b/doc/classes/BoxShape3D.xml index fd08da148d..d8cbd8b980 100644 --- a/doc/classes/BoxShape3D.xml +++ b/doc/classes/BoxShape3D.xml @@ -7,6 +7,9 @@ 3D box shape that can be a child of a [PhysicsBody3D] or [Area3D]. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index e0ccda957f..df47fa8bec 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -36,6 +36,8 @@ See also [BaseButton] which contains common properties and methods associated with this node. </description> <tutorials> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index ad49216b34..c7ee915109 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -9,6 +9,9 @@ Note that the [Camera2D] node's [code]position[/code] doesn't represent the actual position of the screen, which may differ due to applied smoothing or limits. You can use [method get_camera_screen_center] to get the real position. </description> <tutorials> + <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> + <link title="2D Isometric Demo">https://godotengine.org/asset-library/asset/112</link> + <link title="2D HDR Demo">https://godotengine.org/asset-library/asset/110</link> </tutorials> <methods> <method name="align"> diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml index 598b4bd685..b6a108fb25 100644 --- a/doc/classes/Camera3D.xml +++ b/doc/classes/Camera3D.xml @@ -7,6 +7,7 @@ [Camera3D] is a special node that displays what is visible from its current location. Cameras register themselves in the nearest [Viewport] node (when ascending the tree). Only one camera can be active per viewport. If no viewport is available ascending the tree, the camera will register in the global viewport. In other words, a camera just provides 3D display capabilities to a [Viewport], and, without one, a scene registered in that [Viewport] (or higher viewports) can't be displayed. </description> <tutorials> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="clear_current"> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 946c4f8988..d36a997330 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -14,6 +14,7 @@ <tutorials> <link title="Viewport and canvas transforms">https://docs.godotengine.org/en/latest/tutorials/2d/2d_transforms.html</link> <link title="Custom drawing in 2D">https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html</link> + <link title="Audio Spectrum Demo">https://godotengine.org/asset-library/asset/528</link> </tutorials> <methods> <method name="_draw" qualifiers="virtual"> @@ -525,13 +526,6 @@ Returns [code]true[/code] if local transform notifications are communicated to children. </description> </method> - <method name="is_set_as_toplevel" qualifiers="const"> - <return type="bool"> - </return> - <description> - Returns [code]true[/code] if the node is set as top-level. See [method set_as_toplevel]. - </description> - </method> <method name="is_transform_notification_enabled" qualifiers="const"> <return type="bool"> </return> @@ -564,15 +558,6 @@ Transformations issued by [code]event[/code]'s inputs are applied in local space instead of global space. </description> </method> - <method name="set_as_toplevel"> - <return type="void"> - </return> - <argument index="0" name="enable" type="bool"> - </argument> - <description> - If [code]enable[/code] is [code]true[/code], the node won't inherit its transform from parent canvas items. - </description> - </method> <method name="set_notify_local_transform"> <return type="void"> </return> @@ -622,6 +607,9 @@ <member name="show_behind_parent" type="bool" setter="set_draw_behind_parent" getter="is_draw_behind_parent_enabled" default="false"> If [code]true[/code], the object draws behind its parent. </member> + <member name="top_level" type="bool" setter="set_as_top_level" getter="is_set_as_top_level"> + If [code]true[/code], the node will not inherit its transform from parent [CanvasItem]s. + </member> <member name="show_on_top" type="bool" setter="_set_on_top" getter="_is_on_top"> If [code]true[/code], the object draws on top of its parent. </member> diff --git a/doc/classes/CanvasLayer.xml b/doc/classes/CanvasLayer.xml index 89cd28b617..9d952cdba3 100644 --- a/doc/classes/CanvasLayer.xml +++ b/doc/classes/CanvasLayer.xml @@ -9,6 +9,7 @@ <tutorials> <link title="Viewport and canvas transforms">https://docs.godotengine.org/en/latest/tutorials/2d/2d_transforms.html</link> <link title="Canvas layers">https://docs.godotengine.org/en/latest/tutorials/2d/canvas_layers.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="get_canvas" qualifiers="const"> diff --git a/doc/classes/CapsuleShape3D.xml b/doc/classes/CapsuleShape3D.xml index f56d94dc63..27a6242bc9 100644 --- a/doc/classes/CapsuleShape3D.xml +++ b/doc/classes/CapsuleShape3D.xml @@ -7,6 +7,7 @@ Capsule shape for collisions. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> </tutorials> <methods> </methods> diff --git a/doc/classes/CollisionShape2D.xml b/doc/classes/CollisionShape2D.xml index b4fcf9cd88..c03eba82ff 100644 --- a/doc/classes/CollisionShape2D.xml +++ b/doc/classes/CollisionShape2D.xml @@ -8,6 +8,9 @@ </description> <tutorials> <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="2D Pong Demo">https://godotengine.org/asset-library/asset/121</link> + <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> </methods> diff --git a/doc/classes/CollisionShape3D.xml b/doc/classes/CollisionShape3D.xml index 177900dd4f..5590947a4f 100644 --- a/doc/classes/CollisionShape3D.xml +++ b/doc/classes/CollisionShape3D.xml @@ -8,6 +8,9 @@ </description> <tutorials> <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="make_convex_from_siblings"> diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 1d249a253c..ef438e422a 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -11,6 +11,9 @@ [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/color_constants.png]Color constants cheatsheet[/url] </description> <tutorials> + <link title="2D GD Paint Demo">https://godotengine.org/asset-library/asset/517</link> + <link title="Tween Demo">https://godotengine.org/asset-library/asset/146</link> + <link title="GUI Drag And Drop Demo">https://godotengine.org/asset-library/asset/133</link> </tutorials> <methods> <method name="Color"> diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index d8b4a8f76c..aea3542867 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -7,6 +7,7 @@ Displays a color picker widget. Useful for selecting a color from an RGB/RGBA colorspace. </description> <tutorials> + <link title="Tween Demo">https://godotengine.org/asset-library/asset/146</link> </tutorials> <methods> <method name="add_preset"> diff --git a/doc/classes/ColorPickerButton.xml b/doc/classes/ColorPickerButton.xml index 36f7880060..76cc49a043 100644 --- a/doc/classes/ColorPickerButton.xml +++ b/doc/classes/ColorPickerButton.xml @@ -8,6 +8,8 @@ See also [BaseButton] which contains common properties and methods associated with this node. </description> <tutorials> + <link title="GUI Drag And Drop Demo">https://godotengine.org/asset-library/asset/133</link> + <link title="2D GD Paint Demo">https://godotengine.org/asset-library/asset/517</link> </tutorials> <methods> <method name="get_picker"> diff --git a/doc/classes/ColorRect.xml b/doc/classes/ColorRect.xml index 9bfcf5071d..072759e383 100644 --- a/doc/classes/ColorRect.xml +++ b/doc/classes/ColorRect.xml @@ -7,6 +7,7 @@ Displays a colored rectangle. </description> <tutorials> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> </methods> diff --git a/doc/classes/ConcavePolygonShape3D.xml b/doc/classes/ConcavePolygonShape3D.xml index 20402d350a..3e83202472 100644 --- a/doc/classes/ConcavePolygonShape3D.xml +++ b/doc/classes/ConcavePolygonShape3D.xml @@ -8,6 +8,7 @@ Note: when used for collision, [ConcavePolygonShape3D] is intended to work with static [PhysicsBody3D] nodes like [StaticBody3D] and will not work with [KinematicBody3D] or [RigidBody3D] with a mode other than Static. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> </tutorials> <methods> <method name="get_faces" qualifiers="const"> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index bb00ac33bf..3b33bbb45b 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -18,6 +18,7 @@ <tutorials> <link title="GUI tutorial index">https://docs.godotengine.org/en/latest/tutorials/gui/index.html</link> <link title="Custom drawing in 2D">https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html</link> + <link title="All GUI Demos">https://github.com/godotengine/godot-demo-projects/tree/master/gui</link> </tutorials> <methods> <method name="_clips_input" qualifiers="virtual"> diff --git a/doc/classes/ConvexPolygonShape3D.xml b/doc/classes/ConvexPolygonShape3D.xml index c036f80e2d..e18d716255 100644 --- a/doc/classes/ConvexPolygonShape3D.xml +++ b/doc/classes/ConvexPolygonShape3D.xml @@ -7,6 +7,7 @@ Convex polygon shape resource, which can be added to a [PhysicsBody3D] or area. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> </tutorials> <methods> </methods> diff --git a/doc/classes/CylinderShape3D.xml b/doc/classes/CylinderShape3D.xml index eb12568e71..99334ceae6 100644 --- a/doc/classes/CylinderShape3D.xml +++ b/doc/classes/CylinderShape3D.xml @@ -7,6 +7,9 @@ Cylinder shape for collisions. </description> <tutorials> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 7123deb206..cb60d6e621 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -74,6 +74,8 @@ </description> <tutorials> <link title="GDScript basics: Dictionary">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_basics.html#dictionary</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="clear"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 28194587ab..53776b7752 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -521,6 +521,18 @@ <argument index="0" name="screen" type="int" default="-1"> </argument> <description> + Returns the dots per inch density of the specified screen. If [code]screen[/code] is [/code]SCREEN_OF_MAIN_WINDOW[/code] (the default value), a screen with the main window will be used. + [b]Note:[/b] On macOS, returned value is inaccurate if fractional display scaling mode is used. + [b]Note:[/b] On Android devices, the actual screen densities are grouped into six generalized densities: + [codeblock] + ldpi - 120 dpi + mdpi - 160 dpi + hdpi - 240 dpi + xhdpi - 320 dpi + xxhdpi - 480 dpi + xxxhdpi - 640 dpi + [/codeblock] + [b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. Returns [code]72[/code] on unsupported platforms. </description> </method> <method name="screen_get_max_scale" qualifiers="const"> diff --git a/doc/classes/DynamicFont.xml b/doc/classes/DynamicFont.xml index 0b9b97f54a..f687937e27 100644 --- a/doc/classes/DynamicFont.xml +++ b/doc/classes/DynamicFont.xml @@ -15,6 +15,7 @@ [b]Note:[/b] DynamicFont doesn't support features such as kerning, right-to-left typesetting, ligatures, text shaping, variable fonts and optional font features yet. If you wish to "bake" an optional font feature into a TTF font file, you can use [url=https://fontforge.org/]FontForge[/url] to do so. In FontForge, use [b]File > Generate Fonts[/b], click [b]Options[/b], choose the desired features then generate the font. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="add_fallback"> diff --git a/doc/classes/DynamicFontData.xml b/doc/classes/DynamicFontData.xml index 483da96f3f..45585f17e0 100644 --- a/doc/classes/DynamicFontData.xml +++ b/doc/classes/DynamicFontData.xml @@ -7,6 +7,7 @@ Used with [DynamicFont] to describe the location of a vector font file for dynamic rendering at runtime. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml index 8865939eb1..4dea1bb645 100644 --- a/doc/classes/EditorNode3DGizmoPlugin.xml +++ b/doc/classes/EditorNode3DGizmoPlugin.xml @@ -136,7 +136,7 @@ </description> </method> <method name="get_priority" qualifiers="virtual"> - <return type="String"> + <return type="int"> </return> <description> Override this method to set the gizmo's priority. Higher values correspond to higher priority. If a gizmo with higher priority conflicts with another gizmo, only the gizmo with higher priority will be used. diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index caee6a0c07..8afc18f91b 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -13,6 +13,9 @@ <tutorials> <link title="Environment and post-processing">https://docs.godotengine.org/en/latest/tutorials/3d/environment_and_post_processing.html</link> <link title="Light transport in game engines">https://docs.godotengine.org/en/latest/tutorials/3d/high_dynamic_range.html</link> + <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> + <link title="2D HDR Demo">https://godotengine.org/asset-library/asset/110</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="is_glow_level_enabled" qualifiers="const"> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index f1b9106d35..ada57a8114 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -25,6 +25,7 @@ </description> <tutorials> <link title="File system">https://docs.godotengine.org/en/latest/getting_started/step_by_step/filesystem.html</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="close"> @@ -399,8 +400,7 @@ <argument index="0" name="line" type="String"> </argument> <description> - Stores the given [String] as a line in the file. - Text will be encoded as UTF-8. + Appends [code]line[/code] to the file followed by a line return character ([code]\n[/code]), encoding the text as UTF-8. </description> </method> <method name="store_pascal_string"> @@ -428,8 +428,7 @@ <argument index="0" name="string" type="String"> </argument> <description> - Stores the given [String] in the file. - Text will be encoded as UTF-8. + Appends [code]string[/code] to the file without a line return, encoding the text as UTF-8. </description> </method> <method name="store_var"> diff --git a/doc/classes/FuncRef.xml b/doc/classes/FuncRef.xml index bf0c0b0d34..7c01397c24 100644 --- a/doc/classes/FuncRef.xml +++ b/doc/classes/FuncRef.xml @@ -14,7 +14,7 @@ <return type="Variant"> </return> <description> - Calls the referenced function previously set by [method set_function] or [method @GDScript.funcref]. + Calls the referenced function previously set in [member function] or [method @GDScript.funcref]. </description> </method> <method name="call_funcv"> @@ -23,35 +23,31 @@ <argument index="0" name="arg_array" type="Array"> </argument> <description> - Calls the referenced function previously set by [method set_function] or [method @GDScript.funcref]. Contrarily to [method call_func], this method does not support a variable number of arguments but expects all parameters to be passed via a single [Array]. + Calls the referenced function previously set in [member function] or [method @GDScript.funcref]. Contrarily to [method call_func], this method does not support a variable number of arguments but expects all parameters to be passed via a single [Array]. </description> </method> - <method name="is_valid" qualifiers="const"> - <return type="bool"> - </return> - <description> - Returns whether the object still exists and has the function assigned. - </description> - </method> - <method name="set_function"> + <method name="set_instance"> <return type="void"> </return> - <argument index="0" name="name" type="StringName"> + <argument index="0" name="instance" type="Object"> </argument> <description> - The name of the referenced function to call on the object, without parentheses or any parameters. + The object containing the referenced function. This object must be of a type actually inheriting from [Object], not a built-in type such as [int], [Vector2] or [Dictionary]. </description> </method> - <method name="set_instance"> - <return type="void"> + <method name="is_valid" qualifiers="const"> + <return type="bool"> </return> - <argument index="0" name="instance" type="Object"> - </argument> <description> - The object containing the referenced function. This object must be of a type actually inheriting from [Object], not a built-in type such as [int], [Vector2] or [Dictionary]. + Returns whether the object still exists and has the function assigned. </description> </method> </methods> + <members> + <member name="function" type="StringName" setter="set_function" getter="get_function"> + The name of the referenced function. + </member> + </members> <constants> </constants> </class> diff --git a/doc/classes/GIProbe.xml b/doc/classes/GIProbe.xml index 9199468ab3..52d3698201 100644 --- a/doc/classes/GIProbe.xml +++ b/doc/classes/GIProbe.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="GI probes">https://docs.godotengine.org/en/latest/tutorials/3d/gi_probes.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="bake"> diff --git a/doc/classes/GIProbeData.xml b/doc/classes/GIProbeData.xml index 228e1afb4c..693df8f819 100644 --- a/doc/classes/GIProbeData.xml +++ b/doc/classes/GIProbeData.xml @@ -5,6 +5,7 @@ <description> </description> <tutorials> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="allocate"> diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index 244bdcf2f3..ba201af5db 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="Particle systems (2D)">https://docs.godotengine.org/en/latest/tutorials/2d/particle_systems_2d.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="capture_rect" qualifiers="const"> diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 8444610f49..e840e457bf 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="Controlling thousands of fish with Particles">https://docs.godotengine.org/en/latest/tutorials/3d/vertex_animation/controlling_thousands_of_fish.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="capture_aabb" qualifiers="const"> diff --git a/doc/classes/GridContainer.xml b/doc/classes/GridContainer.xml index 6ee794c5c4..ca6b4e69c3 100644 --- a/doc/classes/GridContainer.xml +++ b/doc/classes/GridContainer.xml @@ -9,6 +9,7 @@ [b]Note:[/b] GridContainer only works with child nodes inheriting from Control. It won't rearrange child nodes inheriting from Node2D. </description> <tutorials> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index b05ab5b4d6..fb0ed8ff62 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -8,6 +8,8 @@ </description> <tutorials> <link title="Inputs tutorial index">https://docs.godotengine.org/en/latest/tutorials/inputs/index.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="action_press"> diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index 3663ee98cc..8c6063bd67 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -9,6 +9,8 @@ <tutorials> <link title="InputEvent">https://docs.godotengine.org/en/latest/tutorials/inputs/inputevent.html</link> <link title="Viewport and canvas transforms">https://docs.godotengine.org/en/latest/tutorials/2d/2d_transforms.html</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="accumulate"> diff --git a/doc/classes/InputEventAction.xml b/doc/classes/InputEventAction.xml index e0d3e47219..1fe85a5ae8 100644 --- a/doc/classes/InputEventAction.xml +++ b/doc/classes/InputEventAction.xml @@ -8,6 +8,8 @@ </description> <tutorials> <link title="InputEvent: Actions">https://docs.godotengine.org/en/latest/tutorials/inputs/inputevent.html#actions</link> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/InputEventMouseMotion.xml b/doc/classes/InputEventMouseMotion.xml index 3e64fd63ab..0f9e71adb4 100644 --- a/doc/classes/InputEventMouseMotion.xml +++ b/doc/classes/InputEventMouseMotion.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="Mouse and input coordinates">https://docs.godotengine.org/en/latest/tutorials/inputs/mouse_and_input_coordinates.html</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Joint3D.xml b/doc/classes/Joint3D.xml index 15bef960f6..107c638b9e 100644 --- a/doc/classes/Joint3D.xml +++ b/doc/classes/Joint3D.xml @@ -7,6 +7,7 @@ Joints are used to bind together two physics bodies. They have a solver priority and can define if the bodies of the two attached nodes should be able to collide with each other. </description> <tutorials> + <link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link> </tutorials> <methods> </methods> diff --git a/doc/classes/KinematicBody2D.xml b/doc/classes/KinematicBody2D.xml index bc7a7c0d0d..17c4b85346 100644 --- a/doc/classes/KinematicBody2D.xml +++ b/doc/classes/KinematicBody2D.xml @@ -11,6 +11,8 @@ <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> <link title="Using KinematicBody2D">https://docs.godotengine.org/en/latest/tutorials/physics/using_kinematic_body_2d.html</link> + <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> + <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> </tutorials> <methods> <method name="get_floor_normal" qualifiers="const"> diff --git a/doc/classes/KinematicBody3D.xml b/doc/classes/KinematicBody3D.xml index 897b2c6280..17a6034e9a 100644 --- a/doc/classes/KinematicBody3D.xml +++ b/doc/classes/KinematicBody3D.xml @@ -10,6 +10,10 @@ </description> <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="get_axis_lock" qualifiers="const"> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index 3318f2757d..570d7f075b 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -8,6 +8,7 @@ [b]Note:[/b] Contrarily to most other [Control]s, Label's [member Control.mouse_filter] defaults to [constant Control.MOUSE_FILTER_IGNORE] (i.e. it doesn't react to mouse input events). This implies that a label won't display any configured [member Control.hint_tooltip], unless you change its mouse filter. </description> <tutorials> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="get_line_count" qualifiers="const"> diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index c022e56a39..6c008e4f7e 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="3D lights and shadows">https://docs.godotengine.org/en/latest/tutorials/3d/lights_and_shadows.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="get_param" qualifiers="const"> diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index 22fe4f6194..dec5d60cbb 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -7,6 +7,8 @@ A line through several points in 2D space. </description> <tutorials> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="2.5D Demo">https://godotengine.org/asset-library/asset/583</link> </tutorials> <methods> <method name="add_point"> diff --git a/doc/classes/Material.xml b/doc/classes/Material.xml index f3686876ca..10a7061bef 100644 --- a/doc/classes/Material.xml +++ b/doc/classes/Material.xml @@ -7,6 +7,8 @@ Material is a base [Resource] used for coloring and shading geometry. All materials inherit from it and almost all [VisualInstance3D] derived nodes carry a Material. A few flags and parameters are shared between all material types and are configured here. </description> <tutorials> + <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml index 1efa72a2b2..78db09feee 100644 --- a/doc/classes/Mesh.xml +++ b/doc/classes/Mesh.xml @@ -7,6 +7,10 @@ Mesh is a type of [Resource] that contains vertex array-based geometry, divided in [i]surfaces[/i]. Each surface contains a completely separate array and a material used to draw it. Design wise, a mesh with multiple surfaces is preferred to a single surface, because objects created in 3D editing software commonly contain multiple materials. </description> <tutorials> + <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="create_convex_shape" qualifiers="const"> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index c569da2df1..82cd392cd3 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -7,6 +7,10 @@ MeshInstance3D is a node that takes a [Mesh] resource and adds it to the current scenario by creating an instance of it. This is the class most often used render 3D geometry and can be used to instance a single [Mesh] in many places. This allows reuse of geometry which can save on resources. When a [Mesh] has to be instanced more than thousands of times at close proximity, consider using a [MultiMesh] in a [MultiMeshInstance3D] instead. </description> <tutorials> + <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="create_convex_collision"> diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index ccf6172017..ad8bd6991d 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -7,6 +7,8 @@ A library of meshes. Contains a list of [Mesh] resources, each with a name and ID. Each item can also include collision and navigation shapes. This resource is used in [GridMap]. </description> <tutorials> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> </tutorials> <methods> <method name="clear"> diff --git a/doc/classes/Navigation2D.xml b/doc/classes/Navigation2D.xml index dcbfbc2350..abac29bdb7 100644 --- a/doc/classes/Navigation2D.xml +++ b/doc/classes/Navigation2D.xml @@ -7,6 +7,7 @@ Navigation2D provides navigation and pathfinding within a 2D area, specified as a collection of [NavigationPolygon] resources. These are automatically collected from child [NavigationRegion2D] nodes. </description> <tutorials> + <link title="2D Navigation Demo">https://godotengine.org/asset-library/asset/117</link> </tutorials> <methods> <method name="get_closest_point" qualifiers="const"> diff --git a/doc/classes/Navigation3D.xml b/doc/classes/Navigation3D.xml index 807f0ad309..e7a4fe3c43 100644 --- a/doc/classes/Navigation3D.xml +++ b/doc/classes/Navigation3D.xml @@ -7,6 +7,7 @@ Provides navigation and pathfinding within a collection of [NavigationMesh]es. These will be automatically collected from child [NavigationRegion3D] nodes. In addition to basic pathfinding, this class also assists with aligning navigation agents with the meshes they are navigating on. </description> <tutorials> + <link title="3D Navmesh Demo">https://godotengine.org/asset-library/asset/124</link> </tutorials> <methods> <method name="get_closest_point" qualifiers="const"> diff --git a/doc/classes/NavigationMesh.xml b/doc/classes/NavigationMesh.xml index a2fa31bf65..dd7464ac0e 100644 --- a/doc/classes/NavigationMesh.xml +++ b/doc/classes/NavigationMesh.xml @@ -5,6 +5,7 @@ <description> </description> <tutorials> + <link title="3D Navmesh Demo">https://godotengine.org/asset-library/asset/124</link> </tutorials> <methods> <method name="add_polygon"> diff --git a/doc/classes/NavigationPolygon.xml b/doc/classes/NavigationPolygon.xml index b0f77dff83..e75efa3b27 100644 --- a/doc/classes/NavigationPolygon.xml +++ b/doc/classes/NavigationPolygon.xml @@ -24,6 +24,7 @@ [/codeblock] </description> <tutorials> + <link title="2D Navigation Demo">https://godotengine.org/asset-library/asset/117</link> </tutorials> <methods> <method name="add_outline"> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index 1b9099336c..5f0b04487e 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -7,6 +7,7 @@ NavigationServer2D is the server responsible for all 2D navigation. It creates the agents, maps, and regions for navigation to work as expected. This keeps tracks of any call and executes them during the sync phase. This means that you can request any change to the map, using any thread, without worrying. </description> <tutorials> + <link title="2D Navigation Demo">https://godotengine.org/asset-library/asset/117</link> </tutorials> <methods> <method name="agent_create" qualifiers="const"> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 1f621c3c81..95890c4b4c 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -7,6 +7,7 @@ NavigationServer3D is the server responsible for all 3D navigation. It creates the agents, maps, and regions for navigation to work as expected. This keeps tracks of any call and executes them during the sync phase. This means that you can request any change to the map, using any thread, without worrying. </description> <tutorials> + <link title="3D Navmesh Demo">https://godotengine.org/asset-library/asset/124</link> </tutorials> <methods> <method name="agent_create" qualifiers="const"> diff --git a/doc/classes/NetworkedMultiplayerPeer.xml b/doc/classes/NetworkedMultiplayerPeer.xml index 7e2a9af59f..954d31794a 100644 --- a/doc/classes/NetworkedMultiplayerPeer.xml +++ b/doc/classes/NetworkedMultiplayerPeer.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="High-level multiplayer">https://docs.godotengine.org/en/latest/tutorials/networking/high_level_multiplayer.html</link> + <link title="WebRTC Signaling Demo">https://godotengine.org/asset-library/asset/537</link> </tutorials> <methods> <method name="get_connection_status" qualifiers="const"> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 1548800901..623e4b099b 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -18,6 +18,7 @@ </description> <tutorials> <link title="Scenes and nodes">https://docs.godotengine.org/en/latest/getting_started/step_by_step/scenes_and_nodes.html</link> + <link title="All Demos">https://github.com/godotengine/godot-demo-projects/</link> </tutorials> <methods> <method name="_enter_tree" qualifiers="virtual"> diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml index 987a18f367..eed0ec8d7e 100644 --- a/doc/classes/Node2D.xml +++ b/doc/classes/Node2D.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="Custom drawing in 2D">https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html</link> + <link title="All 2D Demos">https://github.com/godotengine/godot-demo-projects/tree/master/2d</link> </tutorials> <methods> <method name="apply_scale"> diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index 1ef875f606..af4f18c5f7 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -10,6 +10,7 @@ </description> <tutorials> <link title="Introduction to 3D">https://docs.godotengine.org/en/latest/tutorials/3d/introduction_to_3d.html</link> + <link title="All 3D Demos">https://github.com/godotengine/godot-demo-projects/tree/master/3d</link> </tutorials> <methods> <method name="force_update_transform"> @@ -83,13 +84,6 @@ Returns whether this node uses a scale of [code](1, 1, 1)[/code] or its local transformation scale. </description> </method> - <method name="is_set_as_toplevel" qualifiers="const"> - <return type="bool"> - </return> - <description> - Returns whether this node is set as Toplevel, that is whether it ignores its parent nodes transformations. - </description> - </method> <method name="is_transform_notification_enabled" qualifiers="const"> <return type="bool"> </return> @@ -195,15 +189,6 @@ Scales the local transformation by given 3D scale factors in object-local coordinate system. </description> </method> - <method name="set_as_toplevel"> - <return type="void"> - </return> - <argument index="0" name="enable" type="bool"> - </argument> - <description> - Makes the node ignore its parents transformations. Node transformations are only in global space. - </description> - </method> <method name="set_disable_scale"> <return type="void"> </return> @@ -316,6 +301,9 @@ <member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3( 1, 1, 1 )"> Scale part of the local transformation. </member> + <member name="top_level" type="bool" setter="set_as_top_level" getter="is_set_as_top_level"> + If [code]true[/code], the node will not inherit its transformations from its parent. Node transformations are only in global space. + </member> <member name="transform" type="Transform" setter="set_transform" getter="get_transform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )"> Local space [Transform] of this node, with respect to the parent node. </member> diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml index dc7fd1be3f..658f0e6c28 100644 --- a/doc/classes/NodePath.xml +++ b/doc/classes/NodePath.xml @@ -22,6 +22,7 @@ [/codeblock] </description> <tutorials> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> </tutorials> <methods> <method name="NodePath"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a1f12e1a31..1487c9e078 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -7,6 +7,7 @@ Operating System functions. OS wraps the most common functionality to communicate with the host operating system, such as the clipboard, video driver, date and time, timers, environment variables, execution of binaries, command line, etc. </description> <tutorials> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="can_use_threads" qualifiers="const"> diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml index bb56330248..be40ab05de 100644 --- a/doc/classes/PackedScene.xml +++ b/doc/classes/PackedScene.xml @@ -38,6 +38,7 @@ [/codeblock] </description> <tutorials> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> </tutorials> <methods> <method name="can_instance" qualifiers="const"> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index f36af66d6d..5f3a16ac73 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This type is passed by value and not by reference. </description> <tutorials> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="PackedStringArray"> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index ecc535e488..cb62aea95c 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This type is passed by value and not by reference. </description> <tutorials> + <link title="2D Navigation Astar Demo">https://godotengine.org/asset-library/asset/519</link> </tutorials> <methods> <method name="PackedVector2Array"> diff --git a/doc/classes/Panel.xml b/doc/classes/Panel.xml index 7285bc9e2e..b65c2c956d 100644 --- a/doc/classes/Panel.xml +++ b/doc/classes/Panel.xml @@ -7,6 +7,9 @@ Panel is a [Control] that displays an opaque background. It's commonly used as a parent and container for other types of [Control] nodes. </description> <tutorials> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> + <link title="2D Finite State Machine Demo">https://godotengine.org/asset-library/asset/516</link> + <link title="3D Inverse Kinematics Demo">https://godotengine.org/asset-library/asset/523</link> </tutorials> <methods> </methods> diff --git a/doc/classes/PanelContainer.xml b/doc/classes/PanelContainer.xml index d39122c395..ad6080c780 100644 --- a/doc/classes/PanelContainer.xml +++ b/doc/classes/PanelContainer.xml @@ -7,6 +7,7 @@ Panel container type. This container fits controls inside of the delimited area of a stylebox. It's useful for giving controls an outline. </description> <tutorials> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> </tutorials> <methods> </methods> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ed8eddda07..54c337faf9 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -9,6 +9,9 @@ [b]Overriding:[/b] Any project setting can be overridden by creating a file named [code]override.cfg[/code] in the project's root directory. This can also be used in exported projects by placing this file in the same directory as the project binary. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="add_property_info"> @@ -555,6 +558,9 @@ <member name="input_devices/pointing/emulate_touch_from_mouse" type="bool" setter="" getter="" default="false"> If [code]true[/code], sends touch input events when clicking or dragging the mouse. </member> + <member name="input_devices/pointing/ios/touch_delay" type="float" setter="" getter="" default="0.150"> + Default delay for touch events. This only affects iOS devices. + </member> <member name="layer_names/2d_physics/layer_1" type="String" setter="" getter="" default=""""> Optional name for the 2D physics layer 1. </member> diff --git a/doc/classes/QuadMesh.xml b/doc/classes/QuadMesh.xml index 422add08d1..24a3d76ee2 100644 --- a/doc/classes/QuadMesh.xml +++ b/doc/classes/QuadMesh.xml @@ -7,6 +7,8 @@ Class representing a square [PrimitiveMesh]. This flat mesh does not have a thickness. By default, this mesh is aligned on the X and Y axes; this default rotation is more suited for use with billboarded materials. Unlike [PlaneMesh], this mesh doesn't provide subdivision options. </description> <tutorials> + <link title="GUI in 3D Demo">https://godotengine.org/asset-library/asset/127</link> + <link title="2D in 3D Demo">https://godotengine.org/asset-library/asset/129</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Quat.xml b/doc/classes/Quat.xml index 2218852dae..6c95e303b8 100644 --- a/doc/classes/Quat.xml +++ b/doc/classes/Quat.xml @@ -10,6 +10,7 @@ </description> <tutorials> <link title="Using 3D transforms">https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="Quat"> diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml index 1d8edf0adb..d24e86a08b 100644 --- a/doc/classes/RayCast3D.xml +++ b/doc/classes/RayCast3D.xml @@ -12,6 +12,7 @@ </description> <tutorials> <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="add_exception"> diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml index 2d1685871d..aaeaa7629d 100644 --- a/doc/classes/Rect2.xml +++ b/doc/classes/Rect2.xml @@ -10,6 +10,8 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="Advanced vector math">https://docs.godotengine.org/en/latest/tutorials/math/vectors_advanced.html</link> </tutorials> <methods> <method name="Rect2"> diff --git a/doc/classes/Rect2i.xml b/doc/classes/Rect2i.xml index 809f385f6e..8b29daa264 100644 --- a/doc/classes/Rect2i.xml +++ b/doc/classes/Rect2i.xml @@ -9,6 +9,7 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> </tutorials> <methods> <method name="Rect2i"> diff --git a/doc/classes/RectangleShape2D.xml b/doc/classes/RectangleShape2D.xml index b42cc48d43..041416a24b 100644 --- a/doc/classes/RectangleShape2D.xml +++ b/doc/classes/RectangleShape2D.xml @@ -7,6 +7,8 @@ Rectangle shape for 2D collisions. This shape is useful for modeling box-like 2D objects. </description> <tutorials> + <link title="2D Pong Demo">https://godotengine.org/asset-library/asset/121</link> + <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> </methods> diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml index 1cf775f389..6a864b1067 100644 --- a/doc/classes/ResourceLoader.xml +++ b/doc/classes/ResourceLoader.xml @@ -9,6 +9,7 @@ GDScript has a simplified [method @GDScript.load] built-in method which can be used in most situations, leaving the use of [ResourceLoader] for more advanced scenarios. </description> <tutorials> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="exists"> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 050f1aab25..faf0d97766 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -10,6 +10,8 @@ </description> <tutorials> <link title="BBCode in RichTextLabel">https://docs.godotengine.org/en/latest/tutorials/gui/bbcode_in_richtextlabel.html</link> + <link title="GUI Rich Text/BBcode Demo">https://godotengine.org/asset-library/asset/132</link> + <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> <method name="add_image"> diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index f3d43b193e..101f29c43e 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -12,6 +12,8 @@ The center of mass is always located at the node's origin without taking into account the [CollisionShape2D] centroid offsets. </description> <tutorials> + <link title="2D Physics Platformer Demo">https://godotengine.org/asset-library/asset/119</link> + <link title="Instancing Demo">https://godotengine.org/asset-library/asset/148</link> </tutorials> <methods> <method name="_integrate_forces" qualifiers="virtual"> diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml index e9ebf33aa7..8d92c8b066 100644 --- a/doc/classes/RigidBody3D.xml +++ b/doc/classes/RigidBody3D.xml @@ -12,6 +12,8 @@ </description> <tutorials> <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> </tutorials> <methods> <method name="_integrate_forces" qualifiers="virtual"> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 7ec8fe12fb..fe2cc1f5ad 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -9,6 +9,8 @@ Note that "global pose" below refers to the overall transform of the bone with respect to skeleton, so it not the actual global/world transform of the bone. </description> <tutorials> + <link title="3D Inverse Kinematics Demo">https://godotengine.org/asset-library/asset/523</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> <method name="add_bone"> diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml index de83847403..5193109447 100644 --- a/doc/classes/SkeletonIK3D.xml +++ b/doc/classes/SkeletonIK3D.xml @@ -5,6 +5,7 @@ <description> </description> <tutorials> + <link title="3D Inverse Kinematics Demo">https://godotengine.org/asset-library/asset/523</link> </tutorials> <methods> <method name="get_parent_skeleton" qualifiers="const"> diff --git a/doc/classes/SphereShape3D.xml b/doc/classes/SphereShape3D.xml index 1eaf890639..e90493fca2 100644 --- a/doc/classes/SphereShape3D.xml +++ b/doc/classes/SphereShape3D.xml @@ -7,6 +7,7 @@ Sphere shape for 3D collisions, which can be set into a [PhysicsBody3D] or [Area3D]. This shape is useful for modeling sphere-like 3D objects. </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> </tutorials> <methods> </methods> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index fc849baa8d..f868503bbd 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -8,6 +8,7 @@ </description> <tutorials> <link title="3D lights and shadows">https://docs.godotengine.org/en/latest/tutorials/3d/lights_and_shadows.html</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Sprite2D.xml b/doc/classes/Sprite2D.xml index f218631038..8205889ea7 100644 --- a/doc/classes/Sprite2D.xml +++ b/doc/classes/Sprite2D.xml @@ -7,6 +7,7 @@ A node that displays a 2D texture. The texture displayed can be a region from a larger atlas texture, or a frame from a sprite sheet animation. </description> <tutorials> + <link title="Instancing Demo">https://godotengine.org/asset-library/asset/148</link> </tutorials> <methods> <method name="get_rect" qualifiers="const"> diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index c896efc314..63a15cbe1d 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -8,6 +8,9 @@ Additionally, a constant linear or angular velocity can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels). </description> <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 40fff25fc4..4034a5ee07 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -342,7 +342,10 @@ <argument index="0" name="to" type="String"> </argument> <description> - Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]+1[/code] if greater than, or [code]0[/code] if equal. + Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/code] of each string, which roughly matches the alphabetical order. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [code]to[/code] string or [code]-1[/code] if the "base" string is shorter than the [code]to[/code] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [code]to[/code] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="count"> @@ -615,7 +618,7 @@ <return type="bool"> </return> <description> - Returns [code]true[/code] if this string contains a valid IP address. + Returns [code]true[/code] if this string contains only a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]0.0.0.0[/code] as valid. </description> </method> <method name="join"> @@ -712,7 +715,11 @@ <argument index="0" name="to" type="String"> </argument> <description> - Performs a case-insensitive natural order comparison to another string. Returns [code]-1[/code] if less than, [code]+1[/code] if greater than, or [code]0[/code] if equal. + Performs a case-insensitive [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/code] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. + When used for sorting, natural order comparison will order suites of numbers as expected by most people. If you sort the numbers from 1 to 10 using natural order, you will get [code][1, 2, 3, ...][/code] instead of [code][1, 10, 2, 3, ...][/code]. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [code]to[/code] string or [code]-1[/code] if the "base" string is shorter than the [code]to[/code] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [code]to[/code] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. </description> </method> <method name="nocasecmp_to"> @@ -721,7 +728,10 @@ <argument index="0" name="to" type="String"> </argument> <description> - Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]+1[/code] if greater than, or [code]0[/code] if equal. + Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/code] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [code]to[/code] string or [code]-1[/code] if the "base" string is shorter than the [code]to[/code] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [code]to[/code] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="ord_at"> diff --git a/doc/classes/SubViewport.xml b/doc/classes/SubViewport.xml index c9c7589631..4736401395 100644 --- a/doc/classes/SubViewport.xml +++ b/doc/classes/SubViewport.xml @@ -1,10 +1,19 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SubViewport" inherits="Viewport" version="4.0"> <brief_description> + Creates a sub-view into the screen. </brief_description> <description> </description> <tutorials> + <link title="Viewport and canvas transforms">https://docs.godotengine.org/en/latest/tutorials/2d/2d_transforms.html</link> + <link title="Viewports tutorial index">https://docs.godotengine.org/en/latest/tutorials/viewports/index.html</link> + <link title="GUI in 3D Demo">https://godotengine.org/asset-library/asset/127</link> + <link title="3D in 2D Demo">https://godotengine.org/asset-library/asset/128</link> + <link title="2D in 3D Demo">https://godotengine.org/asset-library/asset/129</link> + <link title="Screen Capture Demo">https://godotengine.org/asset-library/asset/130</link> + <link title="Dynamic Split Screen Demo">https://godotengine.org/asset-library/asset/541</link> + <link title="3D Viewport Scaling Demo">https://godotengine.org/asset-library/asset/586</link> </tutorials> <methods> </methods> diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml index eeb6b6cd9d..385e31ccc7 100644 --- a/doc/classes/SurfaceTool.xml +++ b/doc/classes/SurfaceTool.xml @@ -19,6 +19,7 @@ [b]Note:[/b] Godot uses clockwise [url=https://learnopengl.com/Advanced-OpenGL/Face-culling]winding order[/url] for front faces of triangle primitive modes. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="add_bones"> diff --git a/doc/classes/TextureButton.xml b/doc/classes/TextureButton.xml index 46b008c018..70bf138f27 100644 --- a/doc/classes/TextureButton.xml +++ b/doc/classes/TextureButton.xml @@ -9,6 +9,7 @@ See also [BaseButton] which contains common properties and methods associated with this node. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/TextureRect.xml b/doc/classes/TextureRect.xml index 709d87b858..743e7f6d1e 100644 --- a/doc/classes/TextureRect.xml +++ b/doc/classes/TextureRect.xml @@ -7,6 +7,7 @@ Used to draw icons and sprites in a user interface. The texture's placement can be controlled with the [member stretch_mode] property. It can scale, tile, or stay centered inside its bounding rectangle. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index 46377ecf20..88f46e3937 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -10,6 +10,7 @@ <tutorials> <link title="Using multiple threads">https://docs.godotengine.org/en/latest/tutorials/threads/using_multiple_threads.html</link> <link title="Thread-safe APIs">https://docs.godotengine.org/en/latest/tutorials/threads/thread_safe_apis.html</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> <method name="get_id" qualifiers="const"> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 2780545f55..371ba80723 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -8,6 +8,12 @@ </description> <tutorials> <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> + <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> + <link title="2D Isometric Demo">https://godotengine.org/asset-library/asset/112</link> + <link title="2D Hexagonal Demo">https://godotengine.org/asset-library/asset/111</link> + <link title="2D Navigation Astar Demo">https://godotengine.org/asset-library/asset/519</link> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> + <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> <method name="clear"> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 4991a1e58b..9ab9d9ca7a 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -8,6 +8,13 @@ Tiles are referenced by a unique integer ID. </description> <tutorials> + <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> + <link title="2D Platformer Demo">https://godotengine.org/asset-library/asset/120</link> + <link title="2D Isometric Demo">https://godotengine.org/asset-library/asset/112</link> + <link title="2D Hexagonal Demo">https://godotengine.org/asset-library/asset/111</link> + <link title="2D Navigation Astar Demo">https://godotengine.org/asset-library/asset/519</link> + <link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link> + <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> <method name="_forward_atlas_subtile_selection" qualifiers="virtual"> diff --git a/doc/classes/Timer.xml b/doc/classes/Timer.xml index 5d684755fa..ab75e21ce8 100644 --- a/doc/classes/Timer.xml +++ b/doc/classes/Timer.xml @@ -8,6 +8,7 @@ [b]Note:[/b] To create an one-shot timer without instantiating a node, use [method SceneTree.create_timer]. </description> <tutorials> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="is_stopped" qualifiers="const"> diff --git a/doc/classes/Transform.xml b/doc/classes/Transform.xml index 09dfe56f8f..52dda75dff 100644 --- a/doc/classes/Transform.xml +++ b/doc/classes/Transform.xml @@ -11,6 +11,9 @@ <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> <link title="Matrices and transforms">https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link> <link title="Using 3D transforms">https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="2.5D Demo">https://godotengine.org/asset-library/asset/583</link> </tutorials> <methods> <method name="Transform"> diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index 5da88892fe..b23bb4d33b 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -8,7 +8,10 @@ For more information, read the "Matrices and transforms" documentation article. </description> <tutorials> + <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> <link title="Matrices and transforms">https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html</link> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="2.5D Demo">https://godotengine.org/asset-library/asset/583</link> </tutorials> <methods> <method name="Transform2D"> diff --git a/doc/classes/VBoxContainer.xml b/doc/classes/VBoxContainer.xml index 6b32a08f93..213f8fd742 100644 --- a/doc/classes/VBoxContainer.xml +++ b/doc/classes/VBoxContainer.xml @@ -7,6 +7,7 @@ Vertical box container. See [BoxContainer]. </description> <tutorials> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 44398cabbf..025d6474ee 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -10,6 +10,11 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="Advanced vector math">https://docs.godotengine.org/en/latest/tutorials/math/vectors_advanced.html</link> + <link title="3Blue1Brown Essence of Linear Algebra">https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab</link> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="All 2D Demos">https://github.com/godotengine/godot-demo-projects/tree/master/2d</link> </tutorials> <methods> <method name="Vector2"> diff --git a/doc/classes/Vector2i.xml b/doc/classes/Vector2i.xml index 3ad926210b..75ddc46dab 100644 --- a/doc/classes/Vector2i.xml +++ b/doc/classes/Vector2i.xml @@ -10,6 +10,8 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="3Blue1Brown Essence of Linear Algebra">https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab</link> </tutorials> <methods> <method name="Vector2i"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index df5d4f150e..b26fe09e91 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -10,6 +10,11 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="Advanced vector math">https://docs.godotengine.org/en/latest/tutorials/math/vectors_advanced.html</link> + <link title="3Blue1Brown Essence of Linear Algebra">https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab</link> + <link title="Matrix Transform Demo">https://godotengine.org/asset-library/asset/584</link> + <link title="All 3D Demos">https://github.com/godotengine/godot-demo-projects/tree/master/3d</link> </tutorials> <methods> <method name="Vector3"> diff --git a/doc/classes/Vector3i.xml b/doc/classes/Vector3i.xml index bd7c354241..f977b81ce7 100644 --- a/doc/classes/Vector3i.xml +++ b/doc/classes/Vector3i.xml @@ -10,6 +10,8 @@ </description> <tutorials> <link title="Math tutorial index">https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> + <link title="Vector math">https://docs.godotengine.org/en/latest/tutorials/math/vector_math.html</link> + <link title="3Blue1Brown Essence of Linear Algebra">https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab</link> </tutorials> <methods> <method name="Vector3i"> diff --git a/doc/classes/VehicleBody3D.xml b/doc/classes/VehicleBody3D.xml index 5a2cce376e..1125a1da94 100644 --- a/doc/classes/VehicleBody3D.xml +++ b/doc/classes/VehicleBody3D.xml @@ -9,6 +9,7 @@ [b]Note:[/b] This class has known issues and isn't designed to provide realistic 3D vehicle physics. If you want advanced vehicle physics, you will probably have to write your own physics integration using another [PhysicsBody3D] class. </description> <tutorials> + <link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link> </tutorials> <methods> </methods> diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml index 97b2abfa92..fb0cb03d1c 100644 --- a/doc/classes/VehicleWheel3D.xml +++ b/doc/classes/VehicleWheel3D.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This class has known issues and isn't designed to provide realistic 3D vehicle physics. If you want advanced vehicle physics, you will probably have to write your own physics integration using another [PhysicsBody3D] class. </description> <tutorials> + <link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link> </tutorials> <methods> <method name="get_rpm" qualifiers="const"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index e42c4021ab..84974874de 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -14,6 +14,12 @@ <tutorials> <link title="Viewport and canvas transforms">https://docs.godotengine.org/en/latest/tutorials/2d/2d_transforms.html</link> <link title="Viewports tutorial index">https://docs.godotengine.org/en/latest/tutorials/viewports/index.html</link> + <link title="GUI in 3D Demo">https://godotengine.org/asset-library/asset/127</link> + <link title="3D in 2D Demo">https://godotengine.org/asset-library/asset/128</link> + <link title="2D in 3D Demo">https://godotengine.org/asset-library/asset/129</link> + <link title="Screen Capture Demo">https://godotengine.org/asset-library/asset/130</link> + <link title="Dynamic Split Screen Demo">https://godotengine.org/asset-library/asset/541</link> + <link title="3D Viewport Scaling Demo">https://godotengine.org/asset-library/asset/586</link> </tutorials> <methods> <method name="find_world_2d" qualifiers="const"> diff --git a/doc/classes/ViewportTexture.xml b/doc/classes/ViewportTexture.xml index 14b460a43b..393f1bb0b8 100644 --- a/doc/classes/ViewportTexture.xml +++ b/doc/classes/ViewportTexture.xml @@ -8,6 +8,10 @@ To create a ViewportTexture in code, use the [method Viewport.get_texture] method on the target viewport. </description> <tutorials> + <link title="GUI in 3D Demo">https://godotengine.org/asset-library/asset/127</link> + <link title="3D in 2D Demo">https://godotengine.org/asset-library/asset/128</link> + <link title="2D in 3D Demo">https://godotengine.org/asset-library/asset/129</link> + <link title="3D Viewport Scaling Demo">https://godotengine.org/asset-library/asset/586</link> </tutorials> <methods> </methods> diff --git a/doc/classes/VisibilityNotifier2D.xml b/doc/classes/VisibilityNotifier2D.xml index 314a100989..761438b77e 100644 --- a/doc/classes/VisibilityNotifier2D.xml +++ b/doc/classes/VisibilityNotifier2D.xml @@ -9,6 +9,7 @@ [b]Note:[/b] For performance reasons, VisibilityNotifier2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need precise visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node. </description> <tutorials> + <link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link> </tutorials> <methods> <method name="is_on_screen" qualifiers="const"> diff --git a/doc/classes/WorldEnvironment.xml b/doc/classes/WorldEnvironment.xml index f9f0241365..c1cf639ec0 100644 --- a/doc/classes/WorldEnvironment.xml +++ b/doc/classes/WorldEnvironment.xml @@ -10,6 +10,9 @@ </description> <tutorials> <link title="Environment and post-processing">https://docs.godotengine.org/en/latest/tutorials/3d/environment_and_post_processing.html</link> + <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> + <link title="2D HDR Demo">https://godotengine.org/asset-library/asset/110</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> </methods> diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index d569a2ca0a..52c984cbc0 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -4959,11 +4959,6 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { box_selection->set_size(rect.size); box_select_rect = rect; - - if (get_local_mouse_position().y < 0) { - //avoid box selection from going up and lose focus to viewport - warp_mouse(Vector2(mm->get_position().x, 0)); - } } } @@ -5763,7 +5758,7 @@ AnimationTrackEditor::AnimationTrackEditor() { box_selection = memnew(Control); add_child(box_selection); - box_selection->set_as_toplevel(true); + box_selection->set_as_top_level(true); box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE); box_selection->hide(); box_selection->connect("draw", callable_mp(this, &AnimationTrackEditor::_box_selection_draw)); diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index adb09532eb..a3deb95130 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -846,7 +846,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { audioprev_hbc->add_child(audio_value_preview_label); slider->add_child(audio_value_preview_box); - audio_value_preview_box->set_as_toplevel(true); + audio_value_preview_box->set_as_top_level(true); Ref<StyleBoxFlat> panel_style = memnew(StyleBoxFlat); panel_style->set_bg_color(Color(0.0f, 0.0f, 0.0f, 0.8f)); audio_value_preview_box->add_theme_style_override("panel", panel_style); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 2e716a636e..9900e8184d 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -48,7 +48,7 @@ Size2 EditorProperty::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (!c->is_visible()) { @@ -117,7 +117,7 @@ void EditorProperty::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (c == bottom_editor) { @@ -179,7 +179,7 @@ void EditorProperty::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (c == bottom_editor) { @@ -1133,7 +1133,7 @@ void EditorInspectorSection::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (!c->is_visible_in_tree()) { @@ -1225,7 +1225,7 @@ Size2 EditorInspectorSection::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (!c->is_visible()) { diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index d2250fed7a..3170ea5ff8 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -164,7 +164,6 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< r_texture = generated; int small_thumbnail_size = EditorNode::get_singleton()->get_theme_base()->get_theme_icon("Object", "EditorIcons")->get_width(); // Kind of a workaround to retrieve the default icon size - small_thumbnail_size *= EDSCALE; if (preview_generators[i]->can_generate_small_preview()) { Ref<Texture2D> generated_small; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index ac61a75a6c..efc966c6c4 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -483,7 +483,7 @@ EditorSpinSlider::EditorSpinSlider() { grabber = memnew(TextureRect); add_child(grabber); grabber->hide(); - grabber->set_as_toplevel(true); + grabber->set_as_top_level(true); grabber->set_mouse_filter(MOUSE_FILTER_STOP); grabber->connect("mouse_entered", callable_mp(this, &EditorSpinSlider::_grabber_mouse_entered)); grabber->connect("mouse_exited", callable_mp(this, &EditorSpinSlider::_grabber_mouse_exited)); diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index 8caa4aeeaf..f4171eda32 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -379,13 +379,21 @@ Error EditorSceneImporterGLTF::_parse_buffers(GLTFState &state, const String &p_ Vector<uint8_t> buffer_data; String uri = buffer["uri"]; - if (uri.findn("data:application/octet-stream;base64") == 0) { - //embedded data + if (uri.begins_with("data:")) { // Embedded data using base64. + // Validate data MIME types and throw an error if it's one we don't know/support. + // Could be an importer bug on our side or a broken glTF file. + // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#file-extensions-and-mime-types + if (!uri.begins_with("data:application/octet-stream;base64") && + !uri.begins_with("data:application/gltf-buffer;base64") && + !uri.begins_with("data:image/jpeg;base64") && + !uri.begins_with("data:image/png;base64")) { + ERR_PRINT("glTF file contains buffer with an unknown URI data type: " + uri); + } buffer_data = _parse_base64_uri(uri); - } else { - uri = p_base_path.plus_file(uri).replace("\\", "/"); //fix for windows + } else { // Should be a relative file path. + uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. buffer_data = FileAccess::get_file_as_array(uri); - ERR_FAIL_COND_V(buffer.size() == 0, ERR_PARSE_ERROR); + ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "Couldn't load binary file as an array: " + uri); } ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 402ec5d80d..c06580df26 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -548,7 +548,7 @@ void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, c const CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item && !canvas_item->is_set_as_toplevel()) { + if (canvas_item && !canvas_item->is_set_as_top_level()) { _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); } else { const CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); @@ -591,7 +591,7 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no for (int i = p_node->get_child_count() - 1; i >= 0; i--) { if (canvas_item) { - if (!canvas_item->is_set_as_toplevel()) { + if (!canvas_item->is_set_as_top_level()) { _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); } else { _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, canvas_item->get_transform(), p_canvas_xform); @@ -767,7 +767,7 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n if (!lock_children || !editable) { for (int i = p_node->get_child_count() - 1; i >= 0; i--) { if (canvas_item) { - if (!canvas_item->is_set_as_toplevel()) { + if (!canvas_item->is_set_as_top_level()) { _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); } else { _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, canvas_item->get_transform(), p_canvas_xform); @@ -2599,12 +2599,28 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { } void CanvasItemEditor::_update_cursor() { - CursorShape c = CURSOR_ARROW; - bool should_switch = false; - if (drag_selection.size() != 0) { - float angle = drag_selection[0]->_edit_get_rotation(); - should_switch = abs(Math::cos(angle)) < Math_SQRT12; + // Compute an eventual rotation of the cursor + CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE }; + int rotation_array_index = 0; + + List<CanvasItem *> selection = _get_edited_canvas_items(); + if (selection.size() == 1) { + float angle = Math::fposmod((double)selection[0]->get_global_transform_with_canvas().get_rotation(), Math_PI); + if (angle > Math_PI * 7.0 / 8.0) { + rotation_array_index = 0; + } else if (angle > Math_PI * 5.0 / 8.0) { + rotation_array_index = 1; + } else if (angle > Math_PI * 3.0 / 8.0) { + rotation_array_index = 2; + } else if (angle > Math_PI * 1.0 / 8.0) { + rotation_array_index = 3; + } else { + rotation_array_index = 0; + } } + + // Choose the correct cursor + CursorShape c = CURSOR_ARROW; switch (drag_type) { case DRAG_NONE: switch (tool) { @@ -2626,38 +2642,28 @@ void CanvasItemEditor::_update_cursor() { break; case DRAG_LEFT: case DRAG_RIGHT: + c = rotation_array[rotation_array_index]; + break; case DRAG_V_GUIDE: - if (should_switch) { - c = CURSOR_VSIZE; - } else { - c = CURSOR_HSIZE; - } + c = CURSOR_HSIZE; break; case DRAG_TOP: case DRAG_BOTTOM: + c = rotation_array[(rotation_array_index + 2) % 4]; + break; case DRAG_H_GUIDE: - if (should_switch) { - c = CURSOR_HSIZE; - } else { - c = CURSOR_VSIZE; - } + c = CURSOR_VSIZE; break; case DRAG_TOP_LEFT: case DRAG_BOTTOM_RIGHT: + c = rotation_array[(rotation_array_index + 3) % 4]; + break; case DRAG_DOUBLE_GUIDE: - if (should_switch) { - c = CURSOR_BDIAGSIZE; - } else { - c = CURSOR_FDIAGSIZE; - } + c = CURSOR_FDIAGSIZE; break; case DRAG_TOP_RIGHT: case DRAG_BOTTOM_LEFT: - if (should_switch) { - c = CURSOR_FDIAGSIZE; - } else { - c = CURSOR_BDIAGSIZE; - } + c = rotation_array[(rotation_array_index + 1) % 4]; break; case DRAG_MOVE: c = CURSOR_MOVE; @@ -3620,7 +3626,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans Transform2D parent_xform = p_parent_xform; Transform2D canvas_xform = p_canvas_xform; - if (canvas_item && !canvas_item->is_set_as_toplevel()) { + if (canvas_item && !canvas_item->is_set_as_top_level()) { parent_xform = parent_xform * canvas_item->get_transform(); } else { CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); @@ -3689,7 +3695,7 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p Transform2D parent_xform = p_parent_xform; Transform2D canvas_xform = p_canvas_xform; - if (canvas_item && !canvas_item->is_set_as_toplevel()) { + if (canvas_item && !canvas_item->is_set_as_top_level()) { parent_xform = parent_xform * canvas_item->get_transform(); } else { CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 944bf9913c..8580bdf47e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -6810,7 +6810,7 @@ void EditorNode3DGizmoPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material); //, DEFVAL(Ref<EditorNode3DGizmo>())); BIND_VMETHOD(MethodInfo(Variant::STRING, "get_name")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "get_priority")); + BIND_VMETHOD(MethodInfo(Variant::INT, "get_priority")); BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_be_hidden")); BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_selectable_when_hidden")); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 03ed54ae1b..1651de4048 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -47,6 +47,27 @@ #include "servers/display_server.h" #include "servers/rendering/shader_types.h" +struct FloatConstantDef { + String name; + float value; + String desc; +}; + +static FloatConstantDef float_constant_defs[] = { + { "E", Math_E, TTR("E constant (2.718282). Represents the base of the natural logarithm.") }, + { "Epsilon", CMP_EPSILON, TTR("Epsilon constant (0.00001). Smallest possible scalar number.") }, + { "Phi", 1.618034f, TTR("Phi constant (1.618034). Golden ratio.") }, + { "Pi/4", Math_PI / 4, TTR("Pi/4 constant (0.785398) or 45 degrees.") }, + { "Pi/2", Math_PI / 2, TTR("Pi/2 constant (1.570796) or 90 degrees.") }, + { "Pi", Math_PI, TTR("Pi constant (3.141593) or 180 degrees.") }, + { "Tau", Math_TAU, TTR("Tau constant (6.283185) or 360 degrees.") }, + { "Sqrt2", Math_SQRT2, TTR("Sqrt2 constant (1.414214). Square root of 2.") } +}; + +const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConstantDef); + +/////////////////// + Control *VisualShaderNodePlugin::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { if (get_script_instance()) { return get_script_instance()->call("create_editor", p_parent_resource, p_node); @@ -86,6 +107,7 @@ void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("update_node_deferred", &VisualShaderGraphPlugin::update_node_deferred); ClassDB::bind_method("set_input_port_default_value", &VisualShaderGraphPlugin::set_input_port_default_value); ClassDB::bind_method("set_uniform_name", &VisualShaderGraphPlugin::set_uniform_name); + ClassDB::bind_method("update_constant", &VisualShaderGraphPlugin::update_constant); } void VisualShaderGraphPlugin::register_shader(VisualShader *p_shader) { @@ -183,10 +205,42 @@ void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_ } } +int VisualShaderGraphPlugin::get_constant_index(float p_constant) const { + for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { + if (Math::is_equal_approx(p_constant, float_constant_defs[i].value)) { + return i + 1; + } + } + return 0; +} + +void VisualShaderGraphPlugin::update_constant(VisualShader::Type p_type, int p_node_id) { + if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id) || !links[p_node_id].const_op) { + return; + } + VisualShaderNodeFloatConstant *float_const = Object::cast_to<VisualShaderNodeFloatConstant>(links[p_node_id].visual_node); + if (!float_const) { + return; + } + links[p_node_id].const_op->select(get_constant_index(float_const->get_constant())); + links[p_node_id].graph_node->set_size(Size2(-1, -1)); +} + +void VisualShaderGraphPlugin::update_node_size(int p_node_id) { + if (!links.has(p_node_id)) { + return; + } + links[p_node_id].graph_node->set_size(Size2(-1, -1)); +} + void VisualShaderGraphPlugin::register_default_input_button(int p_node_id, int p_port_id, Button *p_button) { links[p_node_id].input_ports.insert(p_port_id, { p_button }); } +void VisualShaderGraphPlugin::register_constant_option_btn(int p_node_id, OptionButton *p_button) { + links[p_node_id].const_op = p_button; +} + void VisualShaderGraphPlugin::update_uniform_refs() { for (Map<int, Link>::Element *E = links.front(); E; E = E->next()) { VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E->get().visual_node); @@ -230,7 +284,7 @@ void VisualShaderGraphPlugin::make_dirty(bool p_enabled) { } void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) { - links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr }); + links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr }); } void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) { @@ -337,6 +391,23 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } } + Ref<VisualShaderNodeFloatConstant> float_const = vsnode; + if (float_const.is_valid()) { + HBoxContainer *hbox = memnew(HBoxContainer); + + hbox->add_child(custom_editor); + OptionButton *btn = memnew(OptionButton); + hbox->add_child(btn); + register_constant_option_btn(p_id, btn); + btn->add_item(""); + for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { + btn->add_item(float_constant_defs[i].name); + } + btn->select(get_constant_index(float_const->get_constant())); + btn->connect("item_selected", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_float_constant_selected), varray(p_id)); + custom_editor = hbox; + } + if (custom_editor && !vsnode->is_use_prop_slots() && vsnode->get_output_port_count() > 0 && vsnode->get_output_port_name(0) == "" && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == "")) { //will be embedded in first port } else if (custom_editor) { @@ -2450,6 +2521,30 @@ void VisualShaderEditor::_uniform_select_item(Ref<VisualShaderNodeUniformRef> p_ undo_redo->commit_action(); } +void VisualShaderEditor::_float_constant_selected(int p_index, int p_node) { + if (p_index == 0) { + graph_plugin->update_node_size(p_node); + return; + } + + --p_index; + + ERR_FAIL_INDEX(p_index, MAX_FLOAT_CONST_DEFS); + + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeFloatConstant> node = visual_shader->get_node(type, p_node); + if (!node.is_valid()) { + return; + } + + undo_redo->create_action(TTR("Set constant")); + undo_redo->add_do_method(node.ptr(), "set_constant", float_constant_defs[p_index].value); + undo_redo->add_undo_method(node.ptr(), "set_constant", node->get_constant()); + undo_redo->add_do_method(graph_plugin.ptr(), "update_constant", type, p_node); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_constant", type, p_node); + undo_redo->commit_action(); +} + void VisualShaderEditor::_member_filter_changed(const String &p_text) { _update_options_menu(); } @@ -2695,6 +2790,7 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_update_uniforms", &VisualShaderEditor::_update_uniforms); ClassDB::bind_method("_set_mode", &VisualShaderEditor::_set_mode); ClassDB::bind_method("_nodes_dragged", &VisualShaderEditor::_nodes_dragged); + ClassDB::bind_method("_float_constant_selected", &VisualShaderEditor::_float_constant_selected); ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw); ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw); @@ -3163,15 +3259,9 @@ VisualShaderEditor::VisualShaderEditor() { //CONSTANTS - add_options.push_back(AddOption("E", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("E constant (2.718282). Represents the base of the natural logarithm."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_E)); - add_options.push_back(AddOption("Epsilon", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Epsilon constant (0.00001). Smallest possible scalar number."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, CMP_EPSILON)); - add_options.push_back(AddOption("Phi", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Phi constant (1.618034). Golden ratio."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, 1.618034f)); - add_options.push_back(AddOption("Pi/4", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Pi/4 constant (0.785398) or 45 degrees."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_PI / 4)); - add_options.push_back(AddOption("Pi/2", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Pi/2 constant (1.570796) or 90 degrees."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_PI / 2)); - add_options.push_back(AddOption("Pi", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Pi constant (3.141593) or 180 degrees."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_PI)); - add_options.push_back(AddOption("Tau", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Tau constant (6.283185) or 360 degrees."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_TAU)); - add_options.push_back(AddOption("Sqrt2", "Scalar", "Constants", "VisualShaderNodeFloatConstant", TTR("Sqrt2 constant (1.414214). Square root of 2."), -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, Math_SQRT2)); - + for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { + add_options.push_back(AddOption(float_constant_defs[i].name, "Scalar", "Constants", "VisualShaderNodeFloatConstant", float_constant_defs[i].desc, -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, float_constant_defs[i].value)); + } // FUNCTIONS add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeFloatFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_SCALAR)); @@ -3553,6 +3643,9 @@ public: if (p_property != "constant") { undo_redo->add_do_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); undo_redo->add_undo_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); + } else { + undo_redo->add_do_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_constant", shader_type, node_id); + undo_redo->add_undo_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_constant", shader_type, node_id); } undo_redo->commit_action(); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index b4c5ff886a..d43af82238 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -72,6 +72,7 @@ private: Map<int, Port> output_ports; VBoxContainer *preview_box; LineEdit *uniform_name; + OptionButton *const_op; }; Ref<VisualShader> visual_shader; @@ -88,6 +89,8 @@ public: void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node); void register_output_port(int p_id, int p_port, TextureButton *p_button); void register_uniform_name(int p_id, LineEdit *p_uniform_name); + void register_default_input_button(int p_node_id, int p_port_id, Button *p_button); + void register_constant_option_btn(int p_node_id, OptionButton *p_button); void clear_links(); void set_shader_type(VisualShader::Type p_type); bool is_preview_visible(int p_id) const; @@ -104,9 +107,11 @@ public: void set_node_size(VisualShader::Type p_type, int p_id, const Vector2 &p_size); void refresh_node_ports(VisualShader::Type p_type, int p_node); void set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, Variant p_value); - void register_default_input_button(int p_node_id, int p_port_id, Button *p_button); void update_uniform_refs(); void set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name); + void update_constant(VisualShader::Type p_type, int p_node_id); + int get_constant_index(float p_constant) const; + void update_node_size(int p_node_id); VisualShader::Type get_shader_type() const; VisualShaderGraphPlugin(); @@ -331,6 +336,8 @@ class VisualShaderEditor : public VBoxContainer { void _input_select_item(Ref<VisualShaderNodeInput> input, String name); void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name); + void _float_constant_selected(int p_index, int p_node); + VisualShader::Type get_current_shader_type() const; void _add_input_port(int p_node, int p_port, int p_port_type, const String &p_name); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 5a504da397..3dee4a229f 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -251,7 +251,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (can_rename) { //should be can edit.. String warning = p_node->get_configuration_warning(); - if (warning != String()) { + if (!warning.empty()) { item->add_button(0, get_theme_icon("NodeWarning", "EditorIcons"), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + p_node->get_configuration_warning()); } diff --git a/main/main.cpp b/main/main.cpp index a2ea085618..5ebd0138d3 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1369,6 +1369,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph "0,33200,1,or_greater")); // No negative numbers GLOBAL_DEF("display/window/ios/hide_home_indicator", true); + GLOBAL_DEF("input_devices/pointing/ios/touch_delay", 0.150); Engine::get_singleton()->set_frame_delay(frame_delay); diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index 9a1143a21a..d8da520ed7 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -386,7 +386,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -416,7 +416,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h index 5a2c50e213..29e09411ff 100644 --- a/modules/arkit/arkit_interface.h +++ b/modules/arkit/arkit_interface.h @@ -44,6 +44,15 @@ // forward declaration for some needed objects class ARKitShader; +#ifdef __OBJC__ + +typedef ARAnchor GodotARAnchor; + +#else + +typedef void GodotARAnchor; +#endif + class ARKitInterface : public XRInterface { GDCLASS(ARKitInterface, XRInterface); @@ -115,8 +124,8 @@ public: virtual void process() override; // called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm) - void _add_or_update_anchor(void *p_anchor); - void _remove_anchor(void *p_anchor); + void _add_or_update_anchor(GodotARAnchor *p_anchor); + void _remove_anchor(GodotARAnchor *p_anchor); ARKitInterface(); ~ARKitInterface(); diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 3fb2cc933d..e8fa023ac7 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -306,12 +306,10 @@ void ARKitInterface::uninitialize() { remove_all_anchors(); if (@available(iOS 11.0, *)) { - [ar_session release]; - ar_session = NULL; + ar_session = nil; } - [ar_delegate release]; - ar_delegate = NULL; + ar_delegate = nil; initialized = false; session_was_started = false; } @@ -687,7 +685,7 @@ void ARKitInterface::process() { } } -void ARKitInterface::_add_or_update_anchor(void *p_anchor) { +void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) { // _THREAD_SAFE_METHOD_ if (@available(iOS 11.0, *)) { @@ -749,7 +747,7 @@ void ARKitInterface::_add_or_update_anchor(void *p_anchor) { } } -void ARKitInterface::_remove_anchor(void *p_anchor) { +void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) { // _THREAD_SAFE_METHOD_ if (@available(iOS 11.0, *)) { @@ -768,7 +766,7 @@ ARKitInterface::ARKitInterface() { plane_detection_is_enabled = false; light_estimation_is_enabled = false; if (@available(iOS 11.0, *)) { - ar_session = NULL; + ar_session = nil; } z_near = 0.01; z_far = 1000.0; diff --git a/modules/bullet/bullet_types_converter.cpp b/modules/bullet/bullet_types_converter.cpp index 7ecad9b78a..09b90fe09e 100644 --- a/modules/bullet/bullet_types_converter.cpp +++ b/modules/bullet/bullet_types_converter.cpp @@ -101,9 +101,9 @@ void UNSCALE_BT_BASIS(btTransform &scaledBasis) { btVector3 column2 = basis.getColumn(2); // Check for zero scaling. - if (btFuzzyZero(column0[0])) { - if (btFuzzyZero(column1[1])) { - if (btFuzzyZero(column2[2])) { + if (column0.fuzzyZero()) { + if (column1.fuzzyZero()) { + if (column2.fuzzyZero()) { // All dimensions are fuzzy zero. Create a default basis. column0 = btVector3(1, 0, 0); column1 = btVector3(0, 1, 0); @@ -115,7 +115,7 @@ void UNSCALE_BT_BASIS(btTransform &scaledBasis) { column0 = column1.cross(column2); } } else { // Column 1 scale not fuzzy zero. - if (btFuzzyZero(column2[2])) { + if (column2.fuzzyZero()) { // Create two vectors othogonal to column 1. // Ensure that a default basis is created if column 1 = <0, 1, 0> column0 = btVector3(column1[1], -column1[0], 0); @@ -126,8 +126,8 @@ void UNSCALE_BT_BASIS(btTransform &scaledBasis) { } } } else { // Column 0 scale not fuzzy zero. - if (btFuzzyZero(column1[1])) { - if (btFuzzyZero(column2[2])) { + if (column1.fuzzyZero()) { + if (column2.fuzzyZero()) { // Create two vectors orthogonal to column 0. // Ensure that a default basis is created if column 0 = <1, 0, 0> column2 = btVector3(-column0[2], 0, column0[0]); @@ -137,7 +137,7 @@ void UNSCALE_BT_BASIS(btTransform &scaledBasis) { column1 = column2.cross(column0); } } else { // Column 0 and column 1 scales not fuzzy zero. - if (btFuzzyZero(column2[2])) { + if (column2.fuzzyZero()) { // Create column 2 orthogonal to column 0 and column 1. column2 = column0.cross(column1); } diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm index c10b13b2af..e4cb928805 100644 --- a/modules/camera/camera_ios.mm +++ b/modules/camera/camera_ios.mm @@ -124,18 +124,12 @@ if (output) { [self removeOutput:output]; [output setSampleBufferDelegate:nil queue:NULL]; - [output release]; output = nil; } [self commitConfiguration]; } -- (void)dealloc { - // bye bye - [super dealloc]; -} - - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // This gets called every time our camera has a new image for us to process. // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. @@ -272,7 +266,6 @@ CameraFeedIOS::CameraFeedIOS() { void CameraFeedIOS::set_device(AVCaptureDevice *p_device) { device = p_device; - [device retain]; // get some info NSString *device_name = p_device.localizedName; @@ -286,14 +279,12 @@ void CameraFeedIOS::set_device(AVCaptureDevice *p_device) { }; CameraFeedIOS::~CameraFeedIOS() { - if (capture_session != NULL) { - [capture_session release]; - capture_session = NULL; + if (capture_session) { + capture_session = nil; }; - if (device != NULL) { - [device release]; - device = NULL; + if (device) { + device = nil; }; }; @@ -312,8 +303,7 @@ void CameraFeedIOS::deactivate_feed() { // end camera capture if we have one if (capture_session) { [capture_session cleanup]; - [capture_session release]; - capture_session = NULL; + capture_session = nil; }; }; @@ -347,8 +337,6 @@ void CameraFeedIOS::deactivate_feed() { // remove notifications [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; - - [super dealloc]; } @end @@ -453,5 +441,5 @@ CameraIOS::CameraIOS() { }; CameraIOS::~CameraIOS() { - [device_notifications release]; + device_notifications = nil; }; diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 613039754f..512452a8df 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -767,7 +767,7 @@ Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative. [codeblock] for i in range(-3, 4): - print("%2.0f %2.0f %2.0f" % [i, i % 3, posmod(i, 3)]) + print("%2d %2d %2d" % [i, i % 3, posmod(i, 3)]) [/codeblock] Produces: [codeblock] diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 3519038ae6..e70e3f7272 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1044,8 +1044,10 @@ GDScript::~GDScript() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } } @@ -1451,8 +1453,10 @@ GDScriptInstance::~GDScriptInstance() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } if (script.is_valid() && owner) { diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 57fbc5bfc0..4dccb03369 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -11,6 +11,8 @@ </description> <tutorials> <link title="Using gridmaps">https://docs.godotengine.org/en/latest/tutorials/3d/using_gridmaps.html</link> + <link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link> + <link title="3D Kinematic Character Demo">https://godotengine.org/asset-library/asset/126</link> </tutorials> <methods> <method name="clear"> diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm index e3d1a647fd..dc23c06eba 100644 --- a/modules/mono/mono_gd/support/ios_support.mm +++ b/modules/mono/mono_gd/support/ios_support.mm @@ -131,8 +131,7 @@ GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t * NSTimeZone *tz = nil; if (p_name) { NSString *n = [[NSString alloc] initWithUTF8String:p_name]; - tz = [[[NSTimeZone alloc] initWithName:n] autorelease]; - [n release]; + tz = [[NSTimeZone alloc] initWithName:n]; } else { tz = [NSTimeZone localTimeZone]; } diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index 49c77468ed..1dd37dabe5 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -18,7 +18,9 @@ iphone_lib = [ "godot_view.mm", "display_layer.mm", "godot_view_renderer.mm", - "godot_view_gesture_recognizer.m", + "godot_view_gesture_recognizer.mm", + "device_metrics.m", + "native_video_view.m", ] env_ios = env.Clone() diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 7edbcc4667..40a63d7ad2 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -62,7 +62,7 @@ static ViewController *mainViewController = nil; CGRect windowBounds = [[UIScreen mainScreen] bounds]; // Create a full-screen window - self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease]; + self.window = [[UIWindow alloc] initWithFrame:windowBounds]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; @@ -140,7 +140,6 @@ static ViewController *mainViewController = nil; - (void)dealloc { self.window = nil; - [super dealloc]; } @end diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 66579c1ad7..5ebabdd3dc 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -129,7 +129,7 @@ def configure(env): detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( - "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" + "-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" ' "-DIBOutlet=__attribute__((iboutlet))"' @@ -141,7 +141,7 @@ def configure(env): detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( - "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" + "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0" " -isysroot $IPHONESDK".split() diff --git a/platform/iphone/device_metrics.h b/platform/iphone/device_metrics.h new file mode 100644 index 0000000000..6d0ff49077 --- /dev/null +++ b/platform/iphone/device_metrics.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* device_metrics.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#import <Foundation/Foundation.h> + +@interface GodotDeviceMetrics : NSObject + +@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList; + +@end diff --git a/platform/iphone/device_metrics.m b/platform/iphone/device_metrics.m new file mode 100644 index 0000000000..747872bc49 --- /dev/null +++ b/platform/iphone/device_metrics.m @@ -0,0 +1,152 @@ +/*************************************************************************/ +/* device_metrics.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#import "device_metrics.h" + +@implementation GodotDeviceMetrics + ++ (NSDictionary *)dpiList { + return @{ + @[ + @"iPad1,1", + @"iPad2,1", + @"iPad2,2", + @"iPad2,3", + @"iPad2,4", + ] : @132, + @[ + @"iPhone1,1", + @"iPhone1,2", + @"iPhone2,1", + @"iPad2,5", + @"iPad2,6", + @"iPad2,7", + @"iPod1,1", + @"iPod2,1", + @"iPod3,1", + ] : @163, + @[ + @"iPad3,1", + @"iPad3,2", + @"iPad3,3", + @"iPad3,4", + @"iPad3,5", + @"iPad3,6", + @"iPad4,1", + @"iPad4,2", + @"iPad4,3", + @"iPad5,3", + @"iPad5,4", + @"iPad6,3", + @"iPad6,4", + @"iPad6,7", + @"iPad6,8", + @"iPad6,11", + @"iPad6,12", + @"iPad7,1", + @"iPad7,2", + @"iPad7,3", + @"iPad7,4", + @"iPad7,5", + @"iPad7,6", + @"iPad7,11", + @"iPad7,12", + @"iPad8,1", + @"iPad8,2", + @"iPad8,3", + @"iPad8,4", + @"iPad8,5", + @"iPad8,6", + @"iPad8,7", + @"iPad8,8", + @"iPad8,9", + @"iPad8,10", + @"iPad8,11", + @"iPad8,12", + @"iPad11,3", + @"iPad11,4", + ] : @264, + @[ + @"iPhone3,1", + @"iPhone3,2", + @"iPhone3,3", + @"iPhone4,1", + @"iPhone5,1", + @"iPhone5,2", + @"iPhone5,3", + @"iPhone5,4", + @"iPhone6,1", + @"iPhone6,2", + @"iPhone7,2", + @"iPhone8,1", + @"iPhone8,4", + @"iPhone9,1", + @"iPhone9,3", + @"iPhone10,1", + @"iPhone10,4", + @"iPhone11,8", + @"iPhone12,1", + @"iPhone12,8", + @"iPad4,4", + @"iPad4,5", + @"iPad4,6", + @"iPad4,7", + @"iPad4,8", + @"iPad4,9", + @"iPad5,1", + @"iPad5,2", + @"iPad11,1", + @"iPad11,2", + @"iPod4,1", + @"iPod5,1", + @"iPod7,1", + @"iPod9,1", + ] : @326, + @[ + @"iPhone7,1", + @"iPhone8,2", + @"iPhone9,2", + @"iPhone9,4", + @"iPhone10,2", + @"iPhone10,5", + ] : @401, + @[ + @"iPhone10,3", + @"iPhone10,6", + @"iPhone11,2", + @"iPhone11,4", + @"iPhone11,6", + @"iPhone12,3", + @"iPhone12,5", + ] : @458, + }; +} + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm index 5ec94fb471..2716538b89 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/iphone/display_layer.mm @@ -124,11 +124,8 @@ } if (context) { - [context release]; context = nil; } - - [super dealloc]; } - (BOOL)createFramebuffer { diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index eea87cecc9..d456f9cbf6 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -32,8 +32,10 @@ #import "app_delegate.h" #include "core/io/file_access_pack.h" #include "core/project_settings.h" +#import "device_metrics.h" #import "godot_view.h" #include "ios.h" +#import "native_video_view.h" #include "os_iphone.h" #import "view_controller.h" @@ -41,120 +43,6 @@ #import <sys/utsname.h> static const float kDisplayServerIPhoneAcceleration = 1; -static NSDictionary *iOSModelToDPI = @{ - @[ - @"iPad1,1", - @"iPad2,1", - @"iPad2,2", - @"iPad2,3", - @"iPad2,4", - ] : @132, - @[ - @"iPhone1,1", - @"iPhone1,2", - @"iPhone2,1", - @"iPad2,5", - @"iPad2,6", - @"iPad2,7", - @"iPod1,1", - @"iPod2,1", - @"iPod3,1", - ] : @163, - @[ - @"iPad3,1", - @"iPad3,2", - @"iPad3,3", - @"iPad3,4", - @"iPad3,5", - @"iPad3,6", - @"iPad4,1", - @"iPad4,2", - @"iPad4,3", - @"iPad5,3", - @"iPad5,4", - @"iPad6,3", - @"iPad6,4", - @"iPad6,7", - @"iPad6,8", - @"iPad6,11", - @"iPad6,12", - @"iPad7,1", - @"iPad7,2", - @"iPad7,3", - @"iPad7,4", - @"iPad7,5", - @"iPad7,6", - @"iPad7,11", - @"iPad7,12", - @"iPad8,1", - @"iPad8,2", - @"iPad8,3", - @"iPad8,4", - @"iPad8,5", - @"iPad8,6", - @"iPad8,7", - @"iPad8,8", - @"iPad8,9", - @"iPad8,10", - @"iPad8,11", - @"iPad8,12", - @"iPad11,3", - @"iPad11,4", - ] : @264, - @[ - @"iPhone3,1", - @"iPhone3,2", - @"iPhone3,3", - @"iPhone4,1", - @"iPhone5,1", - @"iPhone5,2", - @"iPhone5,3", - @"iPhone5,4", - @"iPhone6,1", - @"iPhone6,2", - @"iPhone7,2", - @"iPhone8,1", - @"iPhone8,4", - @"iPhone9,1", - @"iPhone9,3", - @"iPhone10,1", - @"iPhone10,4", - @"iPhone11,8", - @"iPhone12,1", - @"iPhone12,8", - @"iPad4,4", - @"iPad4,5", - @"iPad4,6", - @"iPad4,7", - @"iPad4,8", - @"iPad4,9", - @"iPad5,1", - @"iPad5,2", - @"iPad11,1", - @"iPad11,2", - @"iPod4,1", - @"iPod5,1", - @"iPod7,1", - @"iPod9,1", - ] : @326, - @[ - @"iPhone7,1", - @"iPhone8,2", - @"iPhone9,2", - @"iPhone9,4", - @"iPhone10,2", - @"iPhone10,5", - ] : @401, - @[ - @"iPhone10,3", - @"iPhone10,6", - @"iPhone11,2", - @"iPhone11,4", - @"iPhone11,6", - @"iPhone12,3", - @"iPhone12,5", - ] : @458, -}; DisplayServerIPhone *DisplayServerIPhone::get_singleton() { return (DisplayServerIPhone *)DisplayServer::get_singleton(); @@ -383,8 +271,7 @@ void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); }; -void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, - float p_z) { +void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) { // Found out the Z should not be negated! Pass as is! Vector3 v_accelerometer = Vector3( p_x / kDisplayServerIPhoneAcceleration, @@ -392,39 +279,6 @@ void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, p_z / kDisplayServerIPhoneAcceleration); Input::get_singleton()->set_accelerometer(v_accelerometer); - - /* - if (p_x != last_accel.x) { - //printf("updating accel x %f\n", p_x); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_0; - ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); - last_accel.x = p_x; - queue_event(ev); - }; - if (p_y != last_accel.y) { - //printf("updating accel y %f\n", p_y); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_1; - ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); - last_accel.y = p_y; - queue_event(ev); - }; - if (p_z != last_accel.z) { - //printf("updating accel z %f\n", p_z); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_2; - ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); - last_accel.z = p_z; - queue_event(ev); - }; - */ }; void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { @@ -516,6 +370,8 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; + for (NSArray *keyArray in iOSModelToDPI) { if ([keyArray containsObject:string]) { NSNumber *value = iOSModelToDPI[keyArray]; @@ -523,7 +379,26 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { } } - return 163; + // If device wasn't found in dictionary + // make a best guess from device metrics. + CGFloat scale = [UIScreen mainScreen].scale; + + UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; + + switch (idiom) { + case UIUserInterfaceIdiomPad: + return scale == 2 ? 264 : 132; + case UIUserInterfaceIdiomPhone: { + if (scale == 3) { + CGFloat nativeScale = [UIScreen mainScreen].nativeScale; + return nativeScale == 3 ? 458 : 401; + } + + return 326; + } + default: + return 72; + } } float DisplayServerIPhone::screen_get_scale(int p_screen) const { @@ -716,7 +591,7 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); - NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease]; + NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()]; NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; @@ -731,22 +606,22 @@ Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, Stri } bool DisplayServerIPhone::native_video_is_playing() const { - return [AppDelegate.viewController isVideoPlaying]; + return [AppDelegate.viewController.videoView isVideoPlaying]; } void DisplayServerIPhone::native_video_pause() { if (native_video_is_playing()) { - [AppDelegate.viewController pauseVideo]; + [AppDelegate.viewController.videoView pauseVideo]; } } void DisplayServerIPhone::native_video_unpause() { - [AppDelegate.viewController unpauseVideo]; + [AppDelegate.viewController.videoView unpauseVideo]; }; void DisplayServerIPhone::native_video_stop() { if (native_video_is_playing()) { - [AppDelegate.viewController stopVideo]; + [AppDelegate.viewController.videoView stopVideo]; } } diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index 1e9a68fe48..6705674ac6 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -48,7 +48,7 @@ class GameCenter : public Object { void return_connect_error(const char *p_error_description); public: - void connect(); + Error authenticate(); bool is_authenticated(); Error post_score(Dictionary p_score); diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 4481775c32..0f8c0100c3 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -52,6 +52,7 @@ extern "C" { GameCenter *GameCenter::instance = NULL; void GameCenter::_bind_methods() { + ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate); ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated); ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score); @@ -66,40 +67,28 @@ void GameCenter::_bind_methods() { ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event); }; -void GameCenter::return_connect_error(const char *p_error_description) { - authenticated = false; - Dictionary ret; - ret["type"] = "authentication"; - ret["result"] = "error"; - ret["error_code"] = 0; - ret["error_description"] = p_error_description; - pending_events.push_back(ret); -} - -void GameCenter::connect() { +Error GameCenter::authenticate() { //if this class isn't available, game center isn't implemented if ((NSClassFromString(@"GKLocalPlayer")) == nil) { - return_connect_error("GameCenter not available"); - return; + return ERR_UNAVAILABLE; } GKLocalPlayer *player = [GKLocalPlayer localPlayer]; - if (![player respondsToSelector:@selector(authenticateHandler)]) { - return_connect_error("GameCenter doesn't respond to 'authenticateHandler'"); - return; - } + ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE); ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; - if (!root_controller) { - return_connect_error("Window doesn't have root ViewController"); - return; - } + ERR_FAIL_COND_V(!root_controller, FAILED); // This handler is called several times. First when the view needs to be shown, then again // after the view is cancelled or the user logs in. Or if the user's already logged in, it's // called just once to confirm they're authenticated. This is why no result needs to be specified // in the presentViewController phase. In this case, more calls to this function will follow. + _weakify(root_controller); + _weakify(player); player.authenticateHandler = (^(UIViewController *controller, NSError *error) { + _strongify(root_controller); + _strongify(player); + if (controller) { [root_controller presentViewController:controller animated:YES completion:nil]; } else { @@ -126,6 +115,8 @@ void GameCenter::connect() { pending_events.push_back(ret); }; }); + + return OK; }; bool GameCenter::is_authenticated() { @@ -137,8 +128,8 @@ Error GameCenter::post_score(Dictionary p_score) { float score = p_score["score"]; String category = p_score["category"]; - NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease]; - GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease]; + NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()]; + GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str]; reporter.value = score; ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE); @@ -166,8 +157,8 @@ Error GameCenter::award_achievement(Dictionary p_params) { String name = p_params["name"]; float progress = p_params["progress"]; - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; - GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease]; + NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()]; + GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str]; ERR_FAIL_COND_V(!achievement, FAILED); ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE); @@ -311,7 +302,7 @@ Error GameCenter::show_game_center(Dictionary p_params) { } } - GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease]; + GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init]; ERR_FAIL_COND_V(!controller, FAILED); ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; @@ -323,7 +314,7 @@ Error GameCenter::show_game_center(Dictionary p_params) { controller.leaderboardIdentifier = nil; if (p_params.has("leaderboard_name")) { String name = p_params["leaderboard_name"]; - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; + NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()]; controller.leaderboardIdentifier = name_str; } } diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 090b772947..6d95276f37 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -49,10 +49,8 @@ int add_path(int p_argc, char **p_args) { } p_args[p_argc++] = (char *)"--path"; - [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; p_args[p_argc] = NULL; - [str release]; return p_argc; }; @@ -68,9 +66,7 @@ int add_cmdline(int p_argc, char **p_args) { if (!str) { continue; } - [str retain]; // @todo delete these at some point p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - [str release]; }; p_args[p_argc] = NULL; diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index c0a31549c4..3b4344c46d 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -145,8 +145,6 @@ static const int max_touches = 8; if (self.delayGestureRecognizer) { self.delayGestureRecognizer = nil; } - - [super dealloc]; } - (void)godot_commonInit { @@ -156,7 +154,7 @@ static const int max_touches = 8; // Configure and start accelerometer if (!self.motionManager) { - self.motionManager = [[[CMMotionManager alloc] init] autorelease]; + self.motionManager = [[CMMotionManager alloc] init]; if (self.motionManager.deviceMotionAvailable) { self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical]; @@ -169,7 +167,6 @@ static const int max_touches = 8; GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; self.delayGestureRecognizer = gestureRecognizer; [self addGestureRecognizer:self.delayGestureRecognizer]; - [gestureRecognizer release]; } - (void)stopRendering { @@ -204,14 +201,11 @@ static const int max_touches = 8; if (self.useCADisplayLink) { self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; - // if (@available(iOS 10, *)) { - self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval); - // } else { - // // Approximate frame rate - // // assumes device refreshes at 60 fps - // int frameInterval = (int)floor(self.renderingInterval * 60.0f); - // [self.displayLink setFrameInterval:frameInterval]; - // } + // Approximate frame rate + // assumes device refreshes at 60 fps + int displayFPS = (NSInteger)(1.0 / self.renderingInterval); + + self.displayLink.preferredFramesPerSecond = displayFPS; // Setup DisplayLink in main thread [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h index 8d84914712..1431a9fb89 100644 --- a/platform/iphone/godot_view_gesture_recognizer.h +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -39,6 +39,8 @@ @interface GodotViewGestureRecognizer : UIGestureRecognizer +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; + - (instancetype)init; @end diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.mm index 377ccd52a5..71367a629c 100644 --- a/platform/iphone/godot_view_gesture_recognizer.m +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_view_gesture_recognizer.m */ +/* godot_view_gesture_recognizer.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,8 +30,7 @@ #import "godot_view_gesture_recognizer.h" -// Using same delay interval that is used for `UIScrollView` -const NSTimeInterval kGLGestureDelayInterval = 0.150; +#include "core/project_settings.h" // Minimum distance for touches to move to fire // a delay timer before scheduled time. @@ -41,6 +40,12 @@ const CGFloat kGLGestureMovementDistance = 0.5; @interface GodotViewGestureRecognizer () +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@interface GodotViewGestureRecognizer () + // Timer used to delay begin touch message. // Should work as simple emulation of UIDelayedAction @property(strong, nonatomic) NSTimer *delayTimer; @@ -60,6 +65,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delaysTouchesBegan = YES; self.delaysTouchesEnded = YES; + self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); + return self; } @@ -76,8 +83,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; if (self.delayedEvent) { self.delayedEvent = nil; } - - [super dealloc]; } - (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { @@ -87,7 +92,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delayedEvent = event; self.delayTimer = [NSTimer - scheduledTimerWithTimeInterval:kGLGestureDelayInterval + scheduledTimerWithTimeInterval:self.delayTimeInterval target:self selector:@selector(fireDelayedTouches:) userInfo:nil @@ -109,7 +114,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; [self delayTouches:cleared andEvent:event]; - [cleared release]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { @@ -130,17 +134,14 @@ const CGFloat kGLGestureMovementDistance = 0.5; if (distance > kGLGestureMovementDistance) { [self.delayTimer fire]; [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; return; } } - [cleared release]; return; } [self.view touchesMoved:cleared withEvent:event]; - [cleared release]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { @@ -148,7 +149,6 @@ const CGFloat kGLGestureMovementDistance = 0.5; NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; [self.view touchesEnded:cleared withEvent:event]; - [cleared release]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index d3086e6cea..3d81349883 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -150,7 +150,7 @@ Variant nsobject_to_variant(NSObject *object) { NSObject *variant_to_nsobject(Variant v) { if (v.get_type() == Variant::STRING) { - return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease]; + return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()]; } else if (v.get_type() == Variant::FLOAT) { return [NSNumber numberWithDouble:(double)v]; } else if (v.get_type() == Variant::INT) { @@ -158,11 +158,11 @@ NSObject *variant_to_nsobject(Variant v) { } else if (v.get_type() == Variant::BOOL) { return [NSNumber numberWithBool:BOOL((bool)v)]; } else if (v.get_type() == Variant::DICTIONARY) { - NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease]; + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; Dictionary dic = v; Array keys = dic.keys(); for (int i = 0; i < keys.size(); ++i) { - NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()]; NSObject *value = variant_to_nsobject(dic[keys[i]]); if (key == NULL || value == NULL) { @@ -173,7 +173,7 @@ NSObject *variant_to_nsobject(Variant v) { } return result; } else if (v.get_type() == Variant::ARRAY) { - NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease]; + NSMutableArray *result = [[NSMutableArray alloc] init]; Array arr = v; for (int i = 0; i < arr.size(); ++i) { NSObject *value = variant_to_nsobject(arr[i]); @@ -195,7 +195,7 @@ NSObject *variant_to_nsobject(Variant v) { } Error ICloud::remove_key(String p_param) { - NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()]; NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; @@ -217,7 +217,7 @@ Array ICloud::set_key_values(Dictionary p_params) { String variant_key = keys[i]; Variant variant_value = p_params[variant_key]; - NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()]; if (key == NULL) { error_keys.push_back(variant_key); continue; @@ -238,7 +238,7 @@ Array ICloud::set_key_values(Dictionary p_params) { } Variant ICloud::get_key_value(String p_param) { - NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()]; NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; if (![[store dictionaryRepresentation] objectForKey:key]) { diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 1477f92200..2b973dfbcf 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -56,7 +56,6 @@ static NSArray *latestProducts; [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:self.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:self.price]; - [numberFormatter release]; return formattedString; } @@ -124,8 +123,6 @@ void InAppStore::_bind_methods() { ret["invalid_ids"] = invalid_ids; InAppStore::get_singleton()->_post_event(ret); - - [request release]; }; @end @@ -136,14 +133,14 @@ Error InAppStore::request_product_info(Dictionary p_params) { PackedStringArray pids = p_params["product_ids"]; printf("************ request product info! %i\n", pids.size()); - NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease]; + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()]; for (int i = 0; i < pids.size(); i++) { printf("******** adding %s to product list\n", pids[i].utf8().get_data()); - NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease]; + NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()]; [array addObject:pid]; }; - NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease]; + NSSet *products = [[NSSet alloc] initWithArray:array]; SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products]; ProductsDelegate *delegate = [[ProductsDelegate alloc] init]; @@ -183,30 +180,14 @@ Error InAppStore::restore_purchases() { ret["transaction_id"] = transactionId; NSData *receipt = nil; - int sdk_version = 6; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) { - NSURL *receiptFileURL = nil; - NSBundle *bundle = [NSBundle mainBundle]; - if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) { - // Get the transaction receipt file path location in the app bundle. - receiptFileURL = [bundle appStoreReceiptURL]; - - // Read in the contents of the transaction file. - receipt = [NSData dataWithContentsOfURL:receiptFileURL]; - sdk_version = 7; + int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue]; - } else { - // Fall back to deprecated transaction receipt, - // which is still available in iOS 7. + NSBundle *bundle = [NSBundle mainBundle]; + // Get the transaction receipt file path location in the app bundle. + NSURL *receiptFileURL = [bundle appStoreReceiptURL]; - // Use SKPaymentTransaction's transactionReceipt. - receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - } - - } else { - receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - } + // Read in the contents of the transaction file. + receipt = [NSData dataWithContentsOfURL:receiptFileURL]; NSString *receipt_to_send = nil; if (receipt != nil) { @@ -265,7 +246,7 @@ Error InAppStore::purchase(Dictionary p_params) { printf("purchasing!\n"); ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER); - NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease]; + NSString *pid = [[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()]; SKProduct *product = nil; @@ -307,7 +288,7 @@ void InAppStore::_post_event(Variant p_event) { void InAppStore::_record_purchase(String product_id) { String skey = "purchased/" + product_id; - NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease]; + NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; }; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 6d7699c0c9..d4e099063f 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -30,6 +30,7 @@ #include "ios.h" #import "app_delegate.h" +#import "view_controller.h" #import <UIKit/UIKit.h> #include <sys/sysctl.h> @@ -68,23 +69,9 @@ String iOS::get_model() const { } String iOS::get_rate_url(int p_app_id) const { - String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; - String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID"; - String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"; + String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; - //ios7 before - String ret = templ; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) { - // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131 - ret = templ_iOS7; - } else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { - // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182 - ret = templ_iOS8; - } - - // ios7 for everything? - ret = templ_iOS7.replace("APP_ID", String::num(p_app_id)); + String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); printf("returning rate url %s\n", ret.utf8().get_data()); return ret; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index 6088f1c25c..b0a8076b56 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -131,8 +131,6 @@ void JoypadIPhone::start_processing() { - (void)dealloc { [self finishObserving]; - - [super dealloc]; } - (int)getJoyIdForController:(GCController *)controller { @@ -251,8 +249,13 @@ void JoypadIPhone::start_processing() { // The extended gamepad profile has all the input you could possibly find on // a gamepad but will only be active if your gamepad actually has all of // these... - controller.extendedGamepad.valueChangedHandler = ^( - GCExtendedGamepad *gamepad, GCControllerElement *element) { + _weakify(self); + _weakify(controller); + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { @@ -304,71 +307,33 @@ void JoypadIPhone::start_processing() { Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); }; }; + } else if (controller.microGamepad != nil) { + // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad + _weakify(self); + _weakify(controller); + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); + }; + }; } - // else if (controller.gamepad != nil) { - // // gamepad is the standard profile with 4 buttons, shoulder buttons and a - // // D-pad - // controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, - // GCControllerElement *element) { - // int joy_id = [self getJoyIdForController:controller]; - // - // if (element == gamepad.buttonA) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, - // gamepad.buttonA.isPressed); - // } else if (element == gamepad.buttonB) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, - // gamepad.buttonB.isPressed); - // } else if (element == gamepad.buttonX) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, - // gamepad.buttonX.isPressed); - // } else if (element == gamepad.buttonY) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, - // gamepad.buttonY.isPressed); - // } else if (element == gamepad.leftShoulder) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, - // gamepad.leftShoulder.isPressed); - // } else if (element == gamepad.rightShoulder) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, - // gamepad.rightShoulder.isPressed); - // } else if (element == gamepad.dpad) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, - // gamepad.dpad.up.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, - // gamepad.dpad.down.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, - // gamepad.dpad.left.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, - // gamepad.dpad.right.isPressed); - // }; - // }; - //#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, - // // while we are setting that as the minimum, seems our - // // build environment doesn't like it - // } else if (controller.microGamepad != nil) { - // // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad - // controller.microGamepad.valueChangedHandler = - // ^(GCMicroGamepad *gamepad, GCControllerElement *element) { - // int joy_id = [self getJoyIdForController:controller]; - // - // if (element == gamepad.buttonA) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, - // gamepad.buttonA.isPressed); - // } else if (element == gamepad.buttonX) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, - // gamepad.buttonX.isPressed); - // } else if (element == gamepad.dpad) { - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, - // gamepad.dpad.up.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, - // gamepad.dpad.down.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, - // gamepad.dpad.left.isPressed); - // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, - // gamepad.dpad.right.isPressed); - // }; - // }; - //#endif - // }; ///@TODO need to add support for controller.motion which gives us access to /// the orientation of the device (if supported) diff --git a/platform/iphone/native_video_view.h b/platform/iphone/native_video_view.h new file mode 100644 index 0000000000..d8687b3538 --- /dev/null +++ b/platform/iphone/native_video_view.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* native_video_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +@interface GodotNativeVideoView : UIView + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; +- (BOOL)isVideoPlaying; +- (void)pauseVideo; +- (void)unpauseVideo; +- (void)stopVideo; + +@end diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m new file mode 100644 index 0000000000..a4e9f209f0 --- /dev/null +++ b/platform/iphone/native_video_view.m @@ -0,0 +1,260 @@ +/*************************************************************************/ +/* native_video_view.m */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#import "native_video_view.h" +#import <AVFoundation/AVFoundation.h> + +@interface GodotNativeVideoView () + +@property(strong, nonatomic) AVAsset *avAsset; +@property(strong, nonatomic) AVPlayerItem *avPlayerItem; +@property(strong, nonatomic) AVPlayer *avPlayer; +@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; +@property(assign, nonatomic) CMTime videoCurrentTime; +@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; + +@end + +@implementation GodotNativeVideoView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.isVideoCurrentlyPlaying = NO; + self.videoCurrentTime = kCMTimeZero; + + [self observeVideoAudio]; +} + +- (void)observeVideoAudio { + printf("******** adding observer for sound routing changes\n"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(audioRouteChangeListenerCallback:) + name:AVAudioSessionRouteChangeNotification + object:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { + [self handleVideoOrPlayerStatus]; + } + + if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { + [self handleVideoPlayRate]; + } +} + +// MARK: Video Audio + +- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { + printf("*********** route changed!\n"); + NSDictionary *interuptionDict = notification.userInfo; + + NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + + switch (routeChangeReason) { + case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { + NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); + NSLog(@"Headphone/Line plugged in"); + } break; + case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { + NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); + NSLog(@"Headphone/Line was pulled. Resuming video play...."); + if ([self isVideoPlaying]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + } + } break; + case AVAudioSessionRouteChangeReasonCategoryChange: { + // called at start - also when other audio wants to play + NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); + } break; + } +} + +// MARK: Native Video Player + +- (void)handleVideoOrPlayerStatus { + if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { + [self stopVideo]; + } + + if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { + // NSLog(@"time: %@", self.video_current_time); + [self.avPlayer seekToTime:self.videoCurrentTime]; + self.videoCurrentTime = kCMTimeZero; + } +} + +- (void)handleVideoPlayRate { + NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); + if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + + NSLog(@" . . . PAUSED (or just started)"); + } +} + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { + self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; + + self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; + [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; + + self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; + self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; + + [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:[self.avPlayer currentItem]]; + + [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; + + [self.avPlayerLayer setFrame:self.bounds]; + [self.layer addSublayer:self.avPlayerLayer]; + [self.avPlayer play]; + + AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + + NSMutableArray *allAudioParams = [NSMutableArray array]; + for (id track in audioGroup.options) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:audioTrack]) { + AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; + [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; + [audioInputParams setTrackID:[track trackID]]; + [allAudioParams addObject:audioInputParams]; + + AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; + [audioMix setInputParameters:allAudioParams]; + + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; + [self.avPlayer.currentItem setAudioMix:audioMix]; + + break; + } + } + + AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + + for (id track in useableTracks) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:subtitleTrack]) { + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; + break; + } + } + + self.isVideoCurrentlyPlaying = YES; + + return true; +} + +- (BOOL)isVideoPlaying { + if (self.avPlayer.error) { + printf("Error during playback\n"); + } + return (self.avPlayer.rate > 0 && !self.avPlayer.error); +} + +- (void)pauseVideo { + self.videoCurrentTime = self.avPlayer.currentTime; + [self.avPlayer pause]; + self.isVideoCurrentlyPlaying = NO; +} + +- (void)unpauseVideo { + [self.avPlayer play]; + self.isVideoCurrentlyPlaying = YES; +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification { + [self stopVideo]; +} + +- (void)finishPlayingVideo { + [self.avPlayer pause]; + [self.avPlayerLayer removeFromSuperlayer]; + self.avPlayerLayer = nil; + + if (self.avPlayerItem) { + [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; + self.avPlayerItem = nil; + } + + if (self.avPlayer) { + [self.avPlayer removeObserver:self forKeyPath:@"status"]; + self.avPlayer = nil; + } + + self.avAsset = nil; + + self.isVideoCurrentlyPlaying = NO; +} + +- (void)stopVideo { + [self finishPlayingVideo]; + + [self removeFromSuperview]; +} + +@end diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 946fd51923..e007276b4b 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -128,7 +128,6 @@ void OSIPhone::initialize_modules() { #ifdef GAME_CENTER_ENABLED game_center = memnew(GameCenter); Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); - game_center->connect(); #endif #ifdef STOREKIT_ENABLED @@ -272,7 +271,6 @@ String OSIPhone::get_model_name() const { Error OSIPhone::shell_open(String p_uri) { NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; NSURL *url = [NSURL URLWithString:urlPath]; - [urlPath release]; if (![[UIApplication sharedApplication] canOpenURL:url]) { return ERR_CANT_OPEN; @@ -280,11 +278,7 @@ Error OSIPhone::shell_open(String p_uri) { printf("opening url %s\n", p_uri.utf8().get_data()); - // if (@available(iOS 10, *)) { [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - // } else { - // [[UIApplication sharedApplication] openURL:url]; - // } return OK; }; diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index 2bbbe47c0d..ec39ad0ba4 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -33,3 +33,10 @@ #define PLATFORM_REFCOUNT #define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index dffdc01d4a..b0b31ae377 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -32,17 +32,15 @@ #import <UIKit/UIKit.h> @class GodotView; +@class GodotNativeVideoView; @interface ViewController : UIViewController <GKGameCenterControllerDelegate> -- (GodotView *)godotView; +@property(nonatomic, readonly, strong) GodotView *godotView; +@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView; // MARK: Native Video Player - (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; -- (BOOL)isVideoPlaying; -- (void)pauseVideo; -- (void)unpauseVideo; -- (void)stopVideo; @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 31597f7856..fb25041779 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -33,6 +33,7 @@ #include "display_server_iphone.h" #import "godot_view.h" #import "godot_view_renderer.h" +#import "native_video_view.h" #include "os_iphone.h" #import <GameController/GameController.h> @@ -40,16 +41,7 @@ @interface ViewController () @property(strong, nonatomic) GodotViewRenderer *renderer; - -// TODO: separate view to handle video -// AVPlayer-related properties -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; -@property(assign, nonatomic) CMTime videoCurrentTime; -@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; -@property(assign, nonatomic) BOOL videoHasFoundError; +@property(strong, nonatomic) GodotNativeVideoView *videoView; @end @@ -67,9 +59,6 @@ self.view = view; view.renderer = self.renderer; - - [renderer release]; - [view release]; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { @@ -93,9 +82,7 @@ } - (void)godot_commonInit { - self.isVideoCurrentlyPlaying = NO; - self.videoCurrentTime = kCMTimeZero; - self.videoHasFoundError = false; + // Initialize view controller values. } - (void)didReceiveMemoryWarning { @@ -107,7 +94,6 @@ [super viewDidLoad]; [self observeKeyboard]; - [self observeAudio]; if (@available(iOS 11.0, *)) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; @@ -128,33 +114,13 @@ object:nil]; } -- (void)observeAudio { - printf("******** adding observer for sound routing changes\n"); - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(audioRouteChangeListenerCallback:) - name:AVAudioSessionRouteChangeNotification - object:nil]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { - [self handleVideoOrPlayerStatus]; - } - - if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { - [self handleVideoPlayRate]; - } -} - - (void)dealloc { - [self stopVideo]; + [self.videoView stopVideo]; + self.videoView = nil; self.renderer = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; - - [super dealloc]; } // MARK: Orientation @@ -233,166 +199,19 @@ } } -// MARK: Audio - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { - printf("*********** route changed!\n"); - NSDictionary *interuptionDict = notification.userInfo; - - NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - - switch (routeChangeReason) { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); - NSLog(@"Headphone/Line plugged in"); - } break; - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); - NSLog(@"Headphone/Line was pulled. Resuming video play...."); - if ([self isVideoPlaying]) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - } - } break; - case AVAudioSessionRouteChangeReasonCategoryChange: { - // called at start - also when other audio wants to play - NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); - } break; - } -} - // MARK: Native Video Player -- (void)handleVideoOrPlayerStatus { - if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { - [self stopVideo]; - self.videoHasFoundError = true; - } - - if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { - // NSLog(@"time: %@", self.video_current_time); - [self.avPlayer seekToTime:self.videoCurrentTime]; - self.videoCurrentTime = kCMTimeZero; - } -} - -- (void)handleVideoPlayRate { - NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); - if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - - NSLog(@" . . . PAUSED (or just started)"); - } -} - - (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { - self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; - - self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; - [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; - - self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; - self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; - - [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(playerItemDidReachEnd:) - name:AVPlayerItemDidPlayToEndTimeNotification - object:[self.avPlayer currentItem]]; - - [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - - [self.avPlayerLayer setFrame:self.view.bounds]; - [self.view.layer addSublayer:self.avPlayerLayer]; - [self.avPlayer play]; - - AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - - NSMutableArray *allAudioParams = [NSMutableArray array]; - for (id track in audioGroup.options) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:audioTrack]) { - AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; - [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; - [audioInputParams setTrackID:[track trackID]]; - [allAudioParams addObject:audioInputParams]; - - AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; - [audioMix setInputParameters:allAudioParams]; - - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; - [self.avPlayer.currentItem setAudioMix:audioMix]; - - break; - } - } - - AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - - for (id track in useableTracks) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:subtitleTrack]) { - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; - break; - } - } - - self.isVideoCurrentlyPlaying = YES; - - return true; -} - -- (BOOL)isVideoPlaying { - if (self.avPlayer.error) { - printf("Error during playback\n"); - } - return (self.avPlayer.rate > 0 && !self.avPlayer.error); -} - -- (void)pauseVideo { - self.videoCurrentTime = self.avPlayer.currentTime; - [self.avPlayer pause]; - self.isVideoCurrentlyPlaying = NO; -} - -- (void)unpauseVideo { - [self.avPlayer play]; - self.isVideoCurrentlyPlaying = YES; -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { - [self stopVideo]; -} - -- (void)stopVideo { - [self.avPlayer pause]; - [self.avPlayerLayer removeFromSuperlayer]; - self.avPlayerLayer = nil; - - if (self.avPlayerItem) { - [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; - self.avPlayerItem = nil; - } - - if (self.avPlayer) { - [self.avPlayer removeObserver:self forKeyPath:@"status"]; - self.avPlayer = nil; + // If we are showing some video already, reuse existing view for new video. + if (self.videoView) { + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; + } else { + // Create autoresizing view for video playback. + GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds]; + videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight; + [self.view addSubview:videoView]; + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; } - - self.avAsset = nil; - - self.isVideoCurrentlyPlaying = NO; } // MARK: Delegates diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index cb4dbe7f85..d62e826957 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -35,14 +35,12 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const { return VK_MVK_IOS_SURFACE_EXTENSION_NAME; } -Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, - CALayer *p_metal_layer, int p_width, - int p_height) { +Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height) { VkIOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; createInfo.flags = 0; - createInfo.pView = p_metal_layer; + createInfo.pView = (__bridge const void *)p_metal_layer; VkSurfaceKHR surface; VkResult err = diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index e3cf992302..8b0d08d1cb 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -322,23 +322,19 @@ bool DisplayServerX11::_refresh_device_info() { } void DisplayServerX11::_flush_mouse_motion() { - while (true) { - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); - - if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - - if (event_data->evtype == XI_RawMotion) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; + // Block events polling while flushing motion events. + MutexLock mutex_lock(events_mutex); + + for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) { + XEvent &event = polled_events[event_index]; + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + if (event_data->evtype == XI_RawMotion) { + XFreeEventData(x11_display, &event.xcookie); + polled_events.remove(event_index--); + continue; } - } else { + XFreeEventData(x11_display, &event.xcookie); break; } } @@ -451,12 +447,25 @@ int DisplayServerX11::mouse_get_button_state() const { void DisplayServerX11::clipboard_set(const String &p_text) { _THREAD_SAFE_METHOD_ - internal_clipboard = p_text; + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + internal_clipboard = p_text; + } + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); } -static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { +Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { + if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { + return True; + } else { + return False; + } +} + +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { String ret; Atom type; @@ -464,23 +473,27 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x int format, result; unsigned long len, bytes_left, dummy; unsigned char *data; - Window Sown = XGetSelectionOwner(x11_display, p_source); + Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (Sown == x11_window) { - return p_internal_clipboard; - }; + if (selection_owner == x11_window) { + return internal_clipboard; + } - if (Sown != None) { - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - XFlush(x11_display); - while (true) { + if (selection_owner != None) { + { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True + // and remove the event from the queue. XEvent event; - XNextEvent(x11_display, &event); - if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { - break; - }; - }; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + } // // Do not get any data, see how much data is there @@ -514,14 +527,14 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x return ret; } -static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { +String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { String ret; Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); if (utf8_atom != None) { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); + ret = _clipboard_get_impl(p_source, x11_window, utf8_atom); } - if (ret == "") { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); + if (ret.empty()) { + ret = _clipboard_get_impl(p_source, x11_window, XA_STRING); } return ret; } @@ -530,11 +543,11 @@ String DisplayServerX11::clipboard_get() const { _THREAD_SAFE_METHOD_ String ret; - ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); + ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window); - if (ret == "") { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); - }; + if (ret.empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } return ret; } @@ -1649,10 +1662,16 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win return; } + // Block events polling while changing input focus + // because it triggers some event polling internally. if (p_active) { - XSetICFocus(wd.xic); + { + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } window_set_ime_position(wd.im_position, p_window); } else { + MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); } } @@ -1673,7 +1692,14 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ spot.x = short(p_pos.x); spot.y = short(p_pos.y); XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr); - XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + + { + // Block events polling during this call + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + } + XFree(preedit_attr); } @@ -1993,7 +2019,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button return last_button_state; } -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2130,7 +2156,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, /* Phase 4, determine if event must be filtered */ // This seems to be a side-effect of using XIM. - // XEventFilter looks like a core X11 function, + // XFilterEvent looks like a core X11 function, // but it's actually just used to see if we must // ignore a deadkey, or events XIM determines // must not reach the actual gui. @@ -2164,17 +2190,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // Echo characters in X11 are a keyrelease and a keypress // one after the other with the (almot) same timestamp. - // To detect them, i use XPeekEvent and check that their - // difference in time is below a threshold. + // To detect them, i compare to the next event in list and + // check that their difference in time is below a threshold. if (xkeyevent->type != KeyPress) { p_echo = false; // make sure there are events pending, // so this call won't block. - if (XPending(x11_display) > 0) { - XEvent peek_event; - XPeekEvent(x11_display, &peek_event); + if (p_event_index + 1 < p_events.size()) { + XEvent &peek_event = p_events[p_event_index + 1]; // I'm using a threshold of 5 msecs, // since sometimes there seems to be a little @@ -2189,9 +2214,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); if (rk == keysym_keycode) { - XEvent event; - XNextEvent(x11_display, &event); //erase next event - _handle_key_event(p_window, (XKeyEvent *)&event, true); + // Consume to next event. + ++p_event_index; + _handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true); return; //ignore current, echo next } } @@ -2246,6 +2271,66 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Input::get_singleton()->accumulate_input_event(k); } +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_event->target == XInternAtom(x11_display, "TEXT", 0) || + p_event->target == XA_STRING || + p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_event->target == XInternAtom(x11_display, "text/plain", 0)) { + // Directly using internal clipboard because we know our window + // is the owner during a selection request. + CharString clip = internal_clipboard.utf8(); + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + p_event->target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + respond.xselection.property = p_event->property; + } else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) { + Atom data[7]; + data[0] = XInternAtom(x11_display, "TARGETS", 0); + data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[3] = XInternAtom(x11_display, "TEXT", 0); + data[4] = XA_STRING; + data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[6] = XInternAtom(x11_display, "text/plain", 0); + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)&data, + sizeof(data) / sizeof(data[0])); + respond.xselection.property = p_event->property; + + } else { + char *targetname = XGetAtomName(x11_display, p_event->target); + printf("No Target '%s'\n", targetname); + if (targetname) { + XFree(targetname); + } + respond.xselection.property = None; + } + + respond.xselection.type = SelectionNotify; + respond.xselection.display = p_event->display; + respond.xselection.requestor = p_event->requestor; + respond.xselection.selection = p_event->selection; + respond.xselection.target = p_event->target; + respond.xselection.time = p_event->time; + + XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond); + XFlush(x11_display); +} + void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { WARN_PRINT("Input method stopped"); @@ -2358,6 +2443,65 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev } } +void DisplayServerX11::_poll_events_thread(void *ud) { + DisplayServerX11 *display_server = (DisplayServerX11 *)ud; + display_server->_poll_events(); +} + +Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) { + // Just accept all events. + return True; +} + +void DisplayServerX11::_poll_events() { + int x11_fd = ConnectionNumber(x11_display); + fd_set in_fds; + + while (!events_thread_done) { + XFlush(x11_display); + + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); + + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; + + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + if (num_ready_fds < 0) { + ERR_PRINT("_poll_events: select error: " + itos(errno)); + } + + // Process events from the queue. + { + MutexLock mutex_lock(events_mutex); + + // Non-blocking wait for next event + // and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { + // Check if the input manager wants to process the event. + if (XFilterEvent(&ev, None)) { + // Event has been filtered by the Input Manager, + // it has to be ignored and a new one will be received. + continue; + } + + // Handle selection request events directly in the event thread, because + // communication through the x server takes several events sent back and forth + // and we don't want to block other programs while processing only one each frame. + if (ev.type == SelectionRequest) { + _handle_selection_request_event(&(ev.xselectionrequest)); + continue; + } + + polled_events.push_back(ev); + } + } + } +} + void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ @@ -2401,13 +2545,16 @@ void DisplayServerX11::process_events() { xi.tilt = Vector2(); xi.pressure_supported = false; - while (XPending(x11_display) > 0) { - XEvent event; - XNextEvent(x11_display, &event); + LocalVector<XEvent> events; + { + // Block events polling while flushing events. + MutexLock mutex_lock(events_mutex); + events = polled_events; + polled_events.clear(); + } - if (XFilterEvent(&event, None)) { - continue; - } + for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { + XEvent &event = events[event_index]; WindowID window_id = MAIN_WINDOW_ID; @@ -2666,6 +2813,9 @@ void DisplayServerX11::process_events() { }*/ #endif if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); XSetICFocus(wd.xic); } @@ -2715,7 +2865,10 @@ void DisplayServerX11::process_events() { xi.state.clear(); #endif if (wd.xic) { - XSetICFocus(wd.xic); + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XUnsetICFocus(wd.xic); } } break; @@ -2834,11 +2987,11 @@ void DisplayServerX11::process_events() { break; } - if (XPending(x11_display) > 0) { - XEvent tevent; - XPeekEvent(x11_display, &tevent); - if (tevent.type == MotionNotify) { - XNextEvent(x11_display, &event); + if (event_index + 1 < events.size()) { + const XEvent &next_event = events[event_index + 1]; + if (next_event.type == MotionNotify) { + ++event_index; + event = next_event; } else { break; } @@ -2969,67 +3122,7 @@ void DisplayServerX11::process_events() { // key event is a little complex, so // it will be handled in its own function. - _handle_key_event(window_id, (XKeyEvent *)&event); - } break; - case SelectionRequest: { - XSelectionRequestEvent *req; - XEvent e, respond; - e = event; - - req = &(e.xselectionrequest); - if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) || - req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || - req->target == XInternAtom(x11_display, "TEXT", 0) || - req->target == XA_STRING || - req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || - req->target == XInternAtom(x11_display, "text/plain", 0)) { - CharString clip = clipboard_get().utf8(); - XChangeProperty(x11_display, - req->requestor, - req->property, - req->target, - 8, - PropModeReplace, - (unsigned char *)clip.get_data(), - clip.length()); - respond.xselection.property = req->property; - } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) { - Atom data[7]; - data[0] = XInternAtom(x11_display, "TARGETS", 0); - data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); - data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); - data[3] = XInternAtom(x11_display, "TEXT", 0); - data[4] = XA_STRING; - data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); - data[6] = XInternAtom(x11_display, "text/plain", 0); - - XChangeProperty(x11_display, - req->requestor, - req->property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *)&data, - sizeof(data) / sizeof(data[0])); - respond.xselection.property = req->property; - - } else { - char *targetname = XGetAtomName(x11_display, req->target); - printf("No Target '%s'\n", targetname); - if (targetname) { - XFree(targetname); - } - respond.xselection.property = None; - } - - respond.xselection.type = SelectionNotify; - respond.xselection.display = req->display; - respond.xselection.requestor = req->requestor; - respond.xselection.selection = req->selection; - respond.xselection.target = req->target; - respond.xselection.time = req->time; - XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond); - XFlush(x11_display); + _handle_key_event(window_id, (XKeyEvent *)&event, events, event_index); } break; case SelectionNotify: @@ -3424,6 +3517,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); if (xim && xim_style) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr); if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) { WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); @@ -3926,12 +4023,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } + events_thread = Thread::create(_poll_events_thread, this); + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); r_error = OK; } DisplayServerX11::~DisplayServerX11() { + events_thread_done = true; + Thread::wait_to_finish(events_thread); + memdelete(events_thread); + events_thread = nullptr; + //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { #ifdef VULKAN_ENABLED diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index e894cf515c..740bf81fd9 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -36,6 +36,7 @@ #include "servers/display_server.h" #include "core/input/input.h" +#include "core/local_vector.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" @@ -202,7 +203,11 @@ class DisplayServerX11 : public DisplayServer { MouseMode mouse_mode; Point2i center; - void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); + void _handle_selection_request_event(XSelectionRequestEvent *p_event); + + String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; + String _clipboard_get(Atom p_source, Window x11_window) const; //bool minimized; //bool window_has_focus; @@ -252,6 +257,16 @@ class DisplayServerX11 : public DisplayServer { static void _dispatch_input_events(const Ref<InputEvent> &p_event); void _dispatch_input_event(const Ref<InputEvent> &p_event); + mutable Mutex events_mutex; + Thread *events_thread = nullptr; + bool events_thread_done = false; + LocalVector<XEvent> polled_events; + static void _poll_events_thread(void *ud); + void _poll_events(); + + static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + protected: void _window_changed(XEvent *event); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 49f0e7bfa3..cd5b71890c 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -2246,11 +2246,18 @@ int DisplayServerOSX::screen_get_dpi(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayDPI = [[description objectForKey:NSDeviceResolution] sizeValue]; - return (displayDPI.width + displayDPI.height) / 2; + + const NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + const CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + float scale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; + + float den2 = (displayPhysicalSize.width / 25.4f) * (displayPhysicalSize.width / 25.4f) + (displayPhysicalSize.height / 25.4f) * (displayPhysicalSize.height / 25.4f); + if (den2 > 0.0f) { + return ceil(sqrt(displayPixelSize.width * displayPixelSize.width + displayPixelSize.height * displayPixelSize.height) / sqrt(den2) * scale); + } } - return 96; + return 72; } float DisplayServerOSX::screen_get_scale(int p_screen) const { diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 3268544519..b3370863e1 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -660,11 +660,16 @@ StringName AnimatedSprite2D::get_animation() const { } String AnimatedSprite2D::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (frames.is_null()) { - return TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite to display frames."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite to display frames."); } - return String(); + return warning; } void AnimatedSprite2D::set_specular_color(const Color &p_color) { diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 56643542a8..8fb16534e8 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -78,14 +78,19 @@ String CanvasModulate::get_configuration_warning() const { return String(); } + String warning = Node2D::get_configuration_warning(); + List<Node *> nodes; get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes); if (nodes.size() > 1) { - return TTR("Only one visible CanvasModulate is allowed per scene (or set of instanced scenes). The first created one will work, while the rest will be ignored."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("Only one visible CanvasModulate is allowed per scene (or set of instanced scenes). The first created one will work, while the rest will be ignored."); } - return String(); + return warning; } CanvasModulate::CanvasModulate() { diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index d23398713a..3cbb0f72f8 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -230,15 +230,23 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl #endif String CollisionPolygon2D::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (!Object::cast_to<CollisionObject2D>(get_parent())) { - return TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); } if (polygon.empty()) { - return TTR("An empty CollisionPolygon2D has no effect on collision."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("An empty CollisionPolygon2D has no effect on collision."); } - return String(); + return warning; } void CollisionPolygon2D::set_disabled(bool p_disabled) { diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index dd21fcfe22..8d1162b7e3 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -254,7 +254,7 @@ bool CPUParticles2D::get_fractional_delta() const { } String CPUParticles2D::get_configuration_warning() const { - String warnings; + String warnings = Node2D::get_configuration_warning(); CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 0814fbb549..01da36ec34 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -222,7 +222,7 @@ String GPUParticles2D::get_configuration_warning() const { return TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose."); } - String warnings; + String warnings = Node2D::get_configuration_warning(); if (process_material.is_null()) { if (warnings != String()) { diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 1e7e9f6b6a..217a210342 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -310,11 +310,16 @@ void Light2D::_notification(int p_what) { } String Light2D::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (!texture.is_valid()) { - return TTR("A texture with the shape of the light must be supplied to the \"Texture\" property."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A texture with the shape of the light must be supplied to the \"Texture\" property."); } - return String(); + return warning; } void Light2D::set_shadow_smooth(float p_amount) { diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 023cfa6d03..b2ea60f895 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -246,15 +246,23 @@ int LightOccluder2D::get_occluder_light_mask() const { } String LightOccluder2D::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (!occluder_polygon.is_valid()) { - return TTR("An occluder polygon must be set (or drawn) for this occluder to take effect."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("An occluder polygon must be set (or drawn) for this occluder to take effect."); } if (occluder_polygon.is_valid() && occluder_polygon->get_polygon().size() == 0) { - return TTR("The occluder polygon for this occluder is empty. Please draw a polygon."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The occluder polygon for this occluder is empty. Please draw a polygon."); } - return String(); + return warning; } void LightOccluder2D::_bind_methods() { diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index e5cdade4a4..9949ac6228 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -271,11 +271,16 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { } String NavigationAgent2D::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!Object::cast_to<Node2D>(get_parent())) { - return TTR("The NavigationAgent2D can be used only under a Node2D node"); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The NavigationAgent2D can be used only under a Node2D node"); } - return String(); + return warning; } void NavigationAgent2D::update_navigation() { diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 568023bbe2..252d7cbb96 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -106,11 +106,16 @@ Node *NavigationObstacle2D::get_navigation_node() const { } String NavigationObstacle2D::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!Object::cast_to<Node2D>(get_parent())) { - return TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object."); } - return String(); + return warning; } void NavigationObstacle2D::update_agent_shape() { diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 671bda558d..67e7176e6e 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -500,19 +500,26 @@ String NavigationRegion2D::get_configuration_warning() const { return String(); } + String warning = Node2D::get_configuration_warning(); + if (!navpoly.is_valid()) { - return TTR("A NavigationPolygon resource must be set or created for this node to work. Please set a property or draw a polygon."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A NavigationPolygon resource must be set or created for this node to work. Please set a property or draw a polygon."); } const Node2D *c = this; while (c) { if (Object::cast_to<Navigation2D>(c)) { - return String(); + return warning; } c = Object::cast_to<Node2D>(c->get_parent()); } - - return TTR("NavigationRegion2D must be a child or grandchild to a Navigation2D node. It only provides navigation data."); + if (!warning.empty()) { + warning += "\n\n"; + } + return warning + TTR("NavigationRegion2D must be a child or grandchild to a Navigation2D node. It only provides navigation data."); } void NavigationRegion2D::_bind_methods() { diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 4ed335dec8..ec1f1f40fa 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -136,11 +136,16 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc } String ParallaxLayer::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (!Object::cast_to<ParallaxBackground>(get_parent())) { - return TTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."); } - return String(); + return warning; } void ParallaxLayer::_bind_methods() { diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index f2f549e851..d5994422e7 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -254,11 +254,16 @@ String PathFollow2D::get_configuration_warning() const { return String(); } + String warning = Node2D::get_configuration_warning(); + if (!Object::cast_to<Path2D>(get_parent())) { - return TTR("PathFollow2D only works when set as a child of a Path2D node."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("PathFollow2D only works when set as a child of a Path2D node."); } - return String(); + return warning; } void PathFollow2D::_bind_methods() { diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 0a9de20664..22d426bdb4 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -748,7 +748,7 @@ String RigidBody2D::get_configuration_warning() const { String warning = CollisionObject2D::get_configuration_warning(); if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("Size changes to RigidBody2D (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 3104436dbe..7655416ce2 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -186,11 +186,16 @@ void RemoteTransform2D::force_update_cache() { } String RemoteTransform2D::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { - return TTR("Path property must point to a valid Node2D node to work."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("Path property must point to a valid Node2D node to work."); } - return String(); + return warning; } void RemoteTransform2D::_bind_methods() { diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index ea37c8dfe7..ea1d9f5930 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -136,7 +136,7 @@ int Bone2D::get_index_in_skeleton() const { String Bone2D::get_configuration_warning() const { String warning = Node2D::get_configuration_warning(); if (!skeleton) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } if (parent_bone) { @@ -147,7 +147,7 @@ String Bone2D::get_configuration_warning() const { } if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp index 75154c7acb..9cec589cfb 100644 --- a/scene/2d/visibility_notifier_2d.cpp +++ b/scene/2d/visibility_notifier_2d.cpp @@ -313,12 +313,17 @@ void VisibilityEnabler2D::_node_removed(Node *p_node) { } String VisibilityEnabler2D::get_configuration_warning() const { + String warning = VisibilityNotifier2D::get_configuration_warning(); + #ifdef TOOLS_ENABLED if (is_inside_tree() && get_parent() && (get_parent()->get_filename() == String() && get_parent() != get_tree()->get_edited_scene_root())) { - return TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."); } #endif - return String(); + return warning; } void VisibilityEnabler2D::_bind_methods() { diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index e2d11c740a..b8a4ab74ee 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -156,15 +156,23 @@ bool CollisionPolygon3D::is_disabled() const { } String CollisionPolygon3D::get_configuration_warning() const { + String warning = Node3D::get_configuration_warning(); + if (!Object::cast_to<CollisionObject3D>(get_parent())) { - return TTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, KinematicBody3D, etc. to give them a shape."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, KinematicBody3D, etc. to give them a shape."); } if (polygon.empty()) { - return TTR("An empty CollisionPolygon3D has no effect on collision."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("An empty CollisionPolygon3D has no effect on collision."); } - return String(); + return warning; } bool CollisionPolygon3D::_is_editable_3d_polygon() const { diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index e7f3f53ca9..e1c691b89a 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -124,23 +124,33 @@ void CollisionShape3D::resource_changed(RES res) { } String CollisionShape3D::get_configuration_warning() const { + String warning = Node3D::get_configuration_warning(); + if (!Object::cast_to<CollisionObject3D>(get_parent())) { - return TTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, KinematicBody3D, etc. to give them a shape."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, KinematicBody3D, etc. to give them a shape."); } if (!shape.is_valid()) { - return TTR("A shape must be provided for CollisionShape3D to function. Please create a shape resource for it."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A shape must be provided for CollisionShape3D to function. Please create a shape resource for it."); } - if (Object::cast_to<RigidBody3D>(get_parent())) { - if (Object::cast_to<ConcavePolygonShape3D>(*shape)) { - if (Object::cast_to<RigidBody3D>(get_parent())->get_mode() != RigidBody3D::MODE_STATIC) { - return TTR("ConcavePolygonShape3D doesn't support RigidBody3D in another mode than static."); - } + if (shape.is_valid() && + Object::cast_to<RigidBody3D>(get_parent()) && + Object::cast_to<ConcavePolygonShape3D>(*shape) && + Object::cast_to<RigidBody3D>(get_parent())->get_mode() != RigidBody3D::MODE_STATIC) { + if (!warning.empty()) { + warning += "\n\n"; } + warning += TTR("ConcavePolygonShape3D doesn't support RigidBody3D in another mode than static."); } - return String(); + return warning; } void CollisionShape3D::_bind_methods() { diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index ded83b75ac..c977e0d4aa 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -189,7 +189,7 @@ bool CPUParticles3D::get_fractional_delta() const { } String CPUParticles3D::get_configuration_warning() const { - String warnings; + String warnings = GeometryInstance3D::get_configuration_warning(); bool mesh_found = false; bool anim_material_found = false; diff --git a/scene/3d/gi_probe.cpp b/scene/3d/gi_probe.cpp index 1b6f9b45b9..ab30d2fec5 100644 --- a/scene/3d/gi_probe.cpp +++ b/scene/3d/gi_probe.cpp @@ -514,10 +514,15 @@ Vector<Face3> GIProbe::get_faces(uint32_t p_usage_flags) const { } String GIProbe::get_configuration_warning() const { + String warning = VisualInstance3D::get_configuration_warning(); + if (RenderingServer::get_singleton()->is_low_end()) { - return TTR("GIProbes are not supported by the GLES2 video driver.\nUse a BakedLightmap instead."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("GIProbes are not supported by the GLES2 video driver.\nUse a BakedLightmap instead."); } - return String(); + return warning; } void GIProbe::_bind_methods() { diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 6fa0fc6ecb..da5c99a873 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -232,7 +232,7 @@ String GPUParticles3D::get_configuration_warning() const { return TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose."); } - String warnings; + String warnings = GeometryInstance3D::get_configuration_warning(); bool meshes_found = false; bool anim_material_found = false; diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 2eb12d10fa..46c3f18c38 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -448,7 +448,7 @@ String OmniLight3D::get_configuration_warning() const { String warning = Light3D::get_configuration_warning(); if (!has_shadow() && get_projector().is_valid()) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("Projector texture only works with shadows active."); @@ -482,15 +482,14 @@ String SpotLight3D::get_configuration_warning() const { String warning = Light3D::get_configuration_warning(); if (has_shadow() && get_param(PARAM_SPOT_ANGLE) >= 90.0) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } - warning += TTR("A SpotLight3D with an angle wider than 90 degrees cannot cast shadows."); } if (!has_shadow() && get_projector().is_valid()) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("Projector texture only works with shadows active."); diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index e179261002..422b566867 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -287,11 +287,16 @@ void NavigationAgent3D::_avoidance_done(Vector3 p_new_velocity) { } String NavigationAgent3D::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!Object::cast_to<Node3D>(get_parent())) { - return TTR("The NavigationAgent3D can be used only under a spatial node."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The NavigationAgent3D can be used only under a spatial node."); } - return String(); + return warning; } void NavigationAgent3D::update_navigation() { diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 69fd5b02fc..adbff06ed6 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -113,11 +113,16 @@ Node *NavigationObstacle3D::get_navigation_node() const { } String NavigationObstacle3D::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!Object::cast_to<Node3D>(get_parent())) { - return TTR("The NavigationObstacle3D only serves to provide collision avoidance to a spatial object."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The NavigationObstacle3D only serves to provide collision avoidance to a spatial object."); } - return String(); + return warning; } void NavigationObstacle3D::update_agent_shape() { diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 15ed448a65..3d3467583d 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -189,19 +189,28 @@ String NavigationRegion3D::get_configuration_warning() const { return String(); } + String warning = Node3D::get_configuration_warning(); + if (!navmesh.is_valid()) { - return TTR("A NavigationMesh resource must be set or created for this node to work."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A NavigationMesh resource must be set or created for this node to work."); } + const Node3D *c = this; while (c) { if (Object::cast_to<Navigation3D>(c)) { - return String(); + return warning; } c = Object::cast_to<Node3D>(c->get_parent()); } - return TTR("NavigationRegion3D must be a child or grandchild to a Navigation3D node. It only provides navigation data."); + if (!warning.empty()) { + warning += "\n\n"; + } + return warning + TTR("NavigationRegion3D must be a child or grandchild to a Navigation3D node. It only provides navigation data."); } void NavigationRegion3D::_bind_methods() { diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index bd9e4f5bde..35c4b9d08d 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -103,7 +103,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { data.children_lock++; for (List<Node3D *>::Element *E = data.children.front(); E; E = E->next()) { - if (E->get()->data.toplevel_active) { + if (E->get()->data.top_level_active) { continue; //don't propagate to a toplevel } E->get()->_propagate_transform_changed(p_origin); @@ -136,12 +136,12 @@ void Node3D::_notification(int p_what) { data.C = nullptr; } - if (data.toplevel && !Engine::get_singleton()->is_editor_hint()) { + if (data.top_level && !Engine::get_singleton()->is_editor_hint()) { if (data.parent) { data.local_transform = data.parent->get_global_transform() * get_transform(); data.dirty = DIRTY_VECTORS; //global is always dirty upon entering a scene } - data.toplevel_active = true; + data.top_level_active = true; } data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene @@ -160,7 +160,7 @@ void Node3D::_notification(int p_what) { } data.parent = nullptr; data.C = nullptr; - data.toplevel_active = false; + data.top_level_active = false; } break; case NOTIFICATION_ENTER_WORLD: { data.inside_world = true; @@ -238,7 +238,7 @@ void Node3D::set_transform(const Transform &p_transform) { void Node3D::set_global_transform(const Transform &p_transform) { Transform xform = - (data.parent && !data.toplevel_active) ? + (data.parent && !data.top_level_active) ? data.parent->get_global_transform().affine_inverse() * p_transform : p_transform; @@ -261,7 +261,7 @@ Transform Node3D::get_global_transform() const { _update_local_transform(); } - if (data.parent && !data.toplevel_active) { + if (data.parent && !data.top_level_active) { data.global_transform = data.parent->get_global_transform() * data.local_transform; } else { data.global_transform = data.local_transform; @@ -462,8 +462,8 @@ bool Node3D::is_scale_disabled() const { return data.disable_scale; } -void Node3D::set_as_toplevel(bool p_enabled) { - if (data.toplevel == p_enabled) { +void Node3D::set_as_top_level(bool p_enabled) { + if (data.top_level == p_enabled) { return; } if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { @@ -473,16 +473,16 @@ void Node3D::set_as_toplevel(bool p_enabled) { set_transform(data.parent->get_global_transform().affine_inverse() * get_global_transform()); } - data.toplevel = p_enabled; - data.toplevel_active = p_enabled; + data.top_level = p_enabled; + data.top_level_active = p_enabled; } else { - data.toplevel = p_enabled; + data.top_level = p_enabled; } } -bool Node3D::is_set_as_toplevel() const { - return data.toplevel; +bool Node3D::is_set_as_top_level() const { + return data.top_level; } Ref<World3D> Node3D::get_world_3d() const { @@ -715,8 +715,8 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform); ClassDB::bind_method(D_METHOD("get_parent_spatial"), &Node3D::get_parent_spatial); ClassDB::bind_method(D_METHOD("set_ignore_transform_notification", "enabled"), &Node3D::set_ignore_transform_notification); - ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &Node3D::set_as_toplevel); - ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &Node3D::is_set_as_toplevel); + ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &Node3D::set_as_top_level); + ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &Node3D::is_set_as_top_level); ClassDB::bind_method(D_METHOD("set_disable_scale", "disable"), &Node3D::set_disable_scale); ClassDB::bind_method(D_METHOD("is_scale_disabled"), &Node3D::is_scale_disabled); ClassDB::bind_method(D_METHOD("get_world_3d"), &Node3D::get_world_3d); @@ -773,6 +773,7 @@ void Node3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_NONE, "", 0), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toplevel"), "set_as_toplevel", "is_set_as_toplevel"); ADD_GROUP("Matrix", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "transform", PROPERTY_HINT_NONE, ""), "set_transform", "get_transform"); ADD_GROUP("Visibility", ""); @@ -788,8 +789,8 @@ Node3D::Node3D() : data.children_lock = 0; data.ignore_notification = false; - data.toplevel = false; - data.toplevel_active = false; + data.top_level = false; + data.top_level_active = false; data.scale = Vector3(1, 1, 1); data.viewport = nullptr; data.inside_world = false; diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 327d4671e9..229e0f2c8c 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -71,8 +71,8 @@ class Node3D : public Node { Viewport *viewport; - bool toplevel_active; - bool toplevel; + bool top_level_active; + bool top_level; bool inside_world; int children_lock; @@ -144,8 +144,8 @@ public: virtual Transform get_local_gizmo_transform() const; #endif - void set_as_toplevel(bool p_enabled); - bool is_set_as_toplevel() const; + void set_as_top_level(bool p_enabled); + bool is_set_as_top_level() const; void set_disable_scale(bool p_enabled); bool is_scale_disabled() const; diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index bf69a8598d..d0d2d6f9f4 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -250,16 +250,24 @@ String PathFollow3D::get_configuration_warning() const { return String(); } + String warning = Node3D::get_configuration_warning(); + if (!Object::cast_to<Path3D>(get_parent())) { - return TTR("PathFollow3D only works when set as a child of a Path3D node."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("PathFollow3D only works when set as a child of a Path3D node."); } else { Path3D *path = Object::cast_to<Path3D>(get_parent()); if (path->get_curve().is_valid() && !path->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { - return TTR("PathFollow3D's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path3D's Curve resource."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("PathFollow3D's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path3D's Curve resource."); } } - return String(); + return warning; } void PathFollow3D::_bind_methods() { diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index fc021e5532..193d016010 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -737,7 +737,7 @@ String RigidBody3D::get_configuration_warning() const { String warning = CollisionObject3D::get_configuration_warning(); if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05)) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("Size changes to RigidBody3D (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."); @@ -2606,7 +2606,7 @@ void PhysicalBone3D::_start_physics_simulation() { PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), this, "_direct_state_changed"); - set_as_toplevel(true); + set_as_top_level(true); _internal_simulate_physics = true; } @@ -2626,7 +2626,7 @@ void PhysicalBone3D::_stop_physics_simulation() { if (_internal_simulate_physics) { PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), nullptr, ""); parent_skeleton->set_bone_global_pose_override(bone_id, Transform(), 0.0, false); - set_as_toplevel(false); + set_as_top_level(false); _internal_simulate_physics = false; } } diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index 95fce6b802..358f9346f8 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -180,11 +180,16 @@ void RemoteTransform3D::force_update_cache() { } String RemoteTransform3D::get_configuration_warning() const { + String warning = Node3D::get_configuration_warning(); + if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { - return TTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."); } - return String(); + return warning; } void RemoteTransform3D::_bind_methods() { diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index d3d7cdc1ce..94e9555a1a 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -282,7 +282,7 @@ void SoftBody3D::_notification(int p_what) { set_notify_transform(false); // Required to be top level with Transform at center of world in order to modify RenderingServer only to support custom Transform - set_as_toplevel(true); + set_as_top_level(true); set_transform(Transform()); set_notify_transform(true); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 6e38196ba6..9f4d64cb32 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1074,11 +1074,16 @@ StringName AnimatedSprite3D::get_animation() const { } String AnimatedSprite3D::get_configuration_warning() const { + String warning = SpriteBase3D::get_configuration_warning(); + if (frames.is_null()) { - return TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames."); } - return String(); + return warning; } void AnimatedSprite3D::_bind_methods() { diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index 4c71ab470b..b58f313c16 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -103,11 +103,16 @@ void VehicleWheel3D::_notification(int p_what) { } String VehicleWheel3D::get_configuration_warning() const { + String warning = Node3D::get_configuration_warning(); + if (!Object::cast_to<VehicleBody3D>(get_parent())) { - return TTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."); } - return String(); + return warning; } void VehicleWheel3D::_update(PhysicsDirectBodyState3D *s) { diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index 5e73e460ed..3c12d4991e 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -110,22 +110,30 @@ Ref<CameraEffects> WorldEnvironment::get_camera_effects() const { } String WorldEnvironment::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!environment.is_valid()) { - return TTR("WorldEnvironment requires its \"Environment\" property to contain an Environment to have a visible effect."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("WorldEnvironment requires its \"Environment\" property to contain an Environment to have a visible effect."); } if (!is_inside_tree()) { - return String(); + return warning; } List<Node *> nodes; get_tree()->get_nodes_in_group("_world_environment_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), &nodes); if (nodes.size() > 1) { - return TTR("Only one WorldEnvironment is allowed per scene (or set of instanced scenes)."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("Only one WorldEnvironment is allowed per scene (or set of instanced scenes)."); } - return String(); + return warning; } void WorldEnvironment::_bind_methods() { diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index f4a514cdd6..c0015aa338 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -60,13 +60,18 @@ String XRCamera3D::get_configuration_warning() const { return String(); } + String warning = Camera3D::get_configuration_warning(); + // must be child node of XROrigin3D! XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); if (origin == nullptr) { - return TTR("XRCamera3D must have an XROrigin3D node as its parent."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("XRCamera3D must have an XROrigin3D node as its parent."); }; - return String(); + return warning; }; Vector3 XRCamera3D::project_local_ray_normal(const Point2 &p_pos) const { @@ -362,17 +367,25 @@ String XRController3D::get_configuration_warning() const { return String(); } + String warning = Node3D::get_configuration_warning(); + // must be child node of XROrigin! XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); if (origin == nullptr) { - return TTR("XRController3D must have an XROrigin3D node as its parent."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("XRController3D must have an XROrigin3D node as its parent."); }; if (controller_id == 0) { - return TTR("The controller ID must not be 0 or this controller won't be bound to an actual controller."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The controller ID must not be 0 or this controller won't be bound to an actual controller."); }; - return String(); + return warning; }; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -479,17 +492,25 @@ String XRAnchor3D::get_configuration_warning() const { return String(); } + String warning = Node3D::get_configuration_warning(); + // must be child node of XROrigin3D! XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); if (origin == nullptr) { - return TTR("XRAnchor3D must have an XROrigin3D node as its parent."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("XRAnchor3D must have an XROrigin3D node as its parent."); }; if (anchor_id == 0) { - return TTR("The anchor ID must not be 0 or this anchor won't be bound to an actual anchor."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The anchor ID must not be 0 or this anchor won't be bound to an actual anchor."); }; - return String(); + return warning; }; Plane XRAnchor3D::get_plane() const { @@ -512,11 +533,16 @@ String XROrigin3D::get_configuration_warning() const { return String(); } + String warning = Node3D::get_configuration_warning(); + if (tracked_camera == nullptr) { - return TTR("XROrigin3D requires an XRCamera3D child node."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("XROrigin3D requires an XRCamera3D child node."); } - return String(); + return warning; }; void XROrigin3D::_bind_methods() { diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 466536db10..d7d55b468e 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -1288,39 +1288,31 @@ String AnimationTree::get_configuration_warning() const { String warning = Node::get_configuration_warning(); if (!root.is_valid()) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("No root AnimationNode for the graph is set."); } if (!has_node(animation_player)) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } - warning += TTR("Path to an AnimationPlayer node containing animations is not set."); - return warning; - } - - AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); - - if (!player) { - if (warning != String()) { - warning += "\n\n"; - } - - warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node."); - return warning; - } + } else { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); - if (!player->has_node(player->get_root())) { - if (warning != String()) { - warning += "\n\n"; + if (!player) { + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node."); + } else if (!player->has_node(player->get_root())) { + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("The AnimationPlayer root node is not a valid node."); } - - warning += TTR("The AnimationPlayer root node is not a valid node."); - return warning; } return warning; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index f130726837..33e030a573 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -57,7 +57,7 @@ void BoxContainer::_resort() { if (!c || !c->is_visible_in_tree()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -111,7 +111,7 @@ void BoxContainer::_resort() { if (!c || !c->is_visible_in_tree()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -172,7 +172,7 @@ void BoxContainer::_resort() { if (!c || !c->is_visible_in_tree()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -224,7 +224,7 @@ Size2 BoxContainer::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp index f8f9bec3d7..1a72f3ca4d 100644 --- a/scene/gui/center_container.cpp +++ b/scene/gui/center_container.cpp @@ -40,7 +40,7 @@ Size2 CenterContainer::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (!c->is_visible()) { @@ -77,7 +77,7 @@ void CenterContainer::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index cbe0367c7b..f8a67d154b 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -610,7 +610,7 @@ void ColorPicker::_screen_pick_pressed() { if (!screen) { screen = memnew(Control); r->add_child(screen); - screen->set_as_toplevel(true); + screen->set_as_top_level(true); screen->set_anchors_and_margins_preset(Control::PRESET_WIDE); screen->set_default_cursor_shape(CURSOR_POINTING_HAND); screen->connect("gui_input", callable_mp(this, &ColorPicker::_screen_input)); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index a89eef6209..470a7db2dc 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -168,7 +168,7 @@ String Container::get_configuration_warning() const { String warning = Control::get_configuration_warning(); if (get_class() == "Container" && get_script().is_null()) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 13d7440eb3..17febc8d66 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -493,7 +493,7 @@ void Control::_notification(int p_notification) { } CanvasItem *ci = Object::cast_to<CanvasItem>(parent); - if (ci && ci->is_set_as_toplevel()) { + if (ci && ci->is_set_as_top_level()) { subwindow = true; break; } @@ -532,7 +532,7 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); data.parent_canvas_item = nullptr; - } else if (!is_set_as_toplevel()) { + } else if (!is_set_as_top_level()) { //disconnect viewport get_viewport()->disconnect("size_changed", callable_mp(this, &Control::_size_changed)); } @@ -1816,7 +1816,7 @@ void Control::set_focus_mode(FocusMode p_focus_mode) { } static Control *_next_control(Control *p_from) { - if (p_from->is_set_as_toplevel()) { + if (p_from->is_set_as_top_level()) { return nullptr; // can't go above } @@ -1830,7 +1830,7 @@ static Control *_next_control(Control *p_from) { ERR_FAIL_INDEX_V(next, parent->get_child_count(), nullptr); for (int i = (next + 1); i < parent->get_child_count(); i++) { Control *c = Object::cast_to<Control>(parent->get_child(i)); - if (!c || !c->is_visible_in_tree() || c->is_set_as_toplevel()) { + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { continue; } @@ -1867,7 +1867,7 @@ Control *Control::find_next_valid_focus() const { for (int i = 0; i < from->get_child_count(); i++) { Control *c = Object::cast_to<Control>(from->get_child(i)); - if (!c || !c->is_visible_in_tree() || c->is_set_as_toplevel()) { + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { continue; } @@ -1879,7 +1879,7 @@ Control *Control::find_next_valid_focus() const { next_child = _next_control(from); if (!next_child) { //nothing else.. go up and find either window or subwindow next_child = const_cast<Control *>(this); - while (next_child && !next_child->is_set_as_toplevel()) { + while (next_child && !next_child->is_set_as_top_level()) { next_child = cast_to<Control>(next_child->get_parent()); } @@ -1915,7 +1915,7 @@ static Control *_prev_control(Control *p_from) { Control *child = nullptr; for (int i = p_from->get_child_count() - 1; i >= 0; i--) { Control *c = Object::cast_to<Control>(p_from->get_child(i)); - if (!c || !c->is_visible_in_tree() || c->is_set_as_toplevel()) { + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { continue; } @@ -1955,7 +1955,7 @@ Control *Control::find_prev_valid_focus() const { Control *prev_child = nullptr; - if (from->is_set_as_toplevel() || !Object::cast_to<Control>(from->get_parent())) { + if (from->is_set_as_top_level() || !Object::cast_to<Control>(from->get_parent())) { //find last of the children prev_child = _prev_control(from); @@ -1964,7 +1964,7 @@ Control *Control::find_prev_valid_focus() const { for (int i = (from->get_index() - 1); i >= 0; i--) { Control *c = Object::cast_to<Control>(from->get_parent()->get_child(i)); - if (!c || !c->is_visible_in_tree() || c->is_set_as_toplevel()) { + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { continue; } @@ -2024,7 +2024,7 @@ void Control::release_focus() { } bool Control::is_toplevel_control() const { - return is_inside_tree() && (!data.parent_canvas_item && !data.RI && is_set_as_toplevel()); + return is_inside_tree() && (!data.parent_canvas_item && !data.RI && is_set_as_top_level()); } void Control::_propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign) { @@ -2374,7 +2374,7 @@ void Control::minimum_size_changed() { //invalidate cache upwards while (invalidate && invalidate->data.minimum_size_valid) { invalidate->data.minimum_size_valid = false; - if (invalidate->is_set_as_toplevel()) { + if (invalidate->is_set_as_top_level()) { break; // do not go further up } if (!invalidate->data.parent && get_parent()) { @@ -2560,7 +2560,7 @@ String Control::get_configuration_warning() const { String warning = CanvasItem::get_configuration_warning(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 9077bfa4ba..430e98d50e 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -181,7 +181,7 @@ void AcceptDialog::_update_child_rects() { continue; } - if (c == hbc || c == label || c == bg || c->is_set_as_toplevel()) { + if (c == hbc || c == label || c == bg || c->is_set_as_top_level()) { continue; } @@ -209,7 +209,7 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { continue; } - if (c == hbc || c == label || c->is_set_as_toplevel()) { + if (c == hbc || c == label || c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 467d252c81..a7c15e7027 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -547,7 +547,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) { - if (p_control->is_set_as_toplevel() || !p_control->is_visible()) { + if (p_control->is_set_as_top_level() || !p_control->is_visible()) { return false; } diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 01b54ddaa8..38bf31830f 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -102,7 +102,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { int idx = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || c->is_set_as_toplevel()) { + if (!c || c->is_set_as_top_level()) { continue; } @@ -131,7 +131,7 @@ void GraphNode::_resort() { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -156,7 +156,7 @@ void GraphNode::_resort() { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -388,7 +388,7 @@ Size2 GraphNode::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -477,7 +477,7 @@ void GraphNode::_connpos_update() { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 0299065f77..b674b492d8 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -43,7 +43,7 @@ Size2 MarginContainer::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (!c->is_visible()) { @@ -80,7 +80,7 @@ void MarginContainer::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index 9abdfac009..051b4de825 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -45,7 +45,7 @@ Size2 PanelContainer::get_minimum_size() const { if (!c || !c->is_visible()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -95,7 +95,7 @@ void PanelContainer::_notification(int p_what) { if (!c || !c->is_visible_in_tree()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 8a65aa5032..de866fa956 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -191,7 +191,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } @@ -215,7 +215,7 @@ void PopupPanel::_update_child_rects() { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 59e26d9e38..bdb9f408f0 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -34,7 +34,7 @@ String Range::get_configuration_warning() const { String warning = Control::get_configuration_warning(); if (shared->exp_ratio && shared->min <= 0) { - if (warning != String()) { + if (!warning.empty()) { warning += "\n\n"; } warning += TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index b72b913af0..f4e31c45d2 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -45,7 +45,7 @@ Size2 ScrollContainer::get_minimum_size() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (c == h_scroll || c == v_scroll) { @@ -285,7 +285,7 @@ void ScrollContainer::_notification(int p_what) { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (c == h_scroll || c == v_scroll) { @@ -520,6 +520,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) { } String ScrollContainer::get_configuration_warning() const { + String warning = Container::get_configuration_warning(); + int found = 0; for (int i = 0; i < get_child_count(); i++) { @@ -527,7 +529,7 @@ String ScrollContainer::get_configuration_warning() const { if (!c) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } if (c == h_scroll || c == v_scroll) { @@ -538,10 +540,12 @@ String ScrollContainer::get_configuration_warning() const { } if (found != 1) { - return TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."); - } else { - return ""; + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."); } + return warning; } HScrollBar *ScrollContainer::get_h_scrollbar() { diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 5c60153d91..6508be1e43 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -41,7 +41,7 @@ Control *SplitContainer::_getch(int p_idx) const { if (!c || !c->is_visible_in_tree()) { continue; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 41b8fad49d..14a10ea627 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -478,7 +478,7 @@ void TabContainer::_on_mouse_exited() { int TabContainer::_get_tab_width(int p_index) const { ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); Control *control = Object::cast_to<Control>(_get_tabs()[p_index]); - if (!control || control->is_set_as_toplevel() || get_tab_hidden(p_index)) { + if (!control || control->is_set_as_top_level() || get_tab_hidden(p_index)) { return 0; } @@ -537,7 +537,7 @@ void TabContainer::add_child_notify(Node *p_child) { if (!c) { return; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { return; } diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 796408a38d..2b83e4f532 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -499,7 +499,7 @@ void CanvasItem::_toplevel_raise_self() { } void CanvasItem::_enter_canvas() { - if ((!Object::cast_to<CanvasItem>(get_parent())) || toplevel) { + if ((!Object::cast_to<CanvasItem>(get_parent())) || top_level) { Node *n = this; canvas_layer = nullptr; @@ -674,27 +674,27 @@ Color CanvasItem::get_modulate() const { return modulate; } -void CanvasItem::set_as_toplevel(bool p_toplevel) { - if (toplevel == p_toplevel) { +void CanvasItem::set_as_top_level(bool p_toplevel) { + if (top_level == p_toplevel) { return; } if (!is_inside_tree()) { - toplevel = p_toplevel; + top_level = p_toplevel; return; } _exit_canvas(); - toplevel = p_toplevel; + top_level = p_toplevel; _enter_canvas(); } -bool CanvasItem::is_set_as_toplevel() const { - return toplevel; +bool CanvasItem::is_set_as_top_level() const { + return top_level; } CanvasItem *CanvasItem::get_parent_item() const { - if (toplevel) { + if (top_level) { return nullptr; } @@ -967,7 +967,7 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) { for (List<CanvasItem *>::Element *E = p_node->children_items.front(); E; E = E->next()) { CanvasItem *ci = E->get(); - if (ci->toplevel) { + if (ci->top_level) { continue; } _notify_transform(ci); @@ -997,9 +997,9 @@ ObjectID CanvasItem::get_canvas_layer_instance_id() const { } } -CanvasItem *CanvasItem::get_toplevel() const { +CanvasItem *CanvasItem::get_top_level() const { CanvasItem *ci = const_cast<CanvasItem *>(this); - while (!ci->toplevel && Object::cast_to<CanvasItem>(ci->get_parent())) { + while (!ci->top_level && Object::cast_to<CanvasItem>(ci->get_parent())) { ci = Object::cast_to<CanvasItem>(ci->get_parent()); } @@ -1009,7 +1009,7 @@ CanvasItem *CanvasItem::get_toplevel() const { Ref<World2D> CanvasItem::get_world_2d() const { ERR_FAIL_COND_V(!is_inside_tree(), Ref<World2D>()); - CanvasItem *tl = get_toplevel(); + CanvasItem *tl = get_top_level(); if (tl->get_viewport()) { return tl->get_viewport()->find_world_2d(); @@ -1136,8 +1136,8 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("update"), &CanvasItem::update); - ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &CanvasItem::set_as_toplevel); - ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &CanvasItem::is_set_as_toplevel); + ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &CanvasItem::set_as_top_level); + ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &CanvasItem::is_set_as_top_level); ClassDB::bind_method(D_METHOD("set_light_mask", "light_mask"), &CanvasItem::set_light_mask); ClassDB::bind_method(D_METHOD("get_light_mask"), &CanvasItem::get_light_mask); @@ -1218,6 +1218,7 @@ void CanvasItem::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "self_modulate"), "set_self_modulate", "get_self_modulate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_behind_parent"), "set_draw_behind_parent", "is_draw_behind_parent_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_on_top", PROPERTY_HINT_NONE, "", 0), "_set_on_top", "_is_on_top"); //compatibility ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask"); @@ -1228,8 +1229,6 @@ void CanvasItem::_bind_methods() { ADD_GROUP("Material", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_parent_material"), "set_use_parent_material", "get_use_parent_material"); - //exporting these things doesn't really make much sense i think - // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toplevel", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_as_toplevel", "is_set_as_toplevel"); // ADD_PROPERTY(PropertyInfo(Variant::BOOL,"transform/notify"),"set_transform_notify","is_transform_notify_enabled"); ADD_SIGNAL(MethodInfo("draw")); @@ -1355,7 +1354,7 @@ void CanvasItem::_update_texture_filter_changed(bool p_propagate) { if (p_propagate) { for (List<CanvasItem *>::Element *E = children_items.front(); E; E = E->next()) { - if (!E->get()->toplevel && E->get()->texture_filter == TEXTURE_FILTER_PARENT_NODE) { + if (!E->get()->top_level && E->get()->texture_filter == TEXTURE_FILTER_PARENT_NODE) { E->get()->_update_texture_filter_changed(true); } } @@ -1408,7 +1407,7 @@ void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { update(); if (p_propagate) { for (List<CanvasItem *>::Element *E = children_items.front(); E; E = E->next()) { - if (!E->get()->toplevel && E->get()->texture_repeat == TEXTURE_REPEAT_PARENT_NODE) { + if (!E->get()->top_level && E->get()->texture_repeat == TEXTURE_REPEAT_PARENT_NODE) { E->get()->_update_texture_repeat_changed(true); } } @@ -1437,7 +1436,7 @@ CanvasItem::CanvasItem() : pending_update = false; modulate = Color(1, 1, 1, 1); self_modulate = Color(1, 1, 1, 1); - toplevel = false; + top_level = false; first_draw = false; drawing = false; behind = false; diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 3f32df87a7..457ec2feef 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -201,7 +201,7 @@ private: bool first_draw; bool visible; bool pending_update; - bool toplevel; + bool top_level; bool drawing; bool block_transform_notify; bool behind; @@ -355,8 +355,8 @@ public: /* RECT / TRANSFORM */ - void set_as_toplevel(bool p_toplevel); - bool is_set_as_toplevel() const; + void set_as_top_level(bool p_toplevel); + bool is_set_as_top_level() const; void set_draw_behind_parent(bool p_enable); bool is_draw_behind_parent_enabled() const; @@ -369,7 +369,7 @@ public: virtual Transform2D get_global_transform_with_canvas() const; virtual Transform2D get_screen_transform() const; - CanvasItem *get_toplevel() const; + CanvasItem *get_top_level() const; _FORCE_INLINE_ RID get_canvas_item() const { return canvas_item; } diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 726dcb58de..86c08ca6e9 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -261,11 +261,16 @@ void ShaderGlobalsOverride::_notification(int p_what) { } String ShaderGlobalsOverride::get_configuration_warning() const { + String warning = Node::get_configuration_warning(); + if (!active) { - return TTR("ShaderGlobalsOverride is not active because another node of the same type is in the scene."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("ShaderGlobalsOverride is not active because another node of the same type is in the scene."); } - return String(); + return warning; } void ShaderGlobalsOverride::_bind_methods() { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 7fefbc3f3c..3c7475a150 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1493,7 +1493,7 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { break; } - if (p_control->is_set_as_toplevel()) { + if (p_control->is_set_as_top_level()) { break; } @@ -1620,7 +1620,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu } } - if (!control->is_inside_tree() || control->is_set_as_toplevel()) { + if (!control->is_inside_tree() || control->is_set_as_top_level()) { break; } if (gui.key_event_accepted) { @@ -1631,7 +1631,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu } } - if (ci->is_set_as_toplevel()) { + if (ci->is_set_as_top_level()) { break; } @@ -1655,7 +1655,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { break; } - if (!control->is_inside_tree() || control->is_set_as_toplevel()) { + if (!control->is_inside_tree() || control->is_set_as_top_level()) { break; } if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { @@ -1663,7 +1663,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { } } - if (ci->is_set_as_toplevel()) { + if (ci->is_set_as_top_level()) { break; } @@ -1721,7 +1721,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ if (!c || !c->clips_input() || c->has_point(matrix.affine_inverse().xform(p_global))) { for (int i = p_node->get_child_count() - 1; i >= 0; i--) { CanvasItem *ci = Object::cast_to<CanvasItem>(p_node->get_child(i)); - if (!ci || ci->is_set_as_toplevel()) { + if (!ci || ci->is_set_as_top_level()) { continue; } @@ -1768,7 +1768,7 @@ bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_che p_at_pos = ci->get_transform().xform(p_at_pos); - if (ci->is_set_as_toplevel()) { + if (ci->is_set_as_top_level()) { break; } @@ -1865,7 +1865,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - if (ci->is_set_as_toplevel()) { + if (ci->is_set_as_top_level()) { break; } @@ -1993,7 +1993,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - if (ci->is_set_as_toplevel()) { + if (ci->is_set_as_top_level()) { break; } @@ -2105,7 +2105,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP) { break; } - if (c->is_set_as_toplevel()) { + if (c->is_set_as_top_level()) { break; } c = c->get_parent_control(); @@ -2404,7 +2404,7 @@ void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { if (gui.drag_preview) { memdelete(gui.drag_preview); } - p_control->set_as_toplevel(true); + p_control->set_as_top_level(true); p_control->set_position(gui.last_mouse_pos); p_base->get_root_parent_control()->add_child(p_control); //add as child of viewport p_control->raise(); @@ -3093,10 +3093,15 @@ String Viewport::get_configuration_warning() const { return TTR("This viewport is not set as render target. If you intend for it to display its contents directly to the screen, make it a child of a Control so it can obtain a size. Otherwise, make it a RenderTarget and assign its internal texture to some node for display."); }*/ + String warning = Node::get_configuration_warning(); + if (size.x == 0 || size.y == 0) { - return TTR("Viewport size must be greater than 0 to render anything."); + if (!warning.empty()) { + warning += "\n\n"; + } + warning += TTR("Viewport size must be greater than 0 to render anything."); } - return String(); + return warning; } void Viewport::gui_reset_canvas_sort_index() { diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index b8edd70712..92103f3b1d 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -2633,6 +2633,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("value_track_get_update_mode", "track_idx"), &Animation::value_track_get_update_mode); ClassDB::bind_method(D_METHOD("value_track_get_key_indices", "track_idx", "time_sec", "delta"), &Animation::_value_track_get_key_indices); + ClassDB::bind_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::value_track_interpolate); ClassDB::bind_method(D_METHOD("method_track_get_key_indices", "track_idx", "time_sec", "delta"), &Animation::_method_track_get_key_indices); ClassDB::bind_method(D_METHOD("method_track_get_name", "track_idx", "key_idx"), &Animation::method_track_get_name); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index b8d2003e68..84437faca0 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -65,20 +65,16 @@ bool VisualShaderNode::is_port_separator(int p_index) const { bool VisualShaderNode::is_output_port_connected(int p_port) const { if (connected_output_ports.has(p_port)) { - return connected_output_ports[p_port]; + return connected_output_ports[p_port] > 0; } return false; } void VisualShaderNode::set_output_port_connected(int p_port, bool p_connected) { if (p_connected) { - connected_output_ports[p_port] = true; - ++connected_output_count; + connected_output_ports[p_port]++; } else { - --connected_output_count; - if (connected_output_count == 0) { - connected_output_ports[p_port] = false; - } + connected_output_ports[p_port]--; } } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index e7d74b6c17..e3d5200e6b 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -193,8 +193,7 @@ class VisualShaderNode : public Resource { Map<int, Variant> default_input_values; Map<int, bool> connected_input_ports; - Map<int, bool> connected_output_ports; - int connected_output_count = 0; + Map<int, int> connected_output_ports; protected: bool simple_decl = true; |