diff options
91 files changed, 2522 insertions, 355 deletions
diff --git a/.travis.yml b/.travis.yml index 4bad241c53..818f7b6398 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,12 +31,12 @@ matrix: - env: GODOT_TARGET=android TOOLS=no CACHE_NAME=${GODOT_TARGET}-gcc os: linux compiler: gcc - #- env: GODOT_TARGET=osx TOOLS=yes CACHE_NAME=${GODOT_TARGET}-clang-tools - # os: osx - # compiler: clang - #- env: GODOT_TARGET=iphone TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang - # os: osx - # compiler: clang + - env: GODOT_TARGET=osx TOOLS=yes CACHE_NAME=${GODOT_TARGET}-clang-tools + os: osx + compiler: clang + - env: GODOT_TARGET=iphone TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang + os: osx + compiler: clang addons: apt: diff --git a/core/image.cpp b/core/image.cpp index 790f17a9d6..2ac8ffea56 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -2239,6 +2239,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); ClassDB::bind_method(D_METHOD("normalmap_to_xy"), &Image::normalmap_to_xy); + ClassDB::bind_method(D_METHOD("bumpmap_to_normalmap", "bump_scale"), &Image::bumpmap_to_normalmap, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect); ClassDB::bind_method(D_METHOD("blit_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blit_rect_mask); @@ -2347,6 +2348,47 @@ void Image::normalmap_to_xy() { convert(Image::FORMAT_LA8); } +void Image::bumpmap_to_normalmap(float bump_scale) { + ERR_FAIL_COND(!_can_modify(format)); + convert(Image::FORMAT_RF); + + PoolVector<uint8_t> result_image; //rgba output + result_image.resize(width * height * 4); + + { + PoolVector<uint8_t>::Read rp = data.read(); + PoolVector<uint8_t>::Write wp = result_image.write(); + + unsigned char *write_ptr = wp.ptr(); + float *read_ptr = (float *)rp.ptr(); + + for (int ty = 0; ty < height; ty++) { + int py = ty + 1; + if (py >= height) py -= height; + + for (int tx = 0; tx < width; tx++) { + int px = tx + 1; + if (px >= width) px -= width; + float here = read_ptr[ty * width + tx]; + float to_right = read_ptr[ty * width + px]; + float above = read_ptr[py * width + tx]; + Vector3 up = Vector3(0, 1, (here - above) * bump_scale); + Vector3 across = Vector3(1, 0, (to_right - here) * bump_scale); + + Vector3 normal = across.cross(up); + normal.normalize(); + + write_ptr[((ty * width + tx) << 2) + 0] = (127.5 + normal.x * 127.5); + write_ptr[((ty * width + tx) << 2) + 1] = (127.5 + normal.y * 127.5); + write_ptr[((ty * width + tx) << 2) + 2] = (127.5 + normal.z * 127.5); + write_ptr[((ty * width + tx) << 2) + 3] = 255; + } + } + } + format = FORMAT_RGBA8; + data = result_image; +} + void Image::srgb_to_linear() { if (data.size() == 0) diff --git a/core/image.h b/core/image.h index e962787ae9..17477d88ea 100644 --- a/core/image.h +++ b/core/image.h @@ -284,6 +284,7 @@ public: void premultiply_alpha(); void srgb_to_linear(); void normalmap_to_xy(); + void bumpmap_to_normalmap(float bump_scale = 1.0); void blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest); void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest); diff --git a/core/math/geometry.h b/core/math/geometry.h index ca4363e129..73a53c53b6 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -502,16 +502,15 @@ public: } static bool is_point_in_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) { - int as_x = s.x - a.x; - int as_y = s.y - a.y; + Vector2 an = a - s; + Vector2 bn = b - s; + Vector2 cn = c - s; - bool s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0; + bool orientation = an.cross(bn) > 0; - if (((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0) == s_ab) return false; + if ((bn.cross(cn) > 0) != orientation) return false; - if (((c.x - b.x) * (s.y - b.y) - (c.y - b.y) * (s.x - b.x) > 0) != s_ab) return false; - - return true; + return (cn.cross(an) > 0) == orientation; } static bool is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2> &p_polygon); diff --git a/core/math/quat.cpp b/core/math/quat.cpp index 9aa8b537d2..4f61401ac7 100644 --- a/core/math/quat.cpp +++ b/core/math/quat.cpp @@ -89,7 +89,7 @@ void Quat::set_euler_yxz(const Vector3 &p_euler) { set(sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3, sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3, - -sin_a1 * sin_a2 * cos_a3 + cos_a1 * sin_a2 * sin_a3, + -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3, sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3); } diff --git a/core/os/input.cpp b/core/os/input.cpp index 1d7cd7c791..e8a635e1b5 100644 --- a/core/os/input.cpp +++ b/core/os/input.cpp @@ -85,6 +85,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse_position", "to"), &Input::warp_mouse_position); ClassDB::bind_method(D_METHOD("action_press", "action"), &Input::action_press); ClassDB::bind_method(D_METHOD("action_release", "action"), &Input::action_release); + ClassDB::bind_method(D_METHOD("set_default_cursor_shape", "shape"), &Input::set_default_cursor_shape, DEFVAL(CURSOR_ARROW)); ClassDB::bind_method(D_METHOD("set_custom_mouse_cursor", "image", "shape", "hotspot"), &Input::set_custom_mouse_cursor, DEFVAL(CURSOR_ARROW), DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("parse_input_event", "event"), &Input::parse_input_event); diff --git a/core/os/input.h b/core/os/input.h index 9c7595ff7f..fca68f27b7 100644 --- a/core/os/input.h +++ b/core/os/input.h @@ -119,6 +119,8 @@ public: virtual bool is_emulating_touchscreen() const = 0; + virtual CursorShape get_default_cursor_shape() = 0; + virtual void set_default_cursor_shape(CursorShape p_shape) = 0; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) = 0; virtual void set_mouse_in_window(bool p_in_window) = 0; diff --git a/doc/classes/ARVRServer.xml b/doc/classes/ARVRServer.xml index 965fad1961..3164d0c2c3 100644 --- a/doc/classes/ARVRServer.xml +++ b/doc/classes/ARVRServer.xml @@ -36,6 +36,12 @@ Find an interface by its name. Say that you're making a game that uses specific capabilities of an AR/VR platform you can find the interface for that platform by name and initialize it. </description> </method> + <method name="get_hmd_transform"> + <return type="Transform"> + </return> + <description> + </description> + </method> <method name="get_interface" qualifiers="const"> <return type="ARVRInterface"> </return> @@ -59,6 +65,30 @@ Returns a list of available interfaces with both id and name of the interface. </description> </method> + <method name="get_last_commit_usec"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_last_frame_usec"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_last_process_usec"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_primary_interface" qualifiers="const"> + <return type="ARVRInterface"> + </return> + <description> + </description> + </method> <method name="get_reference_frame" qualifiers="const"> <return type="Transform"> </return> diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index ed859169fd..f3280e7a27 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -110,7 +110,6 @@ Get the actual playing speed of current animation or 0 if not playing. This speed is the [code]playback_speed[/code] property multiplied by [code]custom_speed[/code] argument specified when calling the [code]play[/code] method. </description> </method> - <method name="has_animation" qualifiers="const"> <return type="bool"> </return> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 2e2176743f..5f85751c13 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -131,8 +131,11 @@ <method name="duplicate"> <return type="Array"> </return> + <argument index="0" name="deep" type="bool" default="False"> + </argument> <description> - Returns a copy of this [code]Array[/code]. + Returns a copy of the array. + If [code]deep[/code] is [code]true[/code], a deep copy is be performed: all nested arrays and dictionaries are duplicated and will not be shared with the original array. If [code]false[/code], a shallow copy is made and references to the original nested arrays and dictionaries are kept, so that modifying a sub-array or dictionary in the copy will also impact those referenced in the source array. </description> </method> <method name="empty"> diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 0be91427b1..7b3ba632cc 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -126,6 +126,18 @@ Returns the volume of the bus at index [code]bus_idx[/code] in dB. </description> </method> + <method name="get_device"> + <return type="String"> + </return> + <description> + </description> + </method> + <method name="get_device_list"> + <return type="Array"> + </return> + <description> + </description> + </method> <method name="get_mix_rate" qualifiers="const"> <return type="float"> </return> @@ -313,6 +325,14 @@ Sets the volume of the bus at index [code]bus_idx[/code] to [code]volume_db[/code]. </description> </method> + <method name="set_device"> + <return type="void"> + </return> + <argument index="0" name="arg0" type="String"> + </argument> + <description> + </description> + </method> <method name="swap_bus_effects"> <return type="void"> </return> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index f7270fa5f9..554ed99964 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -8,6 +8,7 @@ For such use, it is composed of a scaling and a rotation matrix, in that order (M = R.S). </description> <tutorials> + http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html </tutorials> <demos> </demos> diff --git a/doc/classes/Camera.xml b/doc/classes/Camera.xml index 9451ee959f..1260b369f5 100644 --- a/doc/classes/Camera.xml +++ b/doc/classes/Camera.xml @@ -14,8 +14,10 @@ <method name="clear_current"> <return type="void"> </return> + <argument index="0" name="enable_next" type="bool" default="true"> + </argument> <description> - If this is the current Camera, remove it from being current. If it is inside the node tree, request to make the next Camera current, if any. + If this is the current Camera, remove it from being current. If it is inside the node tree and [code]enabled_next[/code] is [code]true[/true], request to make the next Camera current, if any. </description> </method> <method name="get_camera_transform" qualifiers="const"> diff --git a/doc/classes/CanvasLayer.xml b/doc/classes/CanvasLayer.xml index b2b0938822..3e4d1b29f7 100644 --- a/doc/classes/CanvasLayer.xml +++ b/doc/classes/CanvasLayer.xml @@ -13,11 +13,11 @@ <demos> </demos> <methods> - <method name="get_world_2d" qualifiers="const"> - <return type="World2D"> + <method name="get_canvas" qualifiers="const"> + <return type="RID"> </return> <description> - Return the [World2D] used by this layer. + Returns the RID of the canvas used by this layer. </description> </method> </methods> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 95e8622cf4..4b6a9cca8a 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -773,6 +773,10 @@ <constant name="NOTIFICATION_MODAL_CLOSE" value="46"> Sent when an open modal dialog closes. See [member show_modal]. </constant> + <constant name="NOTIFICATION_SCROLL_BEGIN" value="47"> + </constant> + <constant name="NOTIFICATION_SCROLL_END" value="48"> + </constant> <constant name="CURSOR_ARROW" value="0" enum="CursorShape"> Show the system's arrow mouse cursor when the user hovers the node. Use with [method set_default_cursor_shape]. </constant> @@ -908,6 +912,8 @@ </constant> <constant name="GROW_DIRECTION_END" value="1" enum="GrowDirection"> </constant> + <constant name="GROW_DIRECTION_BOTH" value="2" enum="GrowDirection"> + </constant> <constant name="ANCHOR_BEGIN" value="0" enum="Anchor"> Snaps one of the 4 anchor's sides to the origin of the node's [code]Rect[/code], in the top left. Use it with one of the [code]anchor_*[/code] member variables, like [member anchor_left]. To change all 4 anchors at once, use [method set_anchors_preset]. </constant> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index d7118265f1..3c1413b028 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -19,6 +19,8 @@ <method name="duplicate"> <return type="Dictionary"> </return> + <argument index="0" name="deep" type="bool" default="False"> + </argument> <description> </description> </method> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 0a8d95d25d..a98cbf0316 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -102,6 +102,20 @@ <description> </description> </method> + <method name="add_tool_menu_item"> + <return type="void"> + </return> + <argument index="0" name="name" type="String"> + </argument> + <argument index="1" name="handler" type="Object"> + </argument> + <argument index="2" name="callback" type="String"> + </argument> + <argument index="3" name="ud" type="Variant" default="null"> + </argument> + <description> + </description> + </method> <method name="add_tool_submenu_item"> <return type="void"> </return> @@ -347,6 +361,14 @@ <description> </description> </method> + <method name="remove_tool_menu_item"> + <return type="void"> + </return> + <argument index="0" name="name" type="String"> + </argument> + <description> + </description> + </method> <method name="save_external_data" qualifiers="virtual"> <return type="void"> </return> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 534323348e..bd368e967a 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -163,14 +163,14 @@ Returns a [String] saved in Pascal format from the file. </description> </method> - <method name="get_path"> + <method name="get_path" qualifiers="const"> <return type="String"> </return> <description> Returns the path as a [String] for the current open file. </description> </method> - <method name="get_path_absolute"> + <method name="get_path_absolute" qualifiers="const"> <return type="String"> </return> <description> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index c1bd8d34eb..ea61aced83 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -67,6 +67,14 @@ Blits [code]src_rect[/code] area from [code]src[/code] image to this image at the coordinates given by [code]dst[/code]. [code]src[/code] pixel is copied onto [code]dst[/code] if the corresponding [code]mask[/code] pixel's alpha value is not 0. [code]src[/code] image and [code]mask[/code] image [b]must[/b] have the same size (width and height) but they can have different formats. </description> </method> + <method name="bumpmap_to_normalmap"> + <return type="void"> + </return> + <argument index="0" name="bump_scale" type="float" default="1.0"> + </argument> + <description> + </description> + </method> <method name="clear_mipmaps"> <return type="void"> </return> @@ -436,109 +444,109 @@ <constant name="FORMAT_LA8" value="1" enum="Format"> </constant> <constant name="FORMAT_R8" value="2" enum="Format"> - OpenGL texture format RED with a single component and a bitdepth of 8. + OpenGL texture format RED with a single component and a bitdepth of 8. </constant> <constant name="FORMAT_RG8" value="3" enum="Format"> - OpenGL texture format RG with two components and a bitdepth of 8 for each. + OpenGL texture format RG with two components and a bitdepth of 8 for each. </constant> <constant name="FORMAT_RGB8" value="4" enum="Format"> - OpenGL texture format RGB with three components, each with a bitdepth of 8. + OpenGL texture format RGB with three components, each with a bitdepth of 8. </constant> <constant name="FORMAT_RGBA8" value="5" enum="Format"> - OpenGL texture format RGBA with four components, each with a bitdepth of 8. + OpenGL texture format RGBA with four components, each with a bitdepth of 8. </constant> <constant name="FORMAT_RGBA4444" value="6" enum="Format"> - OpenGL texture format RGBA with four components, each with a bitdepth of 4. + OpenGL texture format RGBA with four components, each with a bitdepth of 4. </constant> <constant name="FORMAT_RGBA5551" value="7" enum="Format"> - OpenGL texture format GL_RGB5_A1 where 5 bits of depth for each component of RGB and one bit for alpha. + OpenGL texture format GL_RGB5_A1 where 5 bits of depth for each component of RGB and one bit for alpha. </constant> <constant name="FORMAT_RF" value="8" enum="Format"> - OpenGL texture format GL_R32F where there's one component, a 32-bit floating-point value. + OpenGL texture format GL_R32F where there's one component, a 32-bit floating-point value. </constant> <constant name="FORMAT_RGF" value="9" enum="Format"> - OpenGL texture format GL_RG32F where there are two components, each a 32-bit floating-point values. + OpenGL texture format GL_RG32F where there are two components, each a 32-bit floating-point values. </constant> <constant name="FORMAT_RGBF" value="10" enum="Format"> - OpenGL texture format GL_RGB32F where there are three components, each a 32-bit floating-point values. + OpenGL texture format GL_RGB32F where there are three components, each a 32-bit floating-point values. </constant> <constant name="FORMAT_RGBAF" value="11" enum="Format"> - OpenGL texture format GL_RGBA32F where there are four components, each a 32-bit floating-point values. + OpenGL texture format GL_RGBA32F where there are four components, each a 32-bit floating-point values. </constant> <constant name="FORMAT_RH" value="12" enum="Format"> - OpenGL texture format GL_R32F where there's one component, a 16-bit "half-precision" floating-point value. + OpenGL texture format GL_R32F where there's one component, a 16-bit "half-precision" floating-point value. </constant> <constant name="FORMAT_RGH" value="13" enum="Format"> - OpenGL texture format GL_RG32F where there's two components, each a 16-bit "half-precision" floating-point value. + OpenGL texture format GL_RG32F where there's two components, each a 16-bit "half-precision" floating-point value. </constant> <constant name="FORMAT_RGBH" value="14" enum="Format"> - OpenGL texture format GL_RGB32F where there's three components, each a 16-bit "half-precision" floating-point value. + OpenGL texture format GL_RGB32F where there's three components, each a 16-bit "half-precision" floating-point value. </constant> <constant name="FORMAT_RGBAH" value="15" enum="Format"> - OpenGL texture format GL_RGBA32F where there's four components, each a 16-bit "half-precision" floating-point value. + OpenGL texture format GL_RGBA32F where there's four components, each a 16-bit "half-precision" floating-point value. </constant> <constant name="FORMAT_RGBE9995" value="16" enum="Format"> - A special OpenGL texture format where the three color components have 9 bits of precision and all three share a single exponent. + A special OpenGL texture format where the three color components have 9 bits of precision and all three share a single exponent. </constant> <constant name="FORMAT_DXT1" value="17" enum="Format"> - The S3TC texture format that uses Block Compression 1, and is the smallest variation of S3TC, only providing 1 bit of alpha and color data being premultiplied with alpha. More information can be found at https://www.khronos.org/opengl/wiki/S3_Texture_Compression. + The S3TC texture format that uses Block Compression 1, and is the smallest variation of S3TC, only providing 1 bit of alpha and color data being premultiplied with alpha. More information can be found at https://www.khronos.org/opengl/wiki/S3_Texture_Compression. </constant> <constant name="FORMAT_DXT3" value="18" enum="Format"> - The S3TC texture format that uses Block Compression 2, and color data is interpreted as not having been premultiplied by alpha. Well suited for images with sharp alpha transitions between translucent and opaque areas. + The S3TC texture format that uses Block Compression 2, and color data is interpreted as not having been premultiplied by alpha. Well suited for images with sharp alpha transitions between translucent and opaque areas. </constant> <constant name="FORMAT_DXT5" value="19" enum="Format"> - The S3TC texture format also known as Block Compression 3 or BC3 that contains 64 bits of alpha channel data followed by 64 bits of DXT1-encoded color data. Color data is not premultiplied by alpha, same as DXT3. DXT5 generally produces superior results for transparency gradients than DXT3. + The S3TC texture format also known as Block Compression 3 or BC3 that contains 64 bits of alpha channel data followed by 64 bits of DXT1-encoded color data. Color data is not premultiplied by alpha, same as DXT3. DXT5 generally produces superior results for transparency gradients than DXT3. </constant> <constant name="FORMAT_RGTC_R" value="20" enum="Format"> - Texture format that uses Red Green Texture Compression, normalizing the red channel data using the same compression algorithm that DXT5 uses for the alpha channel. More information can be found here https://www.khronos.org/opengl/wiki/Red_Green_Texture_Compression. + Texture format that uses Red Green Texture Compression, normalizing the red channel data using the same compression algorithm that DXT5 uses for the alpha channel. More information can be found here https://www.khronos.org/opengl/wiki/Red_Green_Texture_Compression. </constant> <constant name="FORMAT_RGTC_RG" value="21" enum="Format"> - Texture format that uses Red Green Texture Compression, normalizing the red and green channel data using the same compression algorithm that DXT5 uses for the alpha channel. + Texture format that uses Red Green Texture Compression, normalizing the red and green channel data using the same compression algorithm that DXT5 uses for the alpha channel. </constant> <constant name="FORMAT_BPTC_RGBA" value="22" enum="Format"> - Texture format that uses BPTC compression with unsigned normalized RGBA components. More information can be found at https://www.khronos.org/opengl/wiki/BPTC_Texture_Compression. + Texture format that uses BPTC compression with unsigned normalized RGBA components. More information can be found at https://www.khronos.org/opengl/wiki/BPTC_Texture_Compression. </constant> <constant name="FORMAT_BPTC_RGBF" value="23" enum="Format"> - Texture format that uses BPTC compression with signed floating-point RGB components. + Texture format that uses BPTC compression with signed floating-point RGB components. </constant> <constant name="FORMAT_BPTC_RGBFU" value="24" enum="Format"> - Texture format that uses BPTC compression with unsigned floating-point RGB components. + Texture format that uses BPTC compression with unsigned floating-point RGB components. </constant> <constant name="FORMAT_PVRTC2" value="25" enum="Format"> - Texture format used on PowerVR-supported mobile platforms, uses 2 bit color depth with no alpha. More information on PVRTC can be found here https://en.wikipedia.org/wiki/PVRTC. + Texture format used on PowerVR-supported mobile platforms, uses 2 bit color depth with no alpha. More information on PVRTC can be found here https://en.wikipedia.org/wiki/PVRTC. </constant> <constant name="FORMAT_PVRTC2A" value="26" enum="Format"> - Same as PVRTC2, but with an alpha component. + Same as PVRTC2, but with an alpha component. </constant> <constant name="FORMAT_PVRTC4" value="27" enum="Format"> - Similar to PVRTC2, but with 4 bit color depth and no alpha. + Similar to PVRTC2, but with 4 bit color depth and no alpha. </constant> <constant name="FORMAT_PVRTC4A" value="28" enum="Format"> - Same as PVRTC4, but with an alpha component. + Same as PVRTC4, but with an alpha component. </constant> <constant name="FORMAT_ETC" value="29" enum="Format"> - Ericsson Texture Compression format, also referred to as 'ETC1', and is part of the OpenGL ES graphics standard. An overview of the format is given at https://en.wikipedia.org/wiki/Ericsson_Texture_Compression#ETC1. + Ericsson Texture Compression format, also referred to as 'ETC1', and is part of the OpenGL ES graphics standard. An overview of the format is given at https://en.wikipedia.org/wiki/Ericsson_Texture_Compression#ETC1. </constant> <constant name="FORMAT_ETC2_R11" value="30" enum="Format"> - Ericsson Texture Compression format 2 variant R11_EAC, which provides one channel of unsigned data. + Ericsson Texture Compression format 2 variant R11_EAC, which provides one channel of unsigned data. </constant> <constant name="FORMAT_ETC2_R11S" value="31" enum="Format"> - Ericsson Texture Compression format 2 variant SIGNED_R11_EAC, which provides one channel of signed data. + Ericsson Texture Compression format 2 variant SIGNED_R11_EAC, which provides one channel of signed data. </constant> <constant name="FORMAT_ETC2_RG11" value="32" enum="Format"> - Ericsson Texture Compression format 2 variant RG11_EAC, which provides two channels of unsigned data. + Ericsson Texture Compression format 2 variant RG11_EAC, which provides two channels of unsigned data. </constant> <constant name="FORMAT_ETC2_RG11S" value="33" enum="Format"> - Ericsson Texture Compression format 2 variant SIGNED_RG11_EAC, which provides two channels of signed data. + Ericsson Texture Compression format 2 variant SIGNED_RG11_EAC, which provides two channels of signed data. </constant> <constant name="FORMAT_ETC2_RGB8" value="34" enum="Format"> - Ericsson Texture Compression format 2 variant RGB8, which is a followup of ETC1 and compresses RGB888 data. + Ericsson Texture Compression format 2 variant RGB8, which is a followup of ETC1 and compresses RGB888 data. </constant> <constant name="FORMAT_ETC2_RGBA8" value="35" enum="Format"> - Ericsson Texture Compression format 2 variant RGBA8, which compresses RGBA8888 data with full alpha support. + Ericsson Texture Compression format 2 variant RGBA8, which compresses RGBA8888 data with full alpha support. </constant> <constant name="FORMAT_ETC2_RGB8A1" value="36" enum="Format"> - Ericsson Texture Compression format 2 variant RGB8_PUNCHTHROUGH_ALPHA1, which compresses RGBA data to make alpha either fully transparent or fully opaque. + Ericsson Texture Compression format 2 variant RGB8_PUNCHTHROUGH_ALPHA1, which compresses RGBA data to make alpha either fully transparent or fully opaque. </constant> <constant name="FORMAT_MAX" value="37" enum="Format"> </constant> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index f3bff5a146..f537908625 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -333,7 +333,7 @@ </methods> <signals> <signal name="joy_connection_changed"> - <argument index="0" name="index" type="int"> + <argument index="0" name="device" type="int"> </argument> <argument index="1" name="connected" type="bool"> </argument> diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml index 0147887768..4539c31441 100644 --- a/doc/classes/ItemList.xml +++ b/doc/classes/ItemList.xml @@ -144,6 +144,12 @@ Returns the current vertical scroll bar for the List. </description> </method> + <method name="is_anything_selected"> + <return type="bool"> + </return> + <description> + </description> + </method> <method name="is_item_disabled" qualifiers="const"> <return type="bool"> </return> @@ -180,6 +186,16 @@ Returns whether or not item at the specified index is currently selected. </description> </method> + <method name="move_item"> + <return type="void"> + </return> + <argument index="0" name="p_from_idx" type="int"> + </argument> + <argument index="1" name="p_to_idx" type="int"> + </argument> + <description> + </description> + </method> <method name="remove_item"> <return type="void"> </return> @@ -326,6 +342,12 @@ Ensure item at specified index is not selected. </description> </method> + <method name="unselect_all"> + <return type="void"> + </return> + <description> + </description> + </method> </methods> <members> <member name="allow_reselect" type="bool" setter="set_allow_reselect" getter="get_allow_reselect"> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml new file mode 100644 index 0000000000..591d715174 --- /dev/null +++ b/doc/classes/MultiplayerAPI.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="MultiplayerAPI" inherits="Reference" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="add_peer"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="clear"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="connected_to_server"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="connection_failed"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="del_peer"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_network_connected_peers" qualifiers="const"> + <return type="PoolIntArray"> + </return> + <description> + </description> + </method> + <method name="get_network_unique_id" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_rpc_sender_id" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="has_network_peer" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> + <method name="is_network_server" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> + <method name="poll"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="server_disconnected"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="set_root_node"> + <return type="void"> + </return> + <argument index="0" name="node" type="Node"> + </argument> + <description> + </description> + </method> + </methods> + <members> + <member name="network_peer" type="NetworkedMultiplayerPeer" setter="set_network_peer" getter="get_network_peer"> + </member> + <member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections"> + </member> + </members> + <signals> + <signal name="connected_to_server"> + <description> + </description> + </signal> + <signal name="connection_failed"> + <description> + </description> + </signal> + <signal name="network_peer_connected"> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </signal> + <signal name="network_peer_disconnected"> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </signal> + <signal name="server_disconnected"> + <description> + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/doc/classes/Navigation.xml b/doc/classes/Navigation.xml index b3b9ada26c..08f22d49d3 100644 --- a/doc/classes/Navigation.xml +++ b/doc/classes/Navigation.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Navigation" inherits="Spatial" category="Core" version="3.1"> <brief_description> - A collection of [code]NavigationMesh[/code] resources and methods used for pathfinding. + Mesh-based navigation and pathfinding node. </brief_description> <description> - The Navigation node is used for basic or advanced navigation. By default it will automatically collect all child [code]NavigationMesh[/code] resources, but they can also be added on the fly through scripting. It can be used for generating a simple path between two points or it can be used to ensure that a navigation agent is angled perfectly to the terrain it is navigating. + Provides navigation and pathfinding within a collection of [NavigationMesh]es. By default these will be automatically collected from child [NavigationMeshInstance] nodes, but they can also be added on the fly with [method navmesh_add]. In addition to basic pathfinding, this class also assists with aligning navigation agents with the meshes they are navigating on. </description> <tutorials> </tutorials> @@ -17,7 +17,7 @@ <argument index="0" name="to_point" type="Vector3"> </argument> <description> - Returns the closest navigation point to the point passed. + Returns the navigation point closest to the point given. Points are in local coordinate space. </description> </method> <method name="get_closest_point_normal"> @@ -26,7 +26,7 @@ <argument index="0" name="to_point" type="Vector3"> </argument> <description> - Returns the surface normal of the navigation mesh at the point passed. For instance, if the point passed was at a 45 degree slope it would return something like (0.5,0.5,0). This is useful for rotating a navigation agent in accordance with the [code]NavigationMesh[/code]. + Returns the surface normal at the navigation point closest to the point given. Useful for rotating a navigation agent according to the navigation mesh it moves on. </description> </method> <method name="get_closest_point_owner"> @@ -35,7 +35,7 @@ <argument index="0" name="to_point" type="Vector3"> </argument> <description> - Returns the nearest [code]NavigationMeshInstance[/code] to the point passed. + Returns the owner of the [NavigationMesh] which contains the navigation point closest to the point given. This is usually a [NavigtionMeshInstance]. For meshes added via [method navmesh_add], returns the owner that was given (or [code]null[/code] if the [code]owner[/code] parameter was omitted). </description> </method> <method name="get_closest_point_to_segment"> @@ -48,7 +48,7 @@ <argument index="2" name="use_collision" type="bool" default="false"> </argument> <description> - Returns the nearest point to the line segment passed. The third optional parameter takes collisions into account. + Returns the navigation point closest to the given line segment. When enabling [code]use_collision[/code], only considers intersection points between segment and navigation meshes. If multiple intersection points are found, the one closest to the segment start point is returned. </description> </method> <method name="get_simple_path"> @@ -61,7 +61,7 @@ <argument index="2" name="optimize" type="bool" default="true"> </argument> <description> - Returns a path of points as a [code]PoolVector3Array[/code]. If [code]optimize[/code] is false the [code]NavigationMesh[/code] agent properties will be taken into account, otherwise it will return the nearest path and ignore agent radius, height, etc. + Returns the path between two given points. Points are in local coordinate space. If [code]optimize[/code] is [code]true[/code] (the default), the agent properties associated with each [NavigationMesh] (raidus, height, etc.) are considered in the path calculation, otherwise they are ignored. </description> </method> <method name="navmesh_add"> @@ -74,7 +74,7 @@ <argument index="2" name="owner" type="Object" default="null"> </argument> <description> - Adds a [code]NavigationMesh[/code] to the list of NavigationMesh's in this node. Returns an id. Its position, rotation and scale are associated with the [code]Transform[/code] passed. The [code]Node[/code] (or [code]Object[/code]) that owns this node is an optional parameter. + Adds a [NavigationMesh]. Returns an ID for use with [method navmesh_remove] or [method navmesh_set_transform]. If given, a [Transform2D] is applied to the polygon. The optional [code]owner[/code] is used as return value for [method get_closest_point_owner]. </description> </method> <method name="navmesh_remove"> @@ -83,7 +83,7 @@ <argument index="0" name="id" type="int"> </argument> <description> - Removes a [code]NavigationMesh[/code] from the list of NavigationMesh's in this node. + Removes the [NavigationMesh] with the given ID. </description> </method> <method name="navmesh_set_transform"> @@ -94,13 +94,13 @@ <argument index="1" name="xform" type="Transform"> </argument> <description> - Associates a [code]NavigationMesh[/code]'s id with a [code]Transform[/code]. Its position, rotation and scale are based on the [code]Transform[/code] passed. + Sets the transform applied to the [NavigationMesh] with the given ID. </description> </method> </methods> <members> <member name="up_vector" type="Vector3" setter="set_up_vector" getter="get_up_vector"> - Defines which direction is up. The default defines 0,1,0 as up which is the world up direction. To make this a ceiling use 0,-1,0 to define down as up. + Defines which direction is up. By default this is [code](0, 1, 0)[/code], which is the world up direction. </member> </members> <constants> diff --git a/doc/classes/Navigation2D.xml b/doc/classes/Navigation2D.xml index d2f4513372..364da55f99 100644 --- a/doc/classes/Navigation2D.xml +++ b/doc/classes/Navigation2D.xml @@ -1,9 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Navigation2D" inherits="Node2D" category="Core" version="3.1"> <brief_description> - Class to assist with character navigation and pathfinding. + 2D navigation and pathfinding node. </brief_description> <description> + Navigation2D provides navigation and pathfinding within a 2D area, specified as a collection of [NavigationPolygon] resources. By default these are automatically collected from child [NavigationPolygonInstance] nodes, but they can also be added on the fly with [method navpoly_add]. </description> <tutorials> </tutorials> @@ -16,6 +17,7 @@ <argument index="0" name="to_point" type="Vector2"> </argument> <description> + Returns the navigation point closest to the point given. Points are in local coordinate space. </description> </method> <method name="get_closest_point_owner"> @@ -24,6 +26,7 @@ <argument index="0" name="to_point" type="Vector2"> </argument> <description> + Returns the owner of the [NavigationPolygon] which contains the navigation point closest to the point given. This is usually a [NavigtionPolygonInstance]. For polygons added via [method navpoly_add], returns the owner that was given (or [code]null[/code] if the [code]owner[/code] parameter was omitted). </description> </method> <method name="get_simple_path"> @@ -36,6 +39,7 @@ <argument index="2" name="optimize" type="bool" default="true"> </argument> <description> + Returns the path between two given points. Points are in local coordinate space. If [code]optimize[/code] is [code]true[/code] (the default), the path is smoothed by merging path segments where possible. </description> </method> <method name="navpoly_add"> @@ -48,6 +52,7 @@ <argument index="2" name="owner" type="Object" default="null"> </argument> <description> + Adds a [NavigationPolygon]. Returns an ID for use with [method navpoly_remove] or [method navpoly_set_transform]. If given, a [Transform2D] is applied to the polygon. The optional [code]owner[/code] is used as return value for [method get_closest_point_owner]. </description> </method> <method name="navpoly_remove"> @@ -56,6 +61,7 @@ <argument index="0" name="id" type="int"> </argument> <description> + Removes the [NavigationPolygon] with the given ID. </description> </method> <method name="navpoly_set_transform"> @@ -66,6 +72,7 @@ <argument index="1" name="xform" type="Transform2D"> </argument> <description> + Sets the transform applied to the [NavigationPolygon] with the given ID. </description> </method> </methods> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 9129f64340..5d0f70bc59 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -453,7 +453,30 @@ <return type="void"> </return> <description> - Prints the scene hierarchy of this node and all it's children to stdout. Used mainly for debugging purposes. + Prints the tree to stdout. Used mainly for debugging purposes. This version displays the path relative to the current node, and is good for copy/pasting into the [method get_node] function. Example output: + [codeblock] + TheGame + TheGame/Menu + TheGame/Menu/Label + TheGame/Menu/Camera2D + TheGame/SplashScreen + TheGame/SplashScreen/Camera2D + [/codeblock] + </description> + </method> + <method name="print_tree_pretty"> + <return type="void"> + </return> + <description> + Similar to [method print_tree], this prints the tree to stdout. This version displays a more graphical representation similar to what is displayed in the scene inspector. It is useful for inspecting larger trees. Example output: + [codeblock] + ┖╴TheGame + ┠╴Menu + ┃ ┠╴Label + ┃ ┖╴Camera2D + ┖-SplashScreen + ┖╴Camera2D + [/codeblock] </description> </method> <method name="propagate_call"> @@ -739,9 +762,13 @@ </method> </methods> <members> + <member name="custom_multiplayer_api" type="MultiplayerAPI" setter="set_custom_multiplayer_api" getter="get_custom_multiplayer_api"> + </member> <member name="filename" type="String" setter="set_filename" getter="get_filename"> When a scene is instanced from a file, its topmost node contains the filename from which it was loaded. </member> + <member name="multiplayer_api" type="MultiplayerAPI" setter="" getter="get_multiplayer_api"> + </member> <member name="name" type="String" setter="set_name" getter="get_name"> The name of the node. This name is unique among the siblings (other child nodes from the same parent). When set to an existing name, the node will be automatically renamed </member> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a98385f1d2..1a3cdc34d7 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -40,6 +40,7 @@ <return type="void"> </return> <description> + Centers the window on the screen if in windowed mode. </description> </method> <method name="delay_msec" qualifiers="const"> @@ -48,7 +49,7 @@ <argument index="0" name="msec" type="int"> </argument> <description> - Delay executing of the current thread by given milliseconds. + Delay execution of the current thread by given milliseconds. </description> </method> <method name="delay_usec" qualifiers="const"> @@ -57,7 +58,7 @@ <argument index="0" name="usec" type="int"> </argument> <description> - Delay executing of the current thread by given microseconds. + Delay execution of the current thread by given microseconds. </description> </method> <method name="dump_memory_to_file"> @@ -67,7 +68,7 @@ </argument> <description> Dumps the memory allocation ringlist to a file (only works in debug). - Entry format per line: "Address - Size - Description" + Entry format per line: "Address - Size - Description". </description> </method> <method name="dump_resources_to_file"> @@ -77,7 +78,7 @@ </argument> <description> Dumps all used resources to file (only works in debug). - Entry format per line: "Resource Type : Resource Location" + Entry format per line: "Resource Type : Resource Location". At the end of the file is a statistic of all used Resource Types. </description> </method> @@ -116,6 +117,22 @@ Returns the scancode of the given string (e.g. "Escape") </description> </method> + <method name="get_audio_driver_count" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the total number of available audio drivers. + </description> + </method> + <method name="get_audio_driver_name" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="arg0" type="int"> + </argument> + <description> + Returns the audio driver name for the given index. + </description> + </method> <method name="get_cmdline_args"> <return type="PoolStringArray"> </return> @@ -242,6 +259,7 @@ <return type="Vector2"> </return> <description> + Returns the window size including decorations like window borders. </description> </method> <method name="get_scancode_string" qualifiers="const"> @@ -366,7 +384,7 @@ <return type="int"> </return> <description> - Return the current unix timestamp. + Returns the current unix epoch timestamp. </description> </method> <method name="get_unix_time_from_datetime" qualifiers="const"> @@ -391,10 +409,25 @@ If the project name is empty, [code]user://[/code] falls back to [code]res://[/code]. </description> </method> + <method name="get_video_driver_count" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_video_driver_name" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="arg0" type="int"> + </argument> + <description> + </description> + </method> <method name="get_virtual_keyboard_height"> <return type="int"> </return> <description> + Returns the on-screen keyboard's height in pixels. Returns 0 if there is no keyboard or it is currently hidden. </description> </method> <method name="has_environment" qualifiers="const"> @@ -412,6 +445,7 @@ <argument index="0" name="tag_name" type="String"> </argument> <description> + Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. See feature tags documentation. </description> </method> <method name="has_touchscreen_ui_hint" qualifiers="const"> @@ -478,6 +512,7 @@ <return type="bool"> </return> <description> + Returns [code]true[/code] if the window should always be on top of other windows. </description> </method> <method name="kill"> @@ -614,6 +649,7 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> + Sets whether the window should always be on top. </description> </method> <method name="set_window_title"> @@ -654,27 +690,31 @@ The current screen index (starting from 0). </member> <member name="exit_code" type="int" setter="set_exit_code" getter="get_exit_code"> + The exit code passed to the OS when the main loop exits. </member> <member name="keep_screen_on" type="bool" setter="set_keep_screen_on" getter="is_keep_screen_on"> + If [code]true[/code] the engine tries to keep the screen on while the game is running. Useful on mobile. </member> <member name="low_processor_usage_mode" type="bool" setter="set_low_processor_usage_mode" getter="is_in_low_processor_usage_mode"> + If [code]true[/code] the engine optimizes for low processor usage by only refreshing the screen if needed. Can improve battery consumption on mobile. </member> - <member name="screen_orientation" type="int" setter="set_screen_orientation" getter="get_screen_orientation" enum="_OS.ScreenOrientation"> + <member name="screen_orientation" type="int" setter="set_screen_orientation" getter="get_screen_orientation" enum="OS.ScreenOrientation"> The current screen orientation. </member> <member name="vsync_enabled" type="bool" setter="set_use_vsync" getter="is_vsync_enabled"> + If [code]true[/code] vertical synchronization (Vsync) is enabled. </member> <member name="window_borderless" type="bool" setter="set_borderless_window" getter="get_borderless_window"> - If [code]true[/code], removes the window frame. + If [code]true[/code] removes the window frame. </member> <member name="window_fullscreen" type="bool" setter="set_window_fullscreen" getter="is_window_fullscreen"> - If [code]true[/code], the window is fullscreen. + If [code]true[/code] the window is fullscreen. </member> <member name="window_maximized" type="bool" setter="set_window_maximized" getter="is_window_maximized"> - If [code]true[/code], the window is maximized. + If [code]true[/code] the window is maximized. </member> <member name="window_minimized" type="bool" setter="set_window_minimized" getter="is_window_minimized"> - If [code]true[/code], the window is minimized. + If [code]true[/code] the window is minimized. </member> <member name="window_position" type="Vector2" setter="set_window_position" getter="get_window_position"> The window position relative to the screen, the origin is the top left corner, +Y axis goes to the bottom and +X axis goes to the right. @@ -688,82 +728,121 @@ </members> <constants> <constant name="DAY_SUNDAY" value="0" enum="Weekday"> + Sunday. </constant> <constant name="DAY_MONDAY" value="1" enum="Weekday"> + Monday. </constant> <constant name="DAY_TUESDAY" value="2" enum="Weekday"> + Tuesday. </constant> <constant name="DAY_WEDNESDAY" value="3" enum="Weekday"> + Wednesday. </constant> <constant name="DAY_THURSDAY" value="4" enum="Weekday"> + Thursday. </constant> <constant name="DAY_FRIDAY" value="5" enum="Weekday"> + Friday. </constant> <constant name="DAY_SATURDAY" value="6" enum="Weekday"> + Saturday. </constant> <constant name="MONTH_JANUARY" value="1" enum="Month"> + January. </constant> <constant name="MONTH_FEBRUARY" value="2" enum="Month"> + February. </constant> <constant name="MONTH_MARCH" value="3" enum="Month"> + March. </constant> <constant name="MONTH_APRIL" value="4" enum="Month"> + April. </constant> <constant name="MONTH_MAY" value="5" enum="Month"> + May. </constant> <constant name="MONTH_JUNE" value="6" enum="Month"> + June. </constant> <constant name="MONTH_JULY" value="7" enum="Month"> + July. </constant> <constant name="MONTH_AUGUST" value="8" enum="Month"> + August. </constant> <constant name="MONTH_SEPTEMBER" value="9" enum="Month"> + September. </constant> <constant name="MONTH_OCTOBER" value="10" enum="Month"> + October. </constant> <constant name="MONTH_NOVEMBER" value="11" enum="Month"> + November. </constant> <constant name="MONTH_DECEMBER" value="12" enum="Month"> + December. </constant> <constant name="SCREEN_ORIENTATION_LANDSCAPE" value="0" enum="ScreenOrientation"> + Landscape screen orientation. </constant> <constant name="SCREEN_ORIENTATION_PORTRAIT" value="1" enum="ScreenOrientation"> + Portrait screen orientation. </constant> <constant name="SCREEN_ORIENTATION_REVERSE_LANDSCAPE" value="2" enum="ScreenOrientation"> + Reverse landscape screen orientation. </constant> <constant name="SCREEN_ORIENTATION_REVERSE_PORTRAIT" value="3" enum="ScreenOrientation"> + Reverse portrait screen orientation. </constant> <constant name="SCREEN_ORIENTATION_SENSOR_LANDSCAPE" value="4" enum="ScreenOrientation"> + Uses landscape or reverse landscape based on the hardware sensor. </constant> <constant name="SCREEN_ORIENTATION_SENSOR_PORTRAIT" value="5" enum="ScreenOrientation"> + Uses portrait or reverse portrait based on the hardware sensor. </constant> <constant name="SCREEN_ORIENTATION_SENSOR" value="6" enum="ScreenOrientation"> + Uses most suitable orientation based on the hardware sensor. </constant> <constant name="SYSTEM_DIR_DESKTOP" value="0" enum="SystemDir"> + Desktop directory path. </constant> <constant name="SYSTEM_DIR_DCIM" value="1" enum="SystemDir"> + DCIM (Digital Camera Images) directory path. </constant> <constant name="SYSTEM_DIR_DOCUMENTS" value="2" enum="SystemDir"> + Documents directory path. </constant> <constant name="SYSTEM_DIR_DOWNLOADS" value="3" enum="SystemDir"> + Downloads directory path. </constant> <constant name="SYSTEM_DIR_MOVIES" value="4" enum="SystemDir"> + Movies directory path. </constant> <constant name="SYSTEM_DIR_MUSIC" value="5" enum="SystemDir"> + Music directory path. </constant> <constant name="SYSTEM_DIR_PICTURES" value="6" enum="SystemDir"> + Pictures directory path. </constant> <constant name="SYSTEM_DIR_RINGTONES" value="7" enum="SystemDir"> + Ringtones directory path. </constant> <constant name="POWERSTATE_UNKNOWN" value="0" enum="PowerState"> + Unknown powerstate. </constant> <constant name="POWERSTATE_ON_BATTERY" value="1" enum="PowerState"> + Unplugged, running on battery. </constant> <constant name="POWERSTATE_NO_BATTERY" value="2" enum="PowerState"> + Plugged in, no battery available. </constant> <constant name="POWERSTATE_CHARGING" value="3" enum="PowerState"> + Plugged in, battery charging. </constant> <constant name="POWERSTATE_CHARGED" value="4" enum="PowerState"> + Plugged in, battery fully charged. </constant> </constants> </class> diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml index de6f60c384..9957106177 100644 --- a/doc/classes/OptionButton.xml +++ b/doc/classes/OptionButton.xml @@ -194,6 +194,12 @@ </member> </members> <signals> + <signal name="item_focused"> + <argument index="0" name="ID" type="int"> + </argument> + <description> + </description> + </signal> <signal name="item_selected"> <argument index="0" name="ID" type="int"> </argument> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 6ead220236..12bff45291 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -24,19 +24,6 @@ Add a new checkable item with text "label". An id can optionally be provided, as well as an accelerator. If no id is provided, one will be created from the index. Note that checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. </description> </method> - <method name="add_radio_check_item"> - <return type="void"> - </return> - <argument index="0" name="label" type="String"> - </argument> - <argument index="1" name="id" type="int" default="-1"> - </argument> - <argument index="2" name="accel" type="int" default="0"> - </argument> - <description> - The same as [method add_check_item] but the inserted item will look as a radio button. Remember this is just cosmetic and you have to add the logic for checking/unchecking items in radio groups. - </description> - </method> <method name="add_check_shortcut"> <return type="void"> </return> @@ -49,18 +36,6 @@ <description> </description> </method> - <method name="add_radio_check_shortcut"> - <return type="void"> - </return> - <argument index="0" name="shortcut" type="ShortCut"> - </argument> - <argument index="1" name="id" type="int" default="-1"> - </argument> - <argument index="2" name="global" type="bool" default="false"> - </argument> - <description> - </description> - </method> <method name="add_icon_check_item"> <return type="void"> </return> @@ -133,6 +108,31 @@ Add a new item with text "label". An id can optionally be provided, as well as an accelerator keybinding. If no id is provided, one will be created from the index. </description> </method> + <method name="add_radio_check_item"> + <return type="void"> + </return> + <argument index="0" name="label" type="String"> + </argument> + <argument index="1" name="id" type="int" default="-1"> + </argument> + <argument index="2" name="accel" type="int" default="0"> + </argument> + <description> + The same as [method add_check_item] but the inserted item will look as a radio button. Remember this is just cosmetic and you have to add the logic for checking/unchecking items in radio groups. + </description> + </method> + <method name="add_radio_check_shortcut"> + <return type="void"> + </return> + <argument index="0" name="shortcut" type="ShortCut"> + </argument> + <argument index="1" name="id" type="int" default="-1"> + </argument> + <argument index="2" name="global" type="bool" default="false"> + </argument> + <description> + </description> + </method> <method name="add_separator"> <return type="void"> </return> @@ -267,31 +267,31 @@ Return whether the item at index "idx" is checkable in some way, i.e., whether has a checkbox or radio button. Note that checkable items just display a checkmark or radio button, but don't have any built-in checking behavior and must be checked/unchecked manually. </description> </method> - <method name="is_item_radio_checkable" qualifiers="const"> + <method name="is_item_checked" qualifiers="const"> <return type="bool"> </return> <argument index="0" name="idx" type="int"> </argument> <description> - Return whether the item at index "idx" has radio-button-style checkability. Remember this is just cosmetic and you have to add the logic for checking/unchecking items in radio groups. + Return whether the item at index "idx" is checked. </description> </method> - <method name="is_item_checked" qualifiers="const"> + <method name="is_item_disabled" qualifiers="const"> <return type="bool"> </return> <argument index="0" name="idx" type="int"> </argument> <description> - Return whether the item at index "idx" is checked. + Return whether the item at index "idx" is disabled. When it is disabled it can't be selected, or its action invoked. </description> </method> - <method name="is_item_disabled" qualifiers="const"> + <method name="is_item_radio_checkable" qualifiers="const"> <return type="bool"> </return> <argument index="0" name="idx" type="int"> </argument> <description> - Return whether the item at index "idx" is disabled. When it is disabled it can't be selected, or its action invoked. + Return whether the item at index "idx" has radio-button-style checkability. Remember this is just cosmetic and you have to add the logic for checking/unchecking items in radio groups. </description> </method> <method name="is_item_separator" qualifiers="const"> @@ -491,6 +491,12 @@ </member> </members> <signals> + <signal name="id_focused"> + <argument index="0" name="ID" type="int"> + </argument> + <description> + </description> + </signal> <signal name="id_pressed"> <argument index="0" name="ID" type="int"> </argument> @@ -529,6 +535,10 @@ </theme_item> <theme_item name="panel_disabled" type="StyleBox"> </theme_item> + <theme_item name="radio_checked" type="Texture"> + </theme_item> + <theme_item name="radio_unchecked" type="Texture"> + </theme_item> <theme_item name="separator" type="StyleBox"> </theme_item> <theme_item name="submenu" type="Texture"> diff --git a/doc/classes/Quat.xml b/doc/classes/Quat.xml index 3a258011b2..33f2b9758b 100644 --- a/doc/classes/Quat.xml +++ b/doc/classes/Quat.xml @@ -10,6 +10,7 @@ It can be used to perform SLERP (spherical-linear interpolation) between two rotations. </description> <tutorials> + http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions </tutorials> <demos> </demos> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index be90288308..8846616851 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SceneTree" inherits="MainLoop" category="Core" version="3.1"> <brief_description> + SceneTree manages a hierarchy of nodes. </brief_description> <description> + As one of the most important classes, the [code]SceneTree[/code] manages the hierarchy of nodes in a scene as well as scenes themselves. Nodes can be added, retrieved and removed. The whole scene tree (and thus the current scene) can be paused. Scenes can be loaded, switched and reloaded. You can also use the SceneTree to organize your nodes into groups: every node can be assigned as many groups as you want to create, e.g. a "enemy" group. You can then iterate these groups or even call methods and set properties on all the group's members at once. </description> <tutorials> http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scene_tree.html - http://docs.godotengine.org/en/3.0/tutorials/viewports/multiple_resolutions.html + http://docs.godotengine.org/en/3.0/tutorials/viewports/multiple_resolutions.html </tutorials> <demos> </demos> @@ -19,6 +21,7 @@ <argument index="1" name="method" type="String"> </argument> <description> + Calls [code]method[/code] on each member of the given group. </description> </method> <method name="call_group_flags" qualifiers="vararg"> @@ -31,6 +34,7 @@ <argument index="2" name="method" type="String"> </argument> <description> + Calls [code]method[/code] on each member of the given group, respecting the given [enum GROUP_CALL] flags. </description> </method> <method name="change_scene"> @@ -39,6 +43,7 @@ <argument index="0" name="path" type="String"> </argument> <description> + Changes to the scene at the given [code]path[/code]. </description> </method> <method name="change_scene_to"> @@ -47,6 +52,7 @@ <argument index="0" name="packed_scene" type="PackedScene"> </argument> <description> + Changes to the given [PackedScene]. </description> </method> <method name="create_timer"> @@ -57,6 +63,7 @@ <argument index="1" name="pause_mode_process" type="bool" default="true"> </argument> <description> + Returns a [SceneTreeTimer] which will [signal SceneTreeTimer.timeout] after the given time in seconds elapsed in this SceneTree. If [code]pause_mode_process[/code] is set to false, pausing the SceneTree will also pause the timer. </description> </method> <method name="get_frame" qualifiers="const"> @@ -69,18 +76,21 @@ <return type="PoolIntArray"> </return> <description> + Returns the peer IDs of all connected peers of this SceneTree's [member network_peer]. </description> </method> <method name="get_network_unique_id" qualifiers="const"> <return type="int"> </return> <description> + Returns the unique peer ID of this SceneTree's [member network_peer]. </description> </method> <method name="get_node_count" qualifiers="const"> <return type="int"> </return> <description> + Returns the number of nodes in this SceneTree. </description> </method> <method name="get_nodes_in_group"> @@ -89,12 +99,14 @@ <argument index="0" name="group" type="String"> </argument> <description> + Returns all nodes assigned to the given group. </description> </method> <method name="get_rpc_sender_id" qualifiers="const"> <return type="int"> </return> <description> + Returns the sender's peer ID for the most recently received RPC call. </description> </method> <method name="has_group" qualifiers="const"> @@ -103,26 +115,28 @@ <argument index="0" name="name" type="String"> </argument> <description> + Returns [code]true[/code] if the given group exists. </description> </method> <method name="has_network_peer" qualifiers="const"> <return type="bool"> </return> <description> - Returns true if there is a [NetworkedMultiplayerPeer] set (with [method SceneTree.set_network_peer]). + Returns [code]true[/code] if there is a [member network_peer] set. </description> </method> <method name="is_input_handled"> <return type="bool"> </return> <description> + Returns [code]true[/code] if the most recent InputEvent was marked as handled with [method set_input_as_handled]. </description> </method> <method name="is_network_server" qualifiers="const"> <return type="bool"> </return> <description> - Returns true if this SceneTree's [NetworkedMultiplayerPeer] is in server mode (listening for connections). + Returns [code]true[/code] if this SceneTree's [member network_peer] is in server mode (listening for connections). </description> </method> <method name="notify_group"> @@ -133,6 +147,7 @@ <argument index="1" name="notification" type="int"> </argument> <description> + Sends the given notification to all members of the [code]group[/code]. </description> </method> <method name="notify_group_flags"> @@ -145,6 +160,7 @@ <argument index="2" name="notification" type="int"> </argument> <description> + Sends the given notification to all members of the [code]group[/code], respecting the given [enum GROUP_CALL] flags. </description> </method> <method name="queue_delete"> @@ -153,18 +169,21 @@ <argument index="0" name="obj" type="Object"> </argument> <description> + Queues the given object for deletion, delaying the call to [method Object.free] to after the current frame. </description> </method> <method name="quit"> <return type="void"> </return> <description> + Quits the application. </description> </method> <method name="reload_current_scene"> <return type="int" enum="Error"> </return> <description> + Reloads the currently active scene. </description> </method> <method name="set_auto_accept_quit"> @@ -173,6 +192,7 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> + If [code]true[/code] the application automatically accepts quitting. </description> </method> <method name="set_group"> @@ -185,6 +205,7 @@ <argument index="2" name="value" type="Variant"> </argument> <description> + Sets the given [code]property[/code] to [code]value[/code] on all members of the given group. </description> </method> <method name="set_group_flags"> @@ -199,12 +220,14 @@ <argument index="3" name="value" type="Variant"> </argument> <description> + Sets the given [code]property[/code] to [code]value[/code] on all members of the given group, respecting the given [enum GROUP_CALL] flags. </description> </method> <method name="set_input_as_handled"> <return type="void"> </return> <description> + Marks the most recent input event as handled. </description> </method> <method name="set_quit_on_go_back"> @@ -213,6 +236,7 @@ <argument index="0" name="enabled" type="bool"> </argument> <description> + If [code]true[/code] the application quits automatically on going back (e.g. on Android). </description> </method> <method name="set_screen_stretch"> @@ -227,37 +251,48 @@ <argument index="3" name="shrink" type="float" default="1"> </argument> <description> + Configures screen stretching to the given [enum StretchMode], [enum StretchAspect], minimum size and [code]shrink[/code]. </description> </method> </methods> <members> <member name="current_scene" type="Node" setter="set_current_scene" getter="get_current_scene"> + The current scene. </member> <member name="debug_collisions_hint" type="bool" setter="set_debug_collisions_hint" getter="is_debugging_collisions_hint"> </member> <member name="debug_navigation_hint" type="bool" setter="set_debug_navigation_hint" getter="is_debugging_navigation_hint"> </member> <member name="edited_scene_root" type="Node" setter="set_edited_scene_root" getter="get_edited_scene_root"> + The root of the edited scene. + </member> + <member name="multiplayer_api" type="MultiplayerAPI" setter="set_multiplayer_api" getter="get_multiplayer_api"> </member> <member name="network_peer" type="NetworkedMultiplayerPeer" setter="set_network_peer" getter="get_network_peer"> The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the SceneTree will become a network server (check with [method is_network_server()]) and will set root node's network mode to master (see NETWORK_MODE_* constants in [Node]), or it will become a regular peer with root node set to slave. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to SceneTree's signals. </member> <member name="paused" type="bool" setter="set_pause" getter="is_paused"> + If [code]true[/code] the SceneTree is paused. </member> <member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections"> + If [code]true[/code] the SceneTree's [member network_peer] refuses new incoming connections. </member> <member name="root" type="Viewport" setter="" getter="get_root"> + The SceneTree's [Viewport]. </member> <member name="use_font_oversampling" type="bool" setter="set_use_font_oversampling" getter="is_using_font_oversampling"> + If [code]true[/code] font oversampling is used. </member> </members> <signals> <signal name="connected_to_server"> <description> + Emitted whenever this SceneTree's [member network_peer] successfully connected to a server. Only emitted on clients. </description> </signal> <signal name="connection_failed"> <description> + Emitted whenever this SceneTree's [member network_peer] fails to establish a connection to a server. Only emitted on clients. </description> </signal> <signal name="files_dropped"> @@ -266,56 +301,67 @@ <argument index="1" name="screen" type="int"> </argument> <description> + Emitted whenever files are drag-and-dropped onto the window. </description> </signal> <signal name="idle_frame"> <description> + Emitted immediately before [method Node._process] is called on every node in the SceneTree. </description> </signal> <signal name="network_peer_connected"> <argument index="0" name="id" type="int"> </argument> <description> + Emitted whenever this SceneTree's [member network_peer] connects with a new peer. ID is the peer ID of the new peer. Clients get notified when other clients connect to the same server. Upon connecting to a server, a client also receives this signal for the server (with ID being 1). </description> </signal> <signal name="network_peer_disconnected"> <argument index="0" name="id" type="int"> </argument> <description> + Emitted whenever this SceneTree's [member network_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. </description> </signal> <signal name="node_added"> <argument index="0" name="node" type="Object"> </argument> <description> + Emitted whenever a node is added to the SceneTree. </description> </signal> <signal name="node_configuration_warning_changed"> <argument index="0" name="node" type="Object"> </argument> <description> + Emitted when a node's configuration changed. Only emitted in tool mode. </description> </signal> <signal name="node_removed"> <argument index="0" name="node" type="Object"> </argument> <description> + Emitted whenever a node is removed from the SceneTree. </description> </signal> <signal name="physics_frame"> <description> + Emitted immediately before [method Node._physics_process] is called on every node in the SceneTree. </description> </signal> <signal name="screen_resized"> <description> + Emitted whenever the screen resolution (fullscreen) or window size (windowed) changes. </description> </signal> <signal name="server_disconnected"> <description> + Emitted whenever this SceneTree's [member network_peer] disconnected from server. Only emitted on clients. </description> </signal> <signal name="tree_changed"> <description> + Emitted whenever the SceneTree hierarchy changed (children being moved or renamed, etc.). </description> </signal> </signals> diff --git a/doc/classes/ScrollContainer.xml b/doc/classes/ScrollContainer.xml index 79b70fec99..28fbdea58d 100644 --- a/doc/classes/ScrollContainer.xml +++ b/doc/classes/ScrollContainer.xml @@ -13,6 +13,8 @@ <methods> </methods> <members> + <member name="scroll_deadzone" type="int" setter="set_deadzone" getter="get_deadzone"> + </member> <member name="scroll_horizontal" type="int" setter="set_h_scroll" getter="get_h_scroll"> The current horizontal scroll value. </member> @@ -26,6 +28,16 @@ If [code]true[/code], enables vertical scrolling. </member> </members> + <signals> + <signal name="scroll_ended"> + <description> + </description> + </signal> + <signal name="scroll_started"> + <description> + </description> + </signal> + </signals> <constants> </constants> </class> diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml index d8644c5419..7d78d71330 100644 --- a/doc/classes/SurfaceTool.xml +++ b/doc/classes/SurfaceTool.xml @@ -196,8 +196,11 @@ <method name="generate_normals"> <return type="void"> </return> + <argument index="0" name="flip" type="bool" default="false"> + </argument> <description> Generates normals from Vertices so you do not have to do it manually. + Setting "flip" [code]true[/code] inverts resulting normals. </description> </method> <method name="generate_tangents"> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 330487adf7..11e94abc03 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -78,6 +78,12 @@ Returns the title of the tab at index [code]tab_idx[/code]. Tab titles default to the name of the indexed child node, but this can be overridden with [method set_tab_title]. </description> </method> + <method name="get_tabs_rearrange_group" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> <method name="set_popup"> <return type="void"> </return> @@ -120,11 +126,21 @@ Sets a title for the tab at index [code]tab_idx[/code]. Tab titles default to the name of the indexed child node, but this can be overridden with [method set_tab_title]. </description> </method> + <method name="set_tabs_rearrange_group"> + <return type="void"> + </return> + <argument index="0" name="group_id" type="int"> + </argument> + <description> + </description> + </method> </methods> <members> <member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab"> The current tab index. When set, this index's [Control] node's [code]visible[/code] property is set to [code]true[/code] and all others are set to [code]false[/code]. </member> + <member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled"> + </member> <member name="tab_align" type="int" setter="set_tab_align" getter="get_tab_align" enum="TabContainer.TabAlign"> The alignment of all tabs in the tab container. See the [code]ALIGN_*[/code] constants for details. </member> diff --git a/doc/classes/Tabs.xml b/doc/classes/Tabs.xml index 615bc83c2d..0d5f1f0ba9 100644 --- a/doc/classes/Tabs.xml +++ b/doc/classes/Tabs.xml @@ -80,6 +80,12 @@ <description> </description> </method> + <method name="get_tabs_rearrange_group" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> <method name="move_tab"> <return type="void"> </return> @@ -129,10 +135,20 @@ <description> </description> </method> + <method name="set_tabs_rearrange_group"> + <return type="void"> + </return> + <argument index="0" name="group_id" type="int"> + </argument> + <description> + </description> + </method> </methods> <members> <member name="current_tab" type="int" setter="set_current_tab" getter="get_current_tab"> </member> + <member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled"> + </member> <member name="scrolling_enabled" type="bool" setter="set_scrolling_enabled" getter="get_scrolling_enabled"> </member> <member name="tab_align" type="int" setter="set_tab_align" getter="get_tab_align" enum="Tabs.TabAlign"> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 8a71cb2059..76d94fbfbc 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -130,6 +130,14 @@ <description> </description> </method> + <method name="get_keyword_color" qualifiers="const"> + <return type="Color"> + </return> + <argument index="0" name="keyword" type="String"> + </argument> + <description> + </description> + </method> <method name="get_line" qualifiers="const"> <return type="String"> </return> @@ -193,6 +201,14 @@ <description> </description> </method> + <method name="has_keyword_color" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="keyword" type="String"> + </argument> + <description> + </description> + </method> <method name="insert_text_at_cursor"> <return type="void"> </return> diff --git a/doc/classes/TextureProgress.xml b/doc/classes/TextureProgress.xml index 18bd886b0b..2ae4ef2cf8 100644 --- a/doc/classes/TextureProgress.xml +++ b/doc/classes/TextureProgress.xml @@ -51,6 +51,12 @@ <member name="texture_under" type="Texture" setter="set_under_texture" getter="get_under_texture"> [Texture] that draws under the progress bar. The bar's background. </member> + <member name="tint_over" type="Color" setter="set_tint_over" getter="get_tint_over"> + </member> + <member name="tint_progress" type="Color" setter="set_tint_progress" getter="get_tint_progress"> + </member> + <member name="tint_under" type="Color" setter="set_tint_under" getter="get_tint_under"> + </member> </members> <constants> <constant name="FILL_LEFT_TO_RIGHT" value="0" enum="FillMode"> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 10978970e9..4e218f5595 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -254,6 +254,14 @@ Return the texture offset of the tile. </description> </method> + <method name="tile_get_tile_mode" qualifiers="const"> + <return type="int" enum="TileSet.TileMode"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> <method name="tile_set_light_occluder"> <return type="void"> </return> @@ -410,6 +418,16 @@ Set the texture offset of the tile. </description> </method> + <method name="tile_set_tile_mode"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="tilemode" type="int" enum="TileSet.TileMode"> + </argument> + <description> + </description> + </method> </methods> <constants> <constant name="BITMASK_2X2" value="0" enum="BitmaskMode"> @@ -432,5 +450,11 @@ </constant> <constant name="BIND_BOTTOMRIGHT" value="256" enum="AutotileBindings"> </constant> + <constant name="SINGLE_TILE" value="0" enum="TileMode"> + </constant> + <constant name="AUTO_TILE" value="1" enum="TileMode"> + </constant> + <constant name="ANIMATED_TILE" value="2" enum="TileMode"> + </constant> </constants> </class> diff --git a/doc/classes/Transform.xml b/doc/classes/Transform.xml index df5c2ab3ae..d9f9d8cc73 100644 --- a/doc/classes/Transform.xml +++ b/doc/classes/Transform.xml @@ -8,6 +8,7 @@ </description> <tutorials> http://docs.godotengine.org/en/3.0/tutorials/math/index.html + http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html </tutorials> <demos> </demos> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index b293ec3368..c9fc7d3fb5 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -84,7 +84,7 @@ <method name="cross"> <return type="float"> </return> - <argument index="0" name="b" type="Vector2"> + <argument index="0" name="with" type="Vector2"> </argument> <description> Returns the 2-dimensional analog of the cross product with [code]b[/code]. diff --git a/doc/classes/VideoPlayer.xml b/doc/classes/VideoPlayer.xml index 48d09ccd95..d2639590a1 100644 --- a/doc/classes/VideoPlayer.xml +++ b/doc/classes/VideoPlayer.xml @@ -72,6 +72,12 @@ <member name="volume_db" type="float" setter="set_volume_db" getter="get_volume_db"> </member> </members> + <signals> + <signal name="finished"> + <description> + </description> + </signal> + </signals> <constants> </constants> </class> diff --git a/doc/classes/WebSocketClient.xml b/doc/classes/WebSocketClient.xml index 79313c90c7..496baa5787 100644 --- a/doc/classes/WebSocketClient.xml +++ b/doc/classes/WebSocketClient.xml @@ -28,6 +28,10 @@ </description> </method> </methods> + <members> + <member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled"> + </member> + </members> <signals> <signal name="connection_closed"> <description> diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h index 1ef474ac19..9891920263 100644 --- a/drivers/coreaudio/audio_driver_coreaudio.h +++ b/drivers/coreaudio/audio_driver_coreaudio.h @@ -56,9 +56,11 @@ class AudioDriverCoreAudio : public AudioDriver { Vector<int32_t> samples_in; +#ifdef OSX_ENABLED static OSStatus output_device_address_cb(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inClientData); +#endif static OSStatus output_callback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index a287dca1ed..f91ed35331 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "rasterizer_storage_gles3.h" +#include "engine.h" #include "project_settings.h" #include "rasterizer_canvas_gles3.h" #include "rasterizer_scene_gles3.h" @@ -5855,6 +5856,8 @@ void RasterizerStorageGLES3::update_particles() { shaders.particles.set_uniform(ParticlesShaderGLES3::EMITTING, particles->emitting); shaders.particles.set_uniform(ParticlesShaderGLES3::RANDOMNESS, particles->randomness); + bool zero_time_scale = Engine::get_singleton()->get_time_scale() <= 0.0; + if (particles->clear && particles->pre_process_time > 0.0) { float frame_time; @@ -5872,7 +5875,15 @@ void RasterizerStorageGLES3::update_particles() { } if (particles->fixed_fps > 0) { - float frame_time = 1.0 / particles->fixed_fps; + float frame_time; + float decr; + if (zero_time_scale) { + frame_time = 0.0; + decr = 1.0 / particles->fixed_fps; + } else { + frame_time = 1.0 / particles->fixed_fps; + decr = frame_time; + } float delta = frame.delta; if (delta > 0.1) { //avoid recursive stalls if fps goes below 10 delta = 0.1; @@ -5883,13 +5894,16 @@ void RasterizerStorageGLES3::update_particles() { while (todo >= frame_time) { _particles_process(particles, frame_time); - todo -= frame_time; + todo -= decr; } particles->frame_remainder = todo; } else { - _particles_process(particles, frame.delta); + if (zero_time_scale) + _particles_process(particles, 0.0); + else + _particles_process(particles, frame.delta); } particle_update_list.remove(particle_update_list.first()); diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 966b69a67e..e1680601ad 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -37,6 +37,17 @@ #include <functiondiscoverykeys.h> +#ifndef PKEY_Device_FriendlyName + +#undef DEFINE_PROPERTYKEY +/* clang-format off */ +#define DEFINE_PROPERTYKEY(id, a, b, c, d, e, f, g, h, i, j, k, l) \ + const PROPERTYKEY id = { { a, b, c, { d, e, f, g, h, i, j, k, } }, l }; +/* clang-format on */ + +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); +#endif + const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioClient = __uuidof(IAudioClient); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 141769b16a..c31bec9a1b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3892,6 +3892,53 @@ void EditorNode::_update_dock_slots_visibility() { } } +void EditorNode::_dock_tab_changed(int p_tab) { + + // update visibility but dont set current tab + VSplitContainer *splits[DOCK_SLOT_MAX / 2] = { + left_l_vsplit, + left_r_vsplit, + right_l_vsplit, + right_r_vsplit, + }; + + if (!docks_visible) { + + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + dock_slot[i]->hide(); + } + + for (int i = 0; i < DOCK_SLOT_MAX / 2; i++) { + splits[i]->hide(); + } + + right_hsplit->hide(); + bottom_panel->hide(); + } else { + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + + if (dock_slot[i]->get_tab_count()) + dock_slot[i]->show(); + else + dock_slot[i]->hide(); + } + + for (int i = 0; i < DOCK_SLOT_MAX / 2; i++) { + bool in_use = dock_slot[i * 2 + 0]->get_tab_count() || dock_slot[i * 2 + 1]->get_tab_count(); + if (in_use) + splits[i]->show(); + else + splits[i]->hide(); + } + bottom_panel->show(); + + if (right_l_vsplit->is_visible() || right_r_vsplit->is_visible()) + right_hsplit->show(); + else + right_hsplit->hide(); + } +} + void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) { for (int i = 0; i < DOCK_SLOT_MAX; i++) { @@ -4762,6 +4809,7 @@ void EditorNode::_bind_methods() { ClassDB::bind_method("_dock_popup_exit", &EditorNode::_dock_popup_exit); ClassDB::bind_method("_dock_move_left", &EditorNode::_dock_move_left); ClassDB::bind_method("_dock_move_right", &EditorNode::_dock_move_right); + ClassDB::bind_method("_dock_tab_changed", &EditorNode::_dock_tab_changed); ClassDB::bind_method("_layout_menu_option", &EditorNode::_layout_menu_option); @@ -5121,6 +5169,9 @@ EditorNode::EditorNode() { dock_slot[i]->set_popup(dock_select_popup); dock_slot[i]->connect("pre_popup_pressed", this, "_dock_pre_popup", varray(i)); dock_slot[i]->set_tab_align(TabContainer::ALIGN_LEFT); + dock_slot[i]->set_drag_to_rearrange_enabled(true); + dock_slot[i]->set_tabs_rearrange_group(1); + dock_slot[i]->connect("tab_changed", this, "_dock_tab_changed"); } dock_drag_timer = memnew(Timer); @@ -5158,6 +5209,7 @@ EditorNode::EditorNode() { scene_tabs->set_tab_align(Tabs::ALIGN_LEFT); scene_tabs->set_tab_close_display_policy((bool(EDITOR_DEF("interface/scene_tabs/always_show_close_button", false)) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY)); scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE); + scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->connect("tab_changed", this, "_scene_tab_changed"); scene_tabs->connect("right_button_pressed", this, "_scene_tab_script_edited"); scene_tabs->connect("tab_close", this, "_scene_tab_closed"); diff --git a/editor/editor_node.h b/editor/editor_node.h index 90bebffca6..f774fa0a2e 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -569,6 +569,7 @@ private: void _save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section); void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section); void _update_dock_slots_visibility(); + void _dock_tab_changed(int p_tab); bool restoring_scenes; void _save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 16223dbb16..d4c7d7483e 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -533,6 +533,7 @@ void FileSystemDock::_update_files(bool p_keep_selection) { filelist.push_back(fi); } + filelist.sort(); } String oi = "Object"; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp new file mode 100644 index 0000000000..9442bbc0e8 --- /dev/null +++ b/editor/find_in_files.cpp @@ -0,0 +1,829 @@ +/*************************************************************************/ +/* find_in_files.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#include "find_in_files.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/item_list.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/progress_bar.h" + +#define ROOT_PREFIX "res://" + +const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found"; +const char *FindInFiles::SIGNAL_FINISHED = "finished"; + +// TODO Would be nice in Vector and PoolVectors +template <typename T> +inline void pop_back(T &container) { + container.resize(container.size() - 1); +} + +// TODO Copied from TextEdit private, would be nice to extract it in a single place +static bool is_text_char(CharType c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +FindInFiles::FindInFiles() { + _root_prefix = ROOT_PREFIX; + _extension_filter.insert("gd"); + _extension_filter.insert("cs"); + _searching = false; + _whole_words = true; + _match_case = true; +} + +void FindInFiles::set_search_text(String p_pattern) { + _pattern = p_pattern; +} + +void FindInFiles::set_whole_words(bool p_whole_word) { + _whole_words = p_whole_word; +} + +void FindInFiles::set_match_case(bool p_match_case) { + _match_case = p_match_case; +} + +void FindInFiles::set_folder(String folder) { + _root_dir = folder; +} + +void FindInFiles::set_filter(const Set<String> &exts) { + _extension_filter = exts; +} + +void FindInFiles::_notification(int p_notification) { + if (p_notification == NOTIFICATION_PROCESS) { + _process(); + } +} + +void FindInFiles::start() { + if (_pattern == "") { + print_line("Nothing to search, pattern is empty"); + emit_signal(SIGNAL_FINISHED); + return; + } + if (_extension_filter.size() == 0) { + print_line("Nothing to search, filter matches no files"); + emit_signal(SIGNAL_FINISHED); + return; + } + + // Init search + _current_dir = ""; + PoolStringArray init_folder; + init_folder.append(_root_dir); + _folders_stack.push_back(init_folder); + + _initial_files_count = 0; + + _searching = true; + set_process(true); +} + +void FindInFiles::stop() { + _searching = false; + _current_dir = ""; + set_process(false); +} + +void FindInFiles::_process() { + // This part can be moved to a thread if needed + + OS &os = *OS::get_singleton(); + float duration = 0.0; + while (duration < 1.0 / 120.0) { + float time_before = os.get_ticks_msec(); + _iterate(); + duration += (os.get_ticks_msec() - time_before); + } +} + +void FindInFiles::_iterate() { + + if (_folders_stack.size() != 0) { + + // Scan folders first so we can build a list of files and have progress info later + + PoolStringArray &folders_to_scan = _folders_stack[_folders_stack.size() - 1]; + + if (folders_to_scan.size() != 0) { + // Scan one folder below + + String folder_name = folders_to_scan[folders_to_scan.size() - 1]; + pop_back(folders_to_scan); + + _current_dir = _current_dir.plus_file(folder_name); + + PoolStringArray sub_dirs; + _scan_dir(_root_prefix + _current_dir, sub_dirs); + + if (sub_dirs.size() != 0) { + _folders_stack.push_back(sub_dirs); + } + + } else { + // Go back one level + + pop_back(_folders_stack); + _current_dir = _current_dir.get_base_dir(); + + if (_folders_stack.size() == 0) { + // All folders scanned + _initial_files_count = _files_to_scan.size(); + } + } + + } else if (_files_to_scan.size() != 0) { + + // Then scan files + + String fpath = _files_to_scan[_files_to_scan.size() - 1]; + pop_back(_files_to_scan); + _scan_file(_root_prefix + fpath); + + } else { + print_line("Search complete"); + set_process(false); + _current_dir = ""; + _searching = false; + emit_signal(SIGNAL_FINISHED); + } +} + +float FindInFiles::get_progress() const { + if (_initial_files_count != 0) { + return static_cast<float>(_initial_files_count - _files_to_scan.size()) / static_cast<float>(_initial_files_count); + } + return 0; +} + +void FindInFiles::_scan_dir(String path, PoolStringArray &out_folders) { + + DirAccess *dir = DirAccess::open(path); + if (dir == NULL) { + print_line("Cannot open directory! " + path); + return; + } + + //print_line(String("Scanning ") + path); + + dir->list_dir_begin(); + + for (int i = 0; i < 1000; ++i) { + String file = dir->get_next(); + + if (file == "") + break; + + // Ignore special dirs and hidden dirs (such as .git and .import) + if (file == "." || file == ".." || file.begins_with(".")) + continue; + + if (dir->current_is_dir()) + out_folders.append(file); + + else { + String file_ext = file.get_extension(); + if (_extension_filter.has(file_ext)) { + _files_to_scan.push_back(file); + } + } + } +} + +void FindInFiles::_scan_file(String fpath) { + + FileAccess *f = FileAccess::open(fpath, FileAccess::READ); + if (f == NULL) { + f->close(); + print_line(String("Cannot open file ") + fpath); + return; + } + + int line_number = 0; + + while (!f->eof_reached()) { + + // line number starts at 1 + ++line_number; + + int begin = 0; + int end = 0; + + String line = f->get_line(); + + // Find all occurrences in the current line + while (true) { + begin = _match_case ? line.find(_pattern, end) : line.findn(_pattern, end); + + if (begin == -1) + break; + + end = begin + _pattern.length(); + + if (_whole_words) { + if (begin > 0 && is_text_char(line[begin - 1])) { + continue; + } + if (end < line.size() && is_text_char(line[end])) { + continue; + } + } + + emit_signal(SIGNAL_RESULT_FOUND, fpath, line_number, begin, end, line); + } + } + + f->close(); +} + +void FindInFiles::_bind_methods() { + + ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_FOUND, + PropertyInfo(Variant::STRING, "path"), + PropertyInfo(Variant::INT, "line_number"), + PropertyInfo(Variant::INT, "begin"), + PropertyInfo(Variant::INT, "end"), + PropertyInfo(Variant::STRING, "text"))); + + ADD_SIGNAL(MethodInfo(SIGNAL_FINISHED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesDialog::SIGNAL_FIND_REQUESTED = "find_requested"; +const char *FindInFilesDialog::SIGNAL_REPLACE_REQUESTED = "replace_requested"; + +FindInFilesDialog::FindInFilesDialog() { + + set_custom_minimum_size(Size2(400, 190)); + set_resizable(true); + set_title(TTR("Find in files")); + + VBoxContainer *vbc = memnew(VBoxContainer); + vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -8 * EDSCALE); + vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -8 * EDSCALE); + add_child(vbc); + + GridContainer *gc = memnew(GridContainer); + gc->set_columns(2); + vbc->add_child(gc); + + Label *find_label = memnew(Label); + find_label->set_text(TTR("Find: ")); + gc->add_child(find_label); + + _search_text_line_edit = memnew(LineEdit); + _search_text_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _search_text_line_edit->connect("text_changed", this, "_on_search_text_modified"); + _search_text_line_edit->connect("text_entered", this, "_on_search_text_entered"); + gc->add_child(_search_text_line_edit); + + { + Control *placeholder = memnew(Control); + gc->add_child(placeholder); + } + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + _whole_words_checkbox = memnew(CheckBox); + _whole_words_checkbox->set_text(TTR("Whole words")); + _whole_words_checkbox->set_pressed(true); + hbc->add_child(_whole_words_checkbox); + + _match_case_checkbox = memnew(CheckBox); + _match_case_checkbox->set_text(TTR("Match case")); + _match_case_checkbox->set_pressed(true); + hbc->add_child(_match_case_checkbox); + + gc->add_child(hbc); + } + + Label *folder_label = memnew(Label); + folder_label->set_text(TTR("Folder: ")); + gc->add_child(folder_label); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Label *prefix_label = memnew(Label); + prefix_label->set_text(ROOT_PREFIX); + hbc->add_child(prefix_label); + + _folder_line_edit = memnew(LineEdit); + _folder_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(_folder_line_edit); + + Button *folder_button = memnew(Button); + folder_button->set_text("..."); + folder_button->connect("pressed", this, "_on_folder_button_pressed"); + hbc->add_child(folder_button); + + _folder_dialog = memnew(FileDialog); + _folder_dialog->set_mode(FileDialog::MODE_OPEN_DIR); + _folder_dialog->connect("dir_selected", this, "_on_folder_selected"); + add_child(_folder_dialog); + + gc->add_child(hbc); + } + + Label *filter_label = memnew(Label); + filter_label->set_text(TTR("Filter: ")); + gc->add_child(filter_label); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Vector<String> exts; + exts.push_back("gd"); + exts.push_back("cs"); + + for (int i = 0; i < exts.size(); ++i) { + CheckBox *cb = memnew(CheckBox); + cb->set_text(exts[i]); + cb->set_pressed(true); + hbc->add_child(cb); + _filters.push_back(cb); + } + + gc->add_child(hbc); + } + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(0, EDSCALE * 16)); + vbc->add_child(placeholder); + } + + { + HBoxContainer *hbc = memnew(HBoxContainer); + hbc->set_alignment(HBoxContainer::ALIGN_CENTER); + + _find_button = memnew(Button); + _find_button->set_text(TTR("Find...")); + _find_button->connect("pressed", this, "_on_find_button_pressed"); + _find_button->set_disabled(true); + hbc->add_child(_find_button); + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); + hbc->add_child(placeholder); + } + + _replace_button = memnew(Button); + _replace_button->set_text(TTR("Replace...")); + _replace_button->connect("pressed", this, "_on_replace_button_pressed"); + _replace_button->set_disabled(true); + hbc->add_child(_replace_button); + + { + Control *placeholder = memnew(Control); + placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); + hbc->add_child(placeholder); + } + + Button *cancel_button = memnew(Button); + cancel_button->set_text(TTR("Cancel")); + cancel_button->connect("pressed", this, "hide"); + hbc->add_child(cancel_button); + + vbc->add_child(hbc); + } +} + +void FindInFilesDialog::set_search_text(String text) { + _search_text_line_edit->set_text(text); +} + +String FindInFilesDialog::get_search_text() const { + String text = _search_text_line_edit->get_text(); + return text.strip_edges(); +} + +bool FindInFilesDialog::is_match_case() const { + return _match_case_checkbox->is_pressed(); +} + +bool FindInFilesDialog::is_whole_words() const { + return _whole_words_checkbox->is_pressed(); +} + +String FindInFilesDialog::get_folder() const { + String text = _folder_line_edit->get_text(); + return text.strip_edges(); +} + +Set<String> FindInFilesDialog::get_filter() const { + Set<String> filters; + for (int i = 0; i < _filters.size(); ++i) { + CheckBox *cb = _filters[i]; + if (cb->is_pressed()) { + filters.insert(_filters[i]->get_text()); + } + } + return filters; +} + +void FindInFilesDialog::_notification(int p_what) { + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (is_visible()) { + // Doesn't work more than once if not deferred... + _search_text_line_edit->call_deferred("grab_focus"); + _search_text_line_edit->select_all(); + } + } +} + +void FindInFilesDialog::_on_folder_button_pressed() { + _folder_dialog->popup_centered_ratio(); +} + +void FindInFilesDialog::_on_find_button_pressed() { + emit_signal(SIGNAL_FIND_REQUESTED); + hide(); +} + +void FindInFilesDialog::_on_replace_button_pressed() { + emit_signal(SIGNAL_REPLACE_REQUESTED); + hide(); +} + +void FindInFilesDialog::_on_search_text_modified(String text) { + + ERR_FAIL_COND(!_find_button); + ERR_FAIL_COND(!_replace_button); + + _find_button->set_disabled(get_search_text().empty()); + _replace_button->set_disabled(get_search_text().empty()); +} + +void FindInFilesDialog::_on_search_text_entered(String text) { + // This allows to trigger a global search without leaving the keyboard + if (!_find_button->is_disabled()) + _on_find_button_pressed(); +} + +void FindInFilesDialog::_on_folder_selected(String path) { + int i = path.find("://"); + if (i != -1) + path = path.right(i + 3); + _folder_line_edit->set_text(path); +} + +void FindInFilesDialog::_bind_methods() { + + ClassDB::bind_method("_on_folder_button_pressed", &FindInFilesDialog::_on_folder_button_pressed); + ClassDB::bind_method("_on_find_button_pressed", &FindInFilesDialog::_on_find_button_pressed); + ClassDB::bind_method("_on_replace_button_pressed", &FindInFilesDialog::_on_replace_button_pressed); + ClassDB::bind_method("_on_folder_selected", &FindInFilesDialog::_on_folder_selected); + ClassDB::bind_method("_on_search_text_modified", &FindInFilesDialog::_on_search_text_modified); + ClassDB::bind_method("_on_search_text_entered", &FindInFilesDialog::_on_search_text_entered); + + ADD_SIGNAL(MethodInfo(SIGNAL_FIND_REQUESTED)); + ADD_SIGNAL(MethodInfo(SIGNAL_REPLACE_REQUESTED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesPanel::SIGNAL_RESULT_SELECTED = "result_selected"; +const char *FindInFilesPanel::SIGNAL_FILES_MODIFIED = "files_modified"; + +FindInFilesPanel::FindInFilesPanel() { + + _finder = memnew(FindInFiles); + _finder->connect(FindInFiles::SIGNAL_RESULT_FOUND, this, "_on_result_found"); + _finder->connect(FindInFiles::SIGNAL_FINISHED, this, "_on_finished"); + add_child(_finder); + + VBoxContainer *vbc = memnew(VBoxContainer); + vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 0); + vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 0); + vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0); + vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0); + add_child(vbc); + + { + HBoxContainer *hbc = memnew(HBoxContainer); + + Label *find_label = memnew(Label); + find_label->set_text(TTR("Find: ")); + hbc->add_child(find_label); + + _search_text_label = memnew(Label); + _search_text_label->add_font_override("font", get_font("source", "EditorFonts")); + hbc->add_child(_search_text_label); + + _progress_bar = memnew(ProgressBar); + _progress_bar->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(_progress_bar); + set_progress_visible(false); + + _status_label = memnew(Label); + hbc->add_child(_status_label); + + _cancel_button = memnew(Button); + _cancel_button->set_text(TTR("Cancel")); + _cancel_button->connect("pressed", this, "_on_cancel_button_clicked"); + _cancel_button->set_disabled(true); + hbc->add_child(_cancel_button); + + vbc->add_child(hbc); + } + + // In the future, this should be replaced by a more specific list container, + // which can highlight text regions and change opacity for enabled/disabled states + _results_display = memnew(ItemList); + _results_display->add_font_override("font", get_font("source", "EditorFonts")); + _results_display->set_v_size_flags(SIZE_EXPAND_FILL); + _results_display->connect("item_selected", this, "_on_result_selected"); + vbc->add_child(_results_display); + + { + _replace_container = memnew(HBoxContainer); + + Label *replace_label = memnew(Label); + replace_label->set_text(TTR("Replace: ")); + _replace_container->add_child(replace_label); + + _replace_line_edit = memnew(LineEdit); + _replace_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _replace_line_edit->connect("text_changed", this, "_on_replace_text_changed"); + _replace_container->add_child(_replace_line_edit); + + _replace_all_button = memnew(Button); + _replace_all_button->set_text(TTR("Replace all (no undo)")); + _replace_all_button->connect("pressed", this, "_on_replace_all_clicked"); + _replace_container->add_child(_replace_all_button); + + _replace_container->hide(); + + vbc->add_child(_replace_container); + } +} + +void FindInFilesPanel::set_with_replace(bool with_replace) { + + _replace_container->set_visible(with_replace); +} + +void FindInFilesPanel::start_search() { + + _results_display->clear(); + _status_label->set_text(TTR("Searching...")); + _search_text_label->set_text(_finder->get_search_text()); + + set_process(true); + set_progress_visible(true); + + _finder->start(); + + update_replace_buttons(); + _cancel_button->set_disabled(false); +} + +void FindInFilesPanel::stop_search() { + + _finder->stop(); + + _status_label->set_text(""); + update_replace_buttons(); + set_progress_visible(false); + _cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_notification(int p_what) { + if (p_what == NOTIFICATION_PROCESS) { + _progress_bar->set_as_ratio(_finder->get_progress()); + } +} + +void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin, int end, String text) { + + int i = _results_display->get_item_count(); + _results_display->add_item(fpath + ": " + String::num(line_number) + ": " + text.replace("\t", " ")); + _results_display->set_item_metadata(i, varray(fpath, line_number, begin, end)); +} + +void FindInFilesPanel::_on_finished() { + + _status_label->set_text(TTR("Search complete")); + update_replace_buttons(); + set_progress_visible(false); + _cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_on_cancel_button_clicked() { + stop_search(); +} + +void FindInFilesPanel::_on_result_selected(int i) { + + Array meta = _results_display->get_item_metadata(i); + emit_signal(SIGNAL_RESULT_SELECTED, meta[0], meta[1], meta[2], meta[3]); +} + +void FindInFilesPanel::_on_replace_text_changed(String text) { + update_replace_buttons(); +} + +void FindInFilesPanel::_on_replace_all_clicked() { + + String replace_text = get_replace_text(); + ERR_FAIL_COND(replace_text.empty()); + + String last_fpath; + PoolIntArray locations; + PoolStringArray modified_files; + + for (int i = 0; i < _results_display->get_item_count(); ++i) { + + Array meta = _results_display->get_item_metadata(i); + + String fpath = meta[0]; + + // Results are sorted by file, so we can batch replaces + if (fpath != last_fpath) { + if (locations.size() != 0) { + apply_replaces_in_file(last_fpath, locations, replace_text); + modified_files.append(last_fpath); + locations.resize(0); + } + } + + locations.append(meta[1]); // line_number + locations.append(meta[2]); // begin + locations.append(meta[3]); // end + + last_fpath = fpath; + } + + if (locations.size() != 0) { + apply_replaces_in_file(last_fpath, locations, replace_text); + modified_files.append(last_fpath); + } + + // Hide replace bar so we can't trigger the action twice without doing a new search + set_with_replace(false); + + emit_signal(SIGNAL_FILES_MODIFIED, modified_files); +} + +// Same as get_line, but preserves line ending characters +class ConservativeGetLine { +public: + String get_line(FileAccess *f) { + + _line_buffer.clear(); + + CharType c = f->get_8(); + + while (!f->eof_reached()) { + + if (c == '\n') { + _line_buffer.push_back(c); + _line_buffer.push_back(0); + return String::utf8(_line_buffer.ptr()); + + } else if (c == '\0') { + _line_buffer.push_back(c); + return String::utf8(_line_buffer.ptr()); + + } else if (c != '\r') { + _line_buffer.push_back(c); + } + + c = f->get_8(); + } + + _line_buffer.push_back(0); + return String::utf8(_line_buffer.ptr()); + } + +private: + Vector<char> _line_buffer; +}; + +void FindInFilesPanel::apply_replaces_in_file(String fpath, PoolIntArray locations, String text) { + + ERR_FAIL_COND(locations.size() % 3 != 0); + + //print_line(String("Replacing {0} occurrences in {1}").format(varray(fpath, locations.size() / 3))); + + // If the file is already open, I assume the editor will reload it. + // If there are unsaved changes, the user will be asked on focus, + // however that means either loosing changes or loosing replaces. + + FileAccess *f = FileAccess::open(fpath, FileAccess::READ); + ERR_FAIL_COND(f == NULL); + + String buffer; + int current_line = 1; + + ConservativeGetLine conservative; + + String line = conservative.get_line(f); + + PoolIntArray::Read locations_read = locations.read(); + for (int i = 0; i < locations.size(); i += 3) { + + int repl_line_number = locations_read[i]; + int repl_begin = locations_read[i + 1]; + int repl_end = locations_read[i + 2]; + + while (current_line < repl_line_number) { + buffer += line; + line = conservative.get_line(f); + ++current_line; + } + + line = line.left(repl_begin) + text + line.right(repl_end); + } + + buffer += line; + + while (!f->eof_reached()) { + buffer += conservative.get_line(f); + } + + // Now the modified contents are in the buffer, rewrite the file with our changes + + Error err = f->reopen(fpath, FileAccess::WRITE); + ERR_FAIL_COND(err != OK); + + f->store_string(buffer); + + f->close(); +} + +String FindInFilesPanel::get_replace_text() { + return _replace_line_edit->get_text().strip_edges(); +} + +void FindInFilesPanel::update_replace_buttons() { + + String text = get_replace_text(); + bool disabled = text.empty() || _finder->is_searching(); + + _replace_all_button->set_disabled(disabled); +} + +void FindInFilesPanel::set_progress_visible(bool visible) { + _progress_bar->set_self_modulate(Color(1, 1, 1, visible ? 1 : 0)); +} + +void FindInFilesPanel::_bind_methods() { + + ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found); + ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished); + ClassDB::bind_method("_on_cancel_button_clicked", &FindInFilesPanel::_on_cancel_button_clicked); + ClassDB::bind_method("_on_result_selected", &FindInFilesPanel::_on_result_selected); + ClassDB::bind_method("_on_replace_text_changed", &FindInFilesPanel::_on_replace_text_changed); + ClassDB::bind_method("_on_replace_all_clicked", &FindInFilesPanel::_on_replace_all_clicked); + + ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED, + PropertyInfo(Variant::STRING, "path"), + PropertyInfo(Variant::INT, "line_number"), + PropertyInfo(Variant::INT, "begin"), + PropertyInfo(Variant::INT, "end"))); + + ADD_SIGNAL(MethodInfo(SIGNAL_FILES_MODIFIED, PropertyInfo(Variant::STRING, "paths"))); +} diff --git a/editor/find_in_files.h b/editor/find_in_files.h new file mode 100644 index 0000000000..d57184960b --- /dev/null +++ b/editor/find_in_files.h @@ -0,0 +1,184 @@ +/*************************************************************************/ +/* find_in_files.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#ifndef FIND_IN_FILES_H +#define FIND_IN_FILES_H + +#include "scene/gui/dialogs.h" + +// Performs the actual search +class FindInFiles : public Node { + GDCLASS(FindInFiles, Node) +public: + static const char *SIGNAL_RESULT_FOUND; + static const char *SIGNAL_FINISHED; + + FindInFiles(); + + void set_search_text(String p_pattern); + void set_whole_words(bool p_whole_word); + void set_match_case(bool p_match_case); + void set_folder(String folder); + void set_filter(const Set<String> &exts); + + String get_search_text() const { return _pattern; } + + bool is_whole_words() const { return _whole_words; } + bool is_match_case() const { return _match_case; } + + void start(); + void stop(); + + bool is_searching() const { return _searching; } + float get_progress() const; + +protected: + void _notification(int p_notification); + + static void _bind_methods(); + +private: + void _process(); + void _iterate(); + void _scan_dir(String path, PoolStringArray &out_folders); + void _scan_file(String fpath); + + // Config + String _pattern; + Set<String> _extension_filter; + String _root_prefix; + String _root_dir; + bool _whole_words; + bool _match_case; + + // State + bool _searching; + String _current_dir; + Vector<PoolStringArray> _folders_stack; + Vector<String> _files_to_scan; + int _initial_files_count; +}; + +class LineEdit; +class CheckBox; +class FileDialog; + +// Prompts search parameters +class FindInFilesDialog : public WindowDialog { + GDCLASS(FindInFilesDialog, WindowDialog) +public: + static const char *SIGNAL_FIND_REQUESTED; + static const char *SIGNAL_REPLACE_REQUESTED; + + FindInFilesDialog(); + + void set_search_text(String text); + + String get_search_text() const; + bool is_match_case() const; + bool is_whole_words() const; + String get_folder() const; + Set<String> get_filter() const; + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +private: + void _on_folder_button_pressed(); + void _on_find_button_pressed(); + void _on_replace_button_pressed(); + void _on_folder_selected(String path); + void _on_search_text_modified(String text); + void _on_search_text_entered(String text); + + LineEdit *_search_text_line_edit; + LineEdit *_folder_line_edit; + Vector<CheckBox *> _filters; + CheckBox *_match_case_checkbox; + CheckBox *_whole_words_checkbox; + Button *_find_button; + Button *_replace_button; + FileDialog *_folder_dialog; +}; + +class Button; +class ItemList; +class ProgressBar; + +// Display search results +class FindInFilesPanel : public Control { + GDCLASS(FindInFilesPanel, Control) +public: + static const char *SIGNAL_RESULT_SELECTED; + static const char *SIGNAL_FILES_MODIFIED; + + FindInFilesPanel(); + + FindInFiles *get_finder() const { return _finder; } + + void set_with_replace(bool with_replace); + + void start_search(); + void stop_search(); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + +private: + void _on_result_found(String fpath, int line_number, int begin, int end, String text); + void _on_finished(); + void _on_cancel_button_clicked(); + void _on_result_selected(int i); + void _on_replace_text_changed(String text); + void _on_replace_all_clicked(); + + void apply_replaces_in_file(String fpath, PoolIntArray locations, String text); + + void update_replace_buttons(); + String get_replace_text(); + void set_progress_visible(bool visible); + + FindInFiles *_finder; + Label *_search_text_label; + ItemList *_results_display; + Label *_status_label; + Button *_cancel_button; + ProgressBar *_progress_bar; + + HBoxContainer *_replace_container; + LineEdit *_replace_line_edit; + Button *_replace_all_button; +}; + +#endif // FIND_IN_FILES_H diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 03155b3a48..debdeb1c4a 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -304,17 +304,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s int limit_rate_hz = p_options["force/max_rate_hz"]; if (limit_rate && rate > limit_rate_hz && rate > 0 && frames > 0) { //resampleeee!!! - int new_data_frames = frames * limit_rate_hz / rate; + int new_data_frames = (int)(frames * (float)limit_rate_hz / (float)rate); + + print_line("\tresampling ratio: " + rtos((float)limit_rate_hz / (float)rate)); + print_line("\tnew frames: " + itos(new_data_frames)); + Vector<float> new_data; new_data.resize(new_data_frames * format_channels); for (int c = 0; c < format_channels; c++) { + float frac = .0f; + int ipos = 0; + for (int i = 0; i < new_data_frames; i++) { //simple cubic interpolation should be enough. - float pos = float(i) * frames / new_data_frames; - float mu = pos - Math::floor(pos); - int ipos = int(Math::floor(pos)); + + float mu = frac; float y0 = data[MAX(0, ipos - 1) * format_channels + c]; float y1 = data[ipos * format_channels + c]; @@ -330,14 +336,22 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s float res = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3); new_data[i * format_channels + c] = res; + + // update position and always keep fractional part within ]0...1] + // in order to avoid 32bit floating point precision errors + + frac += (float)rate / (float)limit_rate_hz; + int tpos = (int)Math::floor(frac); + ipos += tpos; + frac -= tpos; } } if (loop) { - - loop_begin = loop_begin * new_data_frames / frames; - loop_end = loop_end * new_data_frames / frames; + loop_begin = (int)(loop_begin * (float)new_data_frames / (float)frames); + loop_end = (int)(loop_end * (float)new_data_frames / (float)frames); } + data = new_data; rate = limit_rate_hz; frames = new_data_frames; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 2ce36ee8d5..9193b3fbbf 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -39,9 +39,11 @@ #include "core/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/find_in_files.h" #include "editor/node_dock.h" #include "editor/script_editor_debugger.h" #include "scene/main/viewport.h" +#include "script_text_editor.h" /*** SCRIPT EDITOR ****/ @@ -54,6 +56,8 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("request_save_history")); ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what"))); + // TODO This signal is no use for VisualScript... + ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); } static bool _can_open_in_editor(Script *p_script) { @@ -298,15 +302,9 @@ void ScriptEditor::_script_created(Ref<Script> p_script) { void ScriptEditor::_goto_script_line2(int p_line) { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) - return; - - current->goto_line(p_line); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->goto_line(p_line); } void ScriptEditor::_goto_script_line(REF p_script, int p_line) { @@ -316,19 +314,22 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) { if (edit(p_script, p_line, 0)) { editor->push_item(p_script.ptr()); - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) - return; - - current->goto_line(p_line, true); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->goto_line(p_line, true); } } } +ScriptEditorBase *ScriptEditor::_get_current_editor() const { + + int selected = tab_container->get_current_tab(); + if (selected < 0 || selected >= tab_container->get_child_count()) + return NULL; + + return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); +} + void ScriptEditor::_update_history_arrows() { script_back->set_disabled(history_pos <= 0); @@ -587,7 +588,7 @@ void ScriptEditor::_close_docs_tab() { } void ScriptEditor::_copy_script_path() { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); + ScriptEditorBase *se = _get_current_editor(); Ref<Script> script = se->get_edited_script(); OS::get_singleton()->set_clipboard(script->get_path()); } @@ -819,11 +820,8 @@ void ScriptEditor::_file_dialog_action(String p_file) { Ref<Script> ScriptEditor::_get_current_script() { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return NULL; + ScriptEditorBase *current = _get_current_editor(); - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); if (current) { return current->get_edited_script(); } else { @@ -939,11 +937,7 @@ void ScriptEditor::_menu_option(int p_option) { } } - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + ScriptEditorBase *current = _get_current_editor(); if (current) { switch (p_option) { @@ -1033,7 +1027,7 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); + ScriptEditorBase *se = _get_current_editor(); Ref<Script> script = se->get_edited_script(); FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); file_system_dock->navigate_to_path(script->get_path()); @@ -1223,6 +1217,17 @@ void ScriptEditor::_notification(int p_what) { recent_scripts->set_as_minsize(); } break; + case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { + + if (is_visible()) { + find_in_files_button->show(); + } else { + find_in_files->hide(); + find_in_files_button->hide(); + } + + } break; + default: break; } @@ -1230,15 +1235,11 @@ void ScriptEditor::_notification(int p_what) { bool ScriptEditor::can_take_away_focus() const { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return true; - - ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); - if (!current) + ScriptEditorBase *current = _get_current_editor(); + if (current) + return current->can_lose_focus_on_node_selection(); + else return true; - - return current->can_lose_focus_on_node_selection(); } void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { @@ -1315,20 +1316,13 @@ void ScriptEditor::ensure_focus_current() { if (!is_inside_tree()) return; - int cidx = tab_container->get_current_tab(); - if (cidx < 0 || cidx >= tab_container->get_tab_count()) - return; - - Control *c = Object::cast_to<Control>(tab_container->get_child(cidx)); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(c); - if (!se) - return; - se->ensure_focus(); + ScriptEditorBase *current = _get_current_editor(); + if (current) + current->ensure_focus(); } void ScriptEditor::_members_overview_selected(int p_idx) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { return; } @@ -1362,18 +1356,12 @@ void ScriptEditor::ensure_select_current() { if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); - - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (se) { - Ref<Script> script = se->get_edited_script(); - if (!grab_focus_block && is_visible_in_tree()) se->ensure_focus(); } - - EditorHelp *eh = Object::cast_to<EditorHelp>(current); } _update_selected_editor_menu(); @@ -1413,12 +1401,7 @@ struct _ScriptEditorItemData { void ScriptEditor::_update_members_overview_visibility() { - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { members_overview->set_visible(false); return; @@ -1434,12 +1417,7 @@ void ScriptEditor::_update_members_overview_visibility() { void ScriptEditor::_update_members_overview() { members_overview->clear(); - int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) - return; - - Node *current = tab_container->get_child(tab_container->get_current_tab()); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); + ScriptEditorBase *se = _get_current_editor(); if (!se) { return; } @@ -1813,6 +1791,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool se->connect("request_open_script_at_line", this, "_goto_script_line"); se->connect("go_to_help", this, "_help_class_goto"); se->connect("request_save_history", this, "_save_history"); + se->connect("search_in_files_requested", this, "_on_find_in_files_requested"); //test for modification, maybe the script was not edited but was loaded @@ -2530,6 +2509,48 @@ void ScriptEditor::_script_changed() { NodeDock::singleton->update_lists(); } +void ScriptEditor::_on_find_in_files_requested(String text) { + + find_in_files_dialog->set_search_text(text); + find_in_files_dialog->popup_centered_minsize(); +} + +void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) { + + Ref<Resource> res = ResourceLoader::load(fpath); + edit(res); + + ScriptEditorBase *seb = _get_current_editor(); + + ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(seb); + if (ste) { + ste->goto_line_selection(line_number - 1, begin, end); + } +} + +void ScriptEditor::_start_find_in_files(bool with_replace) { + + FindInFiles *f = find_in_files->get_finder(); + + f->set_search_text(find_in_files_dialog->get_search_text()); + f->set_match_case(find_in_files_dialog->is_match_case()); + f->set_whole_words(find_in_files_dialog->is_match_case()); + f->set_folder(find_in_files_dialog->get_folder()); + f->set_filter(find_in_files_dialog->get_filter()); + + find_in_files->set_with_replace(with_replace); + find_in_files->start_search(); + + find_in_files_button->set_pressed(true); + find_in_files->show(); +} + +void ScriptEditor::_on_find_in_files_modified_files(PoolStringArray paths) { + + _test_script_times_on_disk(); + _update_modified_scripts_for_external_editor(); +} + void ScriptEditor::_bind_methods() { ClassDB::bind_method("_file_dialog_action", &ScriptEditor::_file_dialog_action); @@ -2577,6 +2598,10 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_script_list_gui_input", &ScriptEditor::_script_list_gui_input); ClassDB::bind_method("_script_changed", &ScriptEditor::_script_changed); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); + ClassDB::bind_method("_on_find_in_files_requested", &ScriptEditor::_on_find_in_files_requested); + ClassDB::bind_method("_start_find_in_files", &ScriptEditor::_start_find_in_files); + ClassDB::bind_method("_on_find_in_files_result_selected", &ScriptEditor::_on_find_in_files_result_selected); + ClassDB::bind_method("_on_find_in_files_modified_files", &ScriptEditor::_on_find_in_files_modified_files); ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw); ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); @@ -2838,6 +2863,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { add_child(help_index); help_index->connect("open_class", this, "_help_class_open"); + find_in_files_dialog = memnew(FindInFilesDialog); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, this, "_start_find_in_files", varray(false)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, this, "_start_find_in_files", varray(true)); + add_child(find_in_files_dialog); + find_in_files = memnew(FindInFilesPanel); + find_in_files_button = editor->add_bottom_panel_item(TTR("Search results"), find_in_files); + find_in_files_button->set_tooltip(TTR("Search in files")); + find_in_files->set_custom_minimum_size(Size2(0, 200)); + find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, this, "_on_find_in_files_result_selected"); + find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, this, "_on_find_in_files_modified_files"); + find_in_files->hide(); + find_in_files_button->hide(); + history_pos = -1; //debugger_gui->hide(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index f947351089..9f37b18d7d 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -119,6 +119,8 @@ typedef SyntaxHighlighter *(*CreateSyntaxHighlighterFunc)(); typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Script> &p_script); class EditorScriptCodeCompletionCache; +class FindInFilesDialog; +class FindInFilesPanel; class ScriptEditor : public PanelContainer { @@ -217,6 +219,10 @@ class ScriptEditor : public PanelContainer { ToolButton *script_back; ToolButton *script_forward; + FindInFilesDialog *find_in_files_dialog; + FindInFilesPanel *find_in_files; + Button *find_in_files_button; + enum { SCRIPT_EDITOR_FUNC_MAX = 32, SYNTAX_HIGHLIGHTER_FUNC_MAX = 32 @@ -304,6 +310,8 @@ class ScriptEditor : public PanelContainer { void _update_window_menu(); void _script_created(Ref<Script> p_script); + ScriptEditorBase *_get_current_editor() const; + void _save_layout(); void _editor_settings_changed(); void _autosave_scripts(); @@ -359,6 +367,11 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; + void _on_find_in_files_requested(String text); + void _on_find_in_files_result_selected(String fpath, int line_number, int begin, int end); + void _start_find_in_files(bool with_replace); + void _on_find_in_files_modified_files(PoolStringArray paths); + static void _open_script_request(const String &p_path); static ScriptEditor *script_editor; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 711a313902..bcc575a7ac 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -529,6 +529,14 @@ void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { tx->call_deferred("cursor_set_line", p_line); } +void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + TextEdit *tx = code_editor->get_text_edit(); + tx->unfold_line(p_line); + tx->call_deferred("cursor_set_line", p_line); + tx->call_deferred("cursor_set_column", p_begin); + tx->select(p_line, p_begin, p_line, p_end); +} + void ScriptTextEditor::ensure_focus() { code_editor->get_text_edit()->grab_focus(); @@ -1173,6 +1181,15 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->get_find_replace_bar()->popup_replace(); } break; + case SEARCH_IN_FILES: { + + String selected_text = code_editor->get_text_edit()->get_selection_text(); + + // Yep, because it doesn't make sense to instance this dialog for every single script open... + // So this will be delegated to the ScriptEditor + emit_signal("search_in_files_requested", selected_text); + + } break; case SEARCH_LOCATE_FUNCTION: { quick_open->popup(get_functions()); @@ -1660,6 +1677,8 @@ ScriptTextEditor::ScriptTextEditor() { search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); + search_menu->get_popup()->add_separator(); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); search_menu->get_popup()->add_separator(); @@ -1739,7 +1758,9 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3); ED_SHORTCUT("script_text_editor/replace", TTR("Replace.."), KEY_MASK_CMD | KEY_R); - ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_F); + ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F); + + ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F); ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line.."), KEY_MASK_CMD | KEY_L); ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index eb52d2593a..a93e1a6fa8 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -106,6 +106,7 @@ class ScriptTextEditor : public ScriptEditorBase { SEARCH_REPLACE, SEARCH_LOCATE_FUNCTION, SEARCH_GOTO_LINE, + SEARCH_IN_FILES, DEBUG_TOGGLE_BREAKPOINT, DEBUG_REMOVE_ALL_BREAKPOINTS, DEBUG_GOTO_NEXT_BREAKPOINT, @@ -170,6 +171,7 @@ public: virtual void tag_saved_version(); virtual void goto_line(int p_line, bool p_with_error = false); + void goto_line_selection(int p_line, int p_begin, int p_end); virtual void reload(bool p_soft); virtual void get_breakpoints(List<int> *p_breakpoints); diff --git a/main/input_default.cpp b/main/input_default.cpp index c3bc83b2de..1c73ecf2d2 100644 --- a/main/input_default.cpp +++ b/main/input_default.cpp @@ -413,10 +413,6 @@ void InputDefault::set_mouse_position(const Point2 &p_posf) { mouse_speed_track.update(p_posf - mouse_pos); mouse_pos = p_posf; - if (custom_cursor.is_valid()) { - //removed, please insist that we implement hardware cursors - // VisualServer::get_singleton()->cursor_set_pos(get_mouse_position()); - } } Point2 InputDefault::get_mouse_position() const { @@ -499,15 +495,19 @@ bool InputDefault::is_emulating_touchscreen() const { return emulate_touch; } +Input::CursorShape InputDefault::get_default_cursor_shape() { + return default_shape; +} + +void InputDefault::set_default_cursor_shape(CursorShape p_shape) { + default_shape = p_shape; + OS::get_singleton()->set_cursor_shape((OS::CursorShape)p_shape); +} + void InputDefault::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (Engine::get_singleton()->is_editor_hint()) return; - if (custom_cursor == p_cursor) - return; - - custom_cursor = p_cursor; - OS::get_singleton()->set_custom_mouse_cursor(p_cursor, (OS::CursorShape)p_shape, p_hotspot); } diff --git a/main/input_default.h b/main/input_default.h index 0479fdc0ff..384b04cf41 100644 --- a/main/input_default.h +++ b/main/input_default.h @@ -115,7 +115,7 @@ class InputDefault : public Input { SpeedTrack mouse_speed_track; Map<int, Joypad> joy_names; int fallback_mapping; - RES custom_cursor; + CursorShape default_shape = CURSOR_ARROW; public: enum HatMask { @@ -226,6 +226,8 @@ public: void set_emulate_touch(bool p_emulate); virtual bool is_emulating_touchscreen() const; + virtual CursorShape get_default_cursor_shape(); + virtual void set_default_cursor_shape(CursorShape p_shape); virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape = Input::CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()); virtual void set_mouse_in_window(bool p_in_window); diff --git a/modules/bullet/area_bullet.cpp b/modules/bullet/area_bullet.cpp index ec78cddb6a..bfb452d109 100644 --- a/modules/bullet/area_bullet.cpp +++ b/modules/bullet/area_bullet.cpp @@ -68,8 +68,9 @@ AreaBullet::AreaBullet() : } AreaBullet::~AreaBullet() { - // Call "remove_all_overlapping_instantly();" is not necessary because the exit - // signal are handled by godot, so just clear the array + // signal are handled by godot, so just clear without notify + for (int i = overlappingObjects.size() - 1; 0 <= i; --i) + overlappingObjects[i].object->on_exit_area(this); } void AreaBullet::dispatch_callbacks() { @@ -122,24 +123,21 @@ void AreaBullet::scratch() { isScratched = true; } -void AreaBullet::remove_all_overlapping_instantly() { - CollisionObjectBullet *supportObject; +void AreaBullet::clear_overlaps(bool p_notify) { for (int i = overlappingObjects.size() - 1; 0 <= i; --i) { - supportObject = overlappingObjects[i].object; - call_event(supportObject, PhysicsServer::AREA_BODY_REMOVED); - supportObject->on_exit_area(this); + if (p_notify) + call_event(overlappingObjects[i].object, PhysicsServer::AREA_BODY_REMOVED); + overlappingObjects[i].object->on_exit_area(this); } overlappingObjects.clear(); } -void AreaBullet::remove_overlapping_instantly(CollisionObjectBullet *p_object, bool p_notify) { - CollisionObjectBullet *supportObject; +void AreaBullet::remove_overlap(CollisionObjectBullet *p_object, bool p_notify) { for (int i = overlappingObjects.size() - 1; 0 <= i; --i) { - supportObject = overlappingObjects[i].object; - if (supportObject == p_object) { + if (overlappingObjects[i].object == p_object) { if (p_notify) - call_event(supportObject, PhysicsServer::AREA_BODY_REMOVED); - supportObject->on_exit_area(this); + call_event(overlappingObjects[i].object, PhysicsServer::AREA_BODY_REMOVED); + overlappingObjects[i].object->on_exit_area(this); overlappingObjects.remove(i); break; } diff --git a/modules/bullet/area_bullet.h b/modules/bullet/area_bullet.h index 4104780de9..b2046c684e 100644 --- a/modules/bullet/area_bullet.h +++ b/modules/bullet/area_bullet.h @@ -150,9 +150,9 @@ public: void set_on_state_change(ObjectID p_id, const StringName &p_method, const Variant &p_udata = Variant()); void scratch(); - void remove_all_overlapping_instantly(); + void clear_overlaps(bool p_notify); // Dispatch the callbacks and removes from overlapping list - void remove_overlapping_instantly(CollisionObjectBullet *p_object, bool p_notify); + void remove_overlap(CollisionObjectBullet *p_object, bool p_notify); virtual void on_collision_filters_change(); virtual void on_collision_checker_start() {} diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp index 77f8df34cb..05c0e653df 100644 --- a/modules/bullet/collision_object_bullet.cpp +++ b/modules/bullet/collision_object_bullet.cpp @@ -70,7 +70,7 @@ CollisionObjectBullet::CollisionObjectBullet(Type p_type) : CollisionObjectBullet::~CollisionObjectBullet() { // Remove all overlapping, notify is not required since godot take care of it for (int i = areasOverlapped.size() - 1; 0 <= i; --i) { - areasOverlapped[i]->remove_overlapping_instantly(this, /*Notify*/ false); + areasOverlapped[i]->remove_overlap(this, /*Notify*/ false); } destroyBulletCollisionObject(); diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml index 308d8defc3..754a6d2514 100644 --- a/modules/gdnative/doc_classes/GDNativeLibrary.xml +++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml @@ -9,12 +9,6 @@ <demos> </demos> <methods> - <method name="get_config_file"> - <return type="ConfigFile"> - </return> - <description> - </description> - </method> <method name="get_current_dependencies" qualifiers="const"> <return type="PoolStringArray"> </return> @@ -29,6 +23,8 @@ </method> </methods> <members> + <member name="config_file" type="ConfigFile" setter="set_config_file" getter="get_config_file"> + </member> <member name="load_once" type="bool" setter="set_load_once" getter="should_load_once"> </member> <member name="reloadable" type="bool" setter="set_reloadable" getter="is_reloadable"> diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 87d8fe1bf5..0d52f0a995 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1333,13 +1333,23 @@ static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p for (int i = 0; i < context.block->statements.size(); i++) { - if (context.block->statements[i]->line > p_line) + GDScriptParser::Node *statement = context.block->statements[i]; + if (statement->line > p_line) continue; - if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { + GDScriptParser::BlockNode::Type statementType = statement->type; + if (statementType == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]); + const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(statement); result.insert(lv->name.operator String()); + } else if (statementType == GDScriptParser::BlockNode::TYPE_CONTROL_FLOW) { + + const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(statement); + if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_FOR) { + + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0]); + result.insert(id->name.operator String()); + } } } } diff --git a/modules/gdscript/gdscript_highlighter.cpp b/modules/gdscript/gdscript_highlighter.cpp index 5b8b652c29..4e89851bf2 100644 --- a/modules/gdscript/gdscript_highlighter.cpp +++ b/modules/gdscript/gdscript_highlighter.cpp @@ -71,24 +71,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ Color keyword_color; Color color; - int in_region = -1; + int in_region = text_editor->_is_line_in_region(p_line); int deregion = 0; - for (int i = 0; i < p_line; i++) { - int ending_color_region = text_editor->_get_line_ending_color_region(i); - if (in_region == -1) { - in_region = ending_color_region; - } else if (in_region == ending_color_region) { - in_region = -1; - } else { - const Map<int, TextEdit::Text::ColorRegionInfo> &cri_map = text_editor->_get_line_color_region_info(i); - for (const Map<int, TextEdit::Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { - const TextEdit::Text::ColorRegionInfo &cri = E->get(); - if (cri.region == in_region) { - in_region = -1; - } - } - } - } const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text_editor->_get_line_color_region_info(p_line); const String &str = text_editor->get_line(p_line); diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index f1cf0bcdf5..1b5a303835 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -407,9 +407,14 @@ void MonoBuildTab::stop_build() { void MonoBuildTab::_issue_activated(int p_idx) { - ERR_FAIL_INDEX(p_idx, issues.size()); + ERR_FAIL_INDEX(p_idx, issues_list->get_item_count()); - const BuildIssue &issue = issues[p_idx]; + // Get correct issue idx from issue list + int issue_idx = this->issues_list->get_item_metadata(p_idx); + + ERR_FAIL_INDEX(issue_idx, issues.size()); + + const BuildIssue &issue = issues[issue_idx]; if (issue.project_file.empty() && issue.file.empty()) return; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 0c5524dd08..fbefd41bb7 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1474,6 +1474,11 @@ void OS_OSX::set_cursor_shape(CursorShape p_shape) { if (cursor_shape == p_shape) return; + if (mouse_mode != MOUSE_MODE_VISIBLE) { + cursor_shape = p_shape; + return; + } + if (cursors[p_shape] != NULL) { [cursors[p_shape] set]; } else { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index f51d969c16..9c37b65d77 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1299,7 +1299,9 @@ void OS_Windows::set_mouse_mode(MouseMode p_mode) { if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) { hCursor = SetCursor(NULL); } else { - SetCursor(hCursor); + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + set_cursor_shape(c); } } @@ -1863,6 +1865,11 @@ void OS_Windows::set_cursor_shape(CursorShape p_shape) { if (cursor_shape == p_shape) return; + if (mouse_mode != MOUSE_MODE_VISIBLE) { + cursor_shape = p_shape; + return; + } + static const LPCTSTR win_cursors[CURSOR_MAX] = { IDC_ARROW, IDC_IBEAM, @@ -1888,6 +1895,7 @@ void OS_Windows::set_cursor_shape(CursorShape p_shape) { } else { SetCursor(LoadCursor(hInstance, win_cursors[p_shape])); } + cursor_shape = p_shape; } diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 338d13410f..1928800d8c 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -634,7 +634,7 @@ void OS_X11::set_mouse_mode(MouseMode p_mode) { bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED); if (showCursor) { - XUndefineCursor(x11_display, x11_window); // show cursor + XDefineCursor(x11_display, x11_window, cursors[current_cursor]); // show cursor } else { XDefineCursor(x11_display, x11_window, null_cursor); // hide cursor } diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index ff4a807de0..9aac391d80 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -781,7 +781,7 @@ String RigidBody::get_configuration_warning() const { String warning = CollisionObject::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(0).length() - 1.0) > 0.05)) { + 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()) { warning += "\n"; } diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 232855c978..bc44c91f64 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -366,11 +366,17 @@ void Sprite3D::_draw() { final_rect.position * pixel_size, }; + + // Properly setup UVs for impostor textures (AtlasTexture). + RID texture_rid = texture->get_rid(); + Vector2 src_tsize = Vector2( + VS::get_singleton()->texture_get_width(texture_rid), + VS::get_singleton()->texture_get_height(texture_rid)); Vector2 uvs[4] = { - final_src_rect.position / tsize, - (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, - (final_src_rect.position + final_src_rect.size) / tsize, - (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / tsize, + final_src_rect.position / src_tsize, + (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, + (final_src_rect.position + final_src_rect.size) / src_tsize, + (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize, }; if (is_flipped_h()) { @@ -649,18 +655,23 @@ void AnimatedSprite3D::_draw() { float pixel_size = get_pixel_size(); Vector2 vertices[4] = { - (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size, (final_rect.position + final_rect.size) * pixel_size, (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, final_rect.position * pixel_size, }; + + // Properly setup UVs for impostor textures (AtlasTexture). + RID texture_rid = texture->get_rid(); + Vector2 src_tsize = Vector2( + VS::get_singleton()->texture_get_width(texture_rid), + VS::get_singleton()->texture_get_height(texture_rid)); Vector2 uvs[4] = { - final_src_rect.position / tsize, - (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, - (final_src_rect.position + final_src_rect.size) / tsize, - (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / tsize, + final_src_rect.position / src_tsize, + (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, + (final_src_rect.position + final_src_rect.size) / src_tsize, + (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize, }; if (is_flipped_h()) { diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 562dd155f9..dbfb96697d 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -252,7 +252,7 @@ void BaseButton::_notification(int p_what) { status.hovering = false; update(); } - if (p_what == NOTIFICATION_DRAG_BEGIN) { + if (p_what == NOTIFICATION_DRAG_BEGIN || p_what == NOTIFICATION_SCROLL_BEGIN) { if (status.press_attempt) { status.press_attempt = false; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index a5883863cd..6ca6d82807 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1281,22 +1281,24 @@ void Control::_size_changed() { Size2 minimum_size = get_combined_minimum_size(); - if (data.h_grow == GROW_DIRECTION_BEGIN) { - if (minimum_size.width > new_size_cache.width) { - new_pos_cache.x = new_pos_cache.x + new_size_cache.width - minimum_size.width; - new_size_cache.width = minimum_size.width; + if (minimum_size.width > new_size_cache.width) { + if (data.h_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.x += new_size_cache.width - minimum_size.width; + } else if (data.h_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width); } - } else { - new_size_cache.width = MAX(minimum_size.width, new_size_cache.width); + + new_size_cache.width = minimum_size.width; } - if (data.v_grow == GROW_DIRECTION_BEGIN) { - if (minimum_size.height > new_size_cache.height) { - new_pos_cache.y = new_pos_cache.y + new_size_cache.height - minimum_size.height; - new_size_cache.height = minimum_size.height; + if (minimum_size.height > new_size_cache.height) { + if (data.v_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.y += new_size_cache.height - minimum_size.height; + } else if (data.v_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height); } - } else { - new_size_cache.height = MAX(minimum_size.height, new_size_cache.height); + + new_size_cache.height = minimum_size.height; } // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() @@ -2838,8 +2840,8 @@ void Control::_bind_methods() { ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "margin_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_BOTTOM); ADD_GROUP("Grow Direction", "grow_"); - ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End"), "set_h_grow_direction", "get_h_grow_direction"); - ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End"), "set_v_grow_direction", "get_v_grow_direction"); + ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction"); + ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); ADD_GROUP("Rect", "rect_"); ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); @@ -2886,6 +2888,8 @@ void Control::_bind_methods() { BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); BIND_CONSTANT(NOTIFICATION_MODAL_CLOSE); + BIND_CONSTANT(NOTIFICATION_SCROLL_BEGIN); + BIND_CONSTANT(NOTIFICATION_SCROLL_END); BIND_ENUM_CONSTANT(CURSOR_ARROW); BIND_ENUM_CONSTANT(CURSOR_IBEAM); @@ -2939,6 +2943,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(GROW_DIRECTION_BEGIN); BIND_ENUM_CONSTANT(GROW_DIRECTION_END); + BIND_ENUM_CONSTANT(GROW_DIRECTION_BOTH); BIND_ENUM_CONSTANT(ANCHOR_BEGIN); BIND_ENUM_CONSTANT(ANCHOR_END); diff --git a/scene/gui/control.h b/scene/gui/control.h index 51325f27b5..a215490295 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -60,7 +60,8 @@ public: enum GrowDirection { GROW_DIRECTION_BEGIN, - GROW_DIRECTION_END + GROW_DIRECTION_END, + GROW_DIRECTION_BOTH }; enum FocusMode { @@ -271,6 +272,8 @@ public: NOTIFICATION_FOCUS_EXIT = 44, NOTIFICATION_THEME_CHANGED = 45, NOTIFICATION_MODAL_CLOSE = 46, + NOTIFICATION_SCROLL_BEGIN = 47, + NOTIFICATION_SCROLL_END = 48, }; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 5c0e8fefc7..09c6a3b32c 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -602,6 +602,7 @@ void LineEdit::_notification(int p_what) { } int x_ofs = 0; + int cached_text_width = text.empty() ? cached_placeholder_width : cached_width; switch (align) { @@ -615,15 +616,15 @@ void LineEdit::_notification(int p_what) { if (window_pos != 0) x_ofs = style->get_offset().x; else - x_ofs = int(size.width - (cached_width)) / 2; + x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - (cached_text_width)) / 2); } break; case ALIGN_RIGHT: { - x_ofs = int(size.width - style->get_offset().x - (cached_width)); + x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_text_width))); } break; } - int ofs_max = width - style->get_minimum_size().width; + int ofs_max = width - style->get_margin(MARGIN_RIGHT); int char_ofs = window_pos; int y_area = height - style->get_minimum_size().height; @@ -881,7 +882,7 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { } break; case ALIGN_RIGHT: { - pixel_ofs = int(size.width - style->get_offset().x - (cached_width)); + pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width)); } break; } @@ -1014,6 +1015,15 @@ String LineEdit::get_text() const { void LineEdit::set_placeholder(String p_text) { placeholder = tr(p_text); + if ((max_length <= 0) || (placeholder.length() <= max_length)) { + Ref<Font> font = get_font("font"); + cached_placeholder_width = 0; + if (font != NULL) { + for (int i = 0; i < placeholder.length(); i++) { + cached_placeholder_width += font->get_char_size(placeholder[i]).width; + } + } + } update(); } @@ -1127,6 +1137,7 @@ void LineEdit::clear_internal() { _clear_undo_stack(); cached_width = 0; + cached_placeholder_width = 0; cursor_pos = 0; window_pos = 0; undo_text = ""; @@ -1468,6 +1479,7 @@ LineEdit::LineEdit() { _create_undo_state(); align = ALIGN_LEFT; cached_width = 0; + cached_placeholder_width = 0; cursor_pos = 0; window_pos = 0; window_has_focus = true; diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e3ad3b17f1..c60ea36cc1 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -84,6 +84,7 @@ private: int max_length; // 0 for no maximum int cached_width; + int cached_placeholder_width; struct Selection { diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index aaad10f579..a9402d6404 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -112,7 +112,7 @@ void OptionButton::pressed() { void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID) { - popup->add_icon_check_item(p_icon, p_label, p_ID); + popup->add_icon_radio_check_item(p_icon, p_label, p_ID); if (popup->get_item_count() == 1) select(0); } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 9ff3bd6e81..fd2466407e 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -588,6 +588,13 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p update(); } +void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { + + add_icon_check_item(p_icon, p_label, p_ID, p_accel); + items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + update(); +} + void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index c7851969d0..fde91bd845 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -122,6 +122,7 @@ public: void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_radio_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); + void add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_submenu_item(const String &p_label, const String &p_submenu, int p_ID = -1); void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 33b3d46486..217df275b9 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -75,6 +75,12 @@ void ScrollContainer::_cancel_drag() { drag_accum = Vector2(); last_drag_accum = Vector2(); drag_from = Vector2(); + + if (beyond_deadzone) { + emit_signal("scroll_ended"); + propagate_notification(NOTIFICATION_SCROLL_END); + beyond_deadzone = false; + } } void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { @@ -122,13 +128,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->is_pressed()) { if (drag_touching) { - set_physics_process(false); - drag_touching_deaccel = false; - drag_touching = false; - drag_speed = Vector2(); - drag_accum = Vector2(); - last_drag_accum = Vector2(); - drag_from = Vector2(); + _cancel_drag(); } if (true) { @@ -138,6 +138,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); drag_touching = OS::get_singleton()->has_touchscreen_ui_hint(); drag_touching_deaccel = false; + beyond_deadzone = false; time_since_motion = 0; if (drag_touching) { set_physics_process(true); @@ -149,9 +150,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (drag_touching) { if (drag_speed == Vector2()) { - drag_touching_deaccel = false; - drag_touching = false; - set_physics_process(false); + _cancel_drag(); } else { drag_touching_deaccel = true; @@ -168,17 +167,27 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); drag_accum -= motion; - Vector2 diff = drag_from + drag_accum; - - if (scroll_h) - h_scroll->set_value(diff.x); - else - drag_accum.x = 0; - if (scroll_v) - v_scroll->set_value(diff.y); - else - drag_accum.y = 0; - time_since_motion = 0; + + if (beyond_deadzone || scroll_h && Math::abs(drag_accum.x) > deadzone || scroll_v && Math::abs(drag_accum.y) > deadzone) { + if (!beyond_deadzone) { + propagate_notification(NOTIFICATION_SCROLL_BEGIN); + emit_signal("scroll_started"); + + beyond_deadzone = true; + // resetting drag_accum here ensures smooth scrolling after reaching deadzone + drag_accum = -motion; + } + Vector2 diff = drag_from + drag_accum; + if (scroll_h) + h_scroll->set_value(diff.x); + else + drag_accum.x = 0; + if (scroll_v) + v_scroll->set_value(diff.y); + else + drag_accum.y = 0; + time_since_motion = 0; + } } } @@ -323,9 +332,7 @@ void ScrollContainer::_notification(int p_what) { drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y); if (turnoff_h && turnoff_v) { - set_physics_process(false); - drag_touching = false; - drag_touching_deaccel = false; + _cancel_drag(); } } else { @@ -430,6 +437,14 @@ void ScrollContainer::set_h_scroll(int p_pos) { _cancel_drag(); } +int ScrollContainer::get_deadzone() const { + return deadzone; +} + +void ScrollContainer::set_deadzone(int p_deadzone) { + deadzone = p_deadzone; +} + String ScrollContainer::get_configuration_warning() const { int found = 0; @@ -466,12 +481,20 @@ void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll); ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll); + ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); + ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); + + ADD_SIGNAL(MethodInfo("scroll_started")); + ADD_SIGNAL(MethodInfo("scroll_ended")); ADD_GROUP("Scroll", "scroll_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); + + GLOBAL_DEF("gui/common/default_scroll_deadzone", 0); }; ScrollContainer::ScrollContainer() { @@ -490,8 +513,11 @@ ScrollContainer::ScrollContainer() { drag_speed = Vector2(); drag_touching = false; drag_touching_deaccel = false; + beyond_deadzone = false; scroll_h = true; scroll_v = true; + deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone"); + set_clip_contents(true); }; diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 6e3387918b..3fe1ed447a 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -56,10 +56,13 @@ class ScrollContainer : public Container { bool drag_touching; bool drag_touching_deaccel; bool click_handled; + bool beyond_deadzone; bool scroll_h; bool scroll_v; + int deadzone; + void _cancel_drag(); protected: @@ -86,6 +89,9 @@ public: void set_enable_v_scroll(bool p_enable); bool is_v_scroll_enabled() const; + int get_deadzone() const; + void set_deadzone(int p_deadzone); + virtual bool clips_input() const; virtual String get_configuration_warning() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 6e85ce5eb4..0363dd44c2 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -31,6 +31,9 @@ #include "tab_container.h" #include "message_queue.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" int TabContainer::_get_top_margin() const { @@ -492,6 +495,141 @@ void TabContainer::_update_current_tab() { set_current_tab(current); } +Variant TabContainer::get_drag_data(const Point2 &p_point) { + + if (!drag_to_rearrange_enabled) + return Variant(); + + int tab_over = get_tab_idx_at_point(p_point); + + if (tab_over < 0) + return Variant(); + + HBoxContainer *drag_preview = memnew(HBoxContainer); + + Ref<Texture> icon = get_tab_icon(tab_over); + if (!icon.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(icon); + drag_preview->add_child(tf); + } + Label *label = memnew(Label(get_tab_title(tab_over))); + drag_preview->add_child(label); + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "tabc_element"; + drag_data["tabc_element"] = tab_over; + drag_data["from_path"] = get_path(); + return drag_data; +} + +bool TabContainer::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + + if (!drag_to_rearrange_enabled) + return false; + + Dictionary d = p_data; + if (!d.has("type")) + return false; + + if (String(d["type"]) == "tabc_element") { + + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + return true; + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between other TabContainers + Node *from_node = get_node(from_path); + TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); + if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + return true; + } + } + } + return false; +} + +void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { + + if (!drag_to_rearrange_enabled) + return; + + int hover_now = get_tab_idx_at_point(p_point); + + Dictionary d = p_data; + if (!d.has("type")) + return; + + if (String(d["type"]) == "tabc_element") { + + int tab_from_id = d["tabc_element"]; + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_child(get_tab_control(tab_from_id), hover_now); + set_current_tab(hover_now); + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between TabContainers + Node *from_node = get_node(from_path); + TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); + if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + Control *moving_tabc = from_tabc->get_tab_control(tab_from_id); + from_tabc->remove_child(moving_tabc); + add_child(moving_tabc); + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_child(moving_tabc, hover_now); + set_current_tab(hover_now); + emit_signal("tab_changed", hover_now); + } + } + } + update(); +} + +int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const { + + if (get_tab_count() == 0) + return -1; + + // must be on tabs in the tab header area. + if (p_point.x < tabs_ofs_cache || p_point.y > _get_top_margin()) + return -1; + + Size2 size = get_size(); + int right_ofs = 0; + + if (popup) { + Ref<Texture> menu = get_icon("menu"); + right_ofs += menu->get_width(); + } + if (buttons_visible_cache) { + Ref<Texture> increment = get_icon("increment"); + Ref<Texture> decrement = get_icon("decrement"); + right_ofs += increment->get_width() + decrement->get_width(); + } + if (p_point.x > size.width - right_ofs) { + return -1; + } + + // get the tab at the point + Vector<Control *> tabs = _get_tabs(); + int px = p_point.x; + px -= tabs_ofs_cache; + for (int i = first_tab_cache; i <= last_tab_cache; i++) { + int tab_width = _get_tab_width(i); + if (px < tab_width) { + return i; + } + px -= tab_width; + } + return -1; +} + void TabContainer::set_tab_align(TabAlign p_align) { ERR_FAIL_INDEX(p_align, 3); @@ -500,6 +638,7 @@ void TabContainer::set_tab_align(TabAlign p_align) { _change_notify("tab_align"); } + TabContainer::TabAlign TabContainer::get_tab_align() const { return align; @@ -643,6 +782,21 @@ Popup *TabContainer::get_popup() const { return popup; } +void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) { + drag_to_rearrange_enabled = p_enabled; +} + +bool TabContainer::get_drag_to_rearrange_enabled() const { + return drag_to_rearrange_enabled; +} +void TabContainer::set_tabs_rearrange_group(int p_group_id) { + tabs_rearrange_group = p_group_id; +} + +int TabContainer::get_tabs_rearrange_group() const { + return tabs_rearrange_group; +} + void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &TabContainer::_gui_input); @@ -664,6 +818,10 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); + ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabContainer::get_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabContainer::set_tabs_rearrange_group); + ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabContainer::get_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("_child_renamed_callback"), &TabContainer::_child_renamed_callback); ClassDB::bind_method(D_METHOD("_on_theme_changed"), &TabContainer::_on_theme_changed); @@ -676,6 +834,7 @@ void TabContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); BIND_ENUM_CONSTANT(ALIGN_LEFT); BIND_ENUM_CONSTANT(ALIGN_CENTER); @@ -694,4 +853,6 @@ TabContainer::TabContainer() { align = ALIGN_CENTER; tabs_visible = true; popup = NULL; + drag_to_rearrange_enabled = false; + tabs_rearrange_group = -1; } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 4bc6e00145..1afe5f7541 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -58,6 +58,8 @@ private: Control *_get_tab(int p_idx) const; int _get_top_margin() const; Popup *popup; + bool drag_to_rearrange_enabled; + int tabs_rearrange_group; Vector<Control *> _get_tabs() const; int _get_tab_width(int p_index) const; @@ -71,6 +73,11 @@ protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); + Variant get_drag_data(const Point2 &p_point); + bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; + void drop_data(const Point2 &p_point, const Variant &p_data); + int get_tab_idx_at_point(const Point2 &p_point) const; + static void _bind_methods(); public: @@ -104,6 +111,11 @@ public: void set_popup(Node *p_popup); Popup *get_popup() const; + void set_drag_to_rearrange_enabled(bool p_enabled); + bool get_drag_to_rearrange_enabled() const; + void set_tabs_rearrange_group(int p_group_id); + int get_tabs_rearrange_group() const; + TabContainer(); }; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index dee32aef7a..b114264de1 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -31,6 +31,9 @@ #include "tabs.h" #include "message_queue.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" Size2 Tabs::get_minimum_size() const { @@ -624,20 +627,105 @@ void Tabs::remove_tab(int p_idx) { Variant Tabs::get_drag_data(const Point2 &p_point) { - return get_tab_idx_at_point(p_point); + if (!drag_to_rearrange_enabled) + return Variant(); + + int tab_over = get_tab_idx_at_point(p_point); + + if (tab_over < 0) + return Variant(); + + HBoxContainer *drag_preview = memnew(HBoxContainer); + + if (!tabs[tab_over].icon.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(tabs[tab_over].icon); + drag_preview->add_child(tf); + } + Label *label = memnew(Label(tabs[tab_over].text)); + drag_preview->add_child(label); + if (!tabs[tab_over].right_button.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(tabs[tab_over].right_button); + drag_preview->add_child(tf); + } + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "tab_element"; + drag_data["tab_element"] = tab_over; + drag_data["from_path"] = get_path(); + return drag_data; } bool Tabs::can_drop_data(const Point2 &p_point, const Variant &p_data) const { - return get_tab_idx_at_point(p_point) > -1; + if (!drag_to_rearrange_enabled) + return false; + + Dictionary d = p_data; + if (!d.has("type")) + return false; + + if (String(d["type"]) == "tab_element") { + + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + return true; + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between other Tabs + Node *from_node = get_node(from_path); + Tabs *from_tabs = Object::cast_to<Tabs>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + return true; + } + } + } + return false; } void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) { + if (!drag_to_rearrange_enabled) + return; + int hover_now = get_tab_idx_at_point(p_point); - ERR_FAIL_INDEX(hover_now, tabs.size()); - emit_signal("reposition_active_tab_request", hover_now); + Dictionary d = p_data; + if (!d.has("type")) + return; + + if (String(d["type"]) == "tab_element") { + + int tab_from_id = d["tab_element"]; + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_tab(tab_from_id, hover_now); + emit_signal("reposition_active_tab_request", hover_now); + set_current_tab(hover_now); + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between Tabs + Node *from_node = get_node(from_path); + Tabs *from_tabs = Object::cast_to<Tabs>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + if (tab_from_id >= from_tabs->get_tab_count()) + return; + Tab moving_tab = from_tabs->tabs[tab_from_id]; + if (hover_now < 0) + hover_now = get_tab_count(); + tabs.insert(hover_now, moving_tab); + from_tabs->remove_tab(tab_from_id); + set_current_tab(hover_now); + emit_signal("tab_changed", hover_now); + _update_cache(); + } + } + } + update(); } int Tabs::get_tab_idx_at_point(const Point2 &p_point) const { @@ -817,6 +905,21 @@ bool Tabs::get_scrolling_enabled() const { return scrolling_enabled; } +void Tabs::set_drag_to_rearrange_enabled(bool p_enabled) { + drag_to_rearrange_enabled = p_enabled; +} + +bool Tabs::get_drag_to_rearrange_enabled() const { + return drag_to_rearrange_enabled; +} +void Tabs::set_tabs_rearrange_group(int p_group_id) { + tabs_rearrange_group = p_group_id; +} + +int Tabs::get_tabs_rearrange_group() const { + return tabs_rearrange_group; +} + void Tabs::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &Tabs::_gui_input); @@ -842,6 +945,10 @@ void Tabs::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &Tabs::get_tab_close_display_policy); ClassDB::bind_method(D_METHOD("set_scrolling_enabled", "enabled"), &Tabs::set_scrolling_enabled); ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &Tabs::get_scrolling_enabled); + ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &Tabs::set_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &Tabs::get_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &Tabs::set_tabs_rearrange_group); + ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &Tabs::get_tabs_rearrange_group); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("right_button_pressed", PropertyInfo(Variant::INT, "tab"))); @@ -854,6 +961,7 @@ void Tabs::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); BIND_ENUM_CONSTANT(ALIGN_LEFT); BIND_ENUM_CONSTANT(ALIGN_CENTER); @@ -884,4 +992,6 @@ Tabs::Tabs() { scrolling_enabled = true; buttons_visible = false; hover = -1; + drag_to_rearrange_enabled = false; + tabs_rearrange_group = -1; } diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h index 246b3cba67..3b38e7f2cb 100644 --- a/scene/gui/tabs.h +++ b/scene/gui/tabs.h @@ -90,6 +90,8 @@ private: int hover; // hovered tab int min_width; bool scrolling_enabled; + bool drag_to_rearrange_enabled; + int tabs_rearrange_group; int get_tab_width(int p_idx) const; void _ensure_no_over_offset(); @@ -143,6 +145,11 @@ public: void set_scrolling_enabled(bool p_enabled); bool get_scrolling_enabled() const; + void set_drag_to_rearrange_enabled(bool p_enabled); + bool get_drag_to_rearrange_enabled() const; + void set_tabs_rearrange_group(int p_group_id); + int get_tabs_rearrange_group() const; + void ensure_tab_visible(int p_idx); void set_min_width(int p_width); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e214a020d5..cc6a677ec8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -145,7 +145,6 @@ void TextEdit::Text::_update_line_cache(int p_line) const { text[p_line].region_info.clear(); - int ending_color_region = -1; for (int i = 0; i < len; i++) { if (!_is_symbol(str[i])) @@ -186,11 +185,6 @@ void TextEdit::Text::_update_line_cache(int p_line) const { text[p_line].region_info[i] = cri; i += lr - 1; - if (ending_color_region == -1 && !cr.line_only) { - ending_color_region = j; - } else if (ending_color_region == j) { - ending_color_region = -1; - } break; } @@ -219,15 +213,10 @@ void TextEdit::Text::_update_line_cache(int p_line) const { text[p_line].region_info[i] = cri; i += lr - 1; - if (ending_color_region == j) { - ending_color_region = -1; - } - break; } } } - text[p_line].ending_color_region = ending_color_region; } const Map<int, TextEdit::Text::ColorRegionInfo> &TextEdit::Text::get_color_region_info(int p_line) const { @@ -569,7 +558,6 @@ void TextEdit::_notification(int p_what) { } } break; case NOTIFICATION_DRAW: { - if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { draw_caret = false; } @@ -3196,6 +3184,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); text_changed_dirty = true; } + _line_edited_from(p_line); } String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { @@ -3246,6 +3235,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); text_changed_dirty = true; } + _line_edited_from(p_from_line); } void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) { @@ -3368,6 +3358,13 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) { update(); } +void TextEdit::_line_edited_from(int p_line) { + int cache_size = color_region_cache.size(); + for (int i = p_line; i < cache_size; i++) { + color_region_cache.erase(i); + } +} + int TextEdit::get_char_count() { int totalsize = 0; @@ -4009,15 +4006,49 @@ void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) update(); } -int TextEdit::_get_line_ending_color_region(int p_line) const { - if (p_line < 0 || p_line > text.size() - 1) { - return -1; +int TextEdit::_is_line_in_region(int p_line) { + + // do we have in cache? + if (color_region_cache.has(p_line)) { + return color_region_cache[p_line]; + } + + // if not find the closest line we have + int previous_line = p_line - 1; + for (previous_line; previous_line > -1; previous_line--) { + if (color_region_cache.has(p_line)) { + break; + } + } + + // calculate up to line we need and update the cache along the way. + int in_region = color_region_cache[previous_line]; + for (int i = previous_line; i < p_line; i++) { + const Map<int, Text::ColorRegionInfo> &cri_map = _get_line_color_region_info(i); + for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { + const Text::ColorRegionInfo &cri = E->get(); + if (in_region == -1) { + if (!cri.end) { + in_region = cri.region; + } + } else if (in_region == cri.region && !_get_color_region(cri.region).line_only) { + if (cri.end || _get_color_region(cri.region).eq) { + in_region = -1; + } + } + } + + if (in_region >= 0 && _get_color_region(in_region).line_only) { + in_region = -1; + } + + color_region_cache[i + 1] = in_region; } - return text.get_line_ending_color_region(p_line); + return in_region; } TextEdit::ColorRegion TextEdit::_get_color_region(int p_region) const { - if (p_region < 0 || p_region > color_regions.size()) { + if (p_region < 0 || p_region >= color_regions.size()) { return ColorRegion(); } return color_regions[p_region]; @@ -4034,6 +4065,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); + color_region_cache.clear(); text.clear_caches(); } @@ -5777,24 +5809,8 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int Color keyword_color; Color color; - int in_region = -1; + int in_region = _is_line_in_region(p_line); int deregion = 0; - for (int i = 0; i < p_line; i++) { - int ending_color_region = text.get_line_ending_color_region(i); - if (in_region == -1) { - in_region = ending_color_region; - } else if (in_region == ending_color_region) { - in_region = -1; - } else { - const Map<int, TextEdit::Text::ColorRegionInfo> &cri_map = text.get_color_region_info(i); - for (const Map<int, TextEdit::Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { - const TextEdit::Text::ColorRegionInfo &cri = E->get(); - if (cri.region == in_region) { - in_region = -1; - } - } - } - } const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text.get_color_region_info(p_line); const String &str = text[p_line]; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 2360ce79db..30e70bfd0b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -76,7 +76,6 @@ public: bool marked : 1; bool breakpoint : 1; bool hidden : 1; - int ending_color_region; Map<int, ColorRegionInfo> region_info; String data; }; @@ -103,7 +102,6 @@ public: bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } - int get_line_ending_color_region(int p_line) const { return text[p_line].ending_color_region; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } @@ -189,6 +187,8 @@ private: Size2 size; } cache; + Map<int, int> color_region_cache; + struct TextOperation { enum Type { @@ -368,6 +368,7 @@ private: void _update_caches(); void _cursor_changed_emit(); void _text_changed_emit(); + void _line_edited_from(int p_line); void _push_current_op(); @@ -407,7 +408,7 @@ public: SyntaxHighlighter *_get_syntax_highlighting(); void _set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter); - int _get_line_ending_color_region(int p_line) const; + int _is_line_in_region(int p_line); ColorRegion _get_color_region(int p_region) const; Map<int, Text::ColorRegionInfo> _get_line_color_region_info(int p_line) const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index ec01490ae5..fcf8768094 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1708,11 +1708,18 @@ bool Node::has_persistent_groups() const { return false; } -void Node::_print_tree(const Node *p_node) { +void Node::_print_tree_pretty(const String prefix, const bool last) { - print_line(String(p_node->get_path_to(this))); - for (int i = 0; i < data.children.size(); i++) - data.children[i]->_print_tree(p_node); + String new_prefix = last ? String::utf8(" ┖╴") : String::utf8(" ┠╴"); + print_line(prefix + new_prefix + String(get_name())); + for (int i = 0; i < data.children.size(); i++) { + new_prefix = last ? String::utf8(" ") : String::utf8(" ┃ "); + data.children[i]->_print_tree_pretty(prefix + new_prefix, i == data.children.size() - 1); + } +} + +void Node::print_tree_pretty() { + _print_tree_pretty("", true); } void Node::print_tree() { @@ -1720,6 +1727,12 @@ void Node::print_tree() { _print_tree(this); } +void Node::_print_tree(const Node *p_node) { + print_line(String(p_node->get_path_to(this))); + for (int i = 0; i < data.children.size(); i++) + data.children[i]->_print_tree(p_node); +} + void Node::_propagate_reverse_notification(int p_notification) { data.blocked++; @@ -2668,6 +2681,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip); ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index); ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree); + ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty); ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename); ClassDB::bind_method(D_METHOD("get_filename"), &Node::get_filename); ClassDB::bind_method(D_METHOD("propagate_notification", "what"), &Node::propagate_notification); diff --git a/scene/main/node.h b/scene/main/node.h index 2e8716cbd1..b9bafb1ed1 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -153,6 +153,7 @@ private: Ref<MultiplayerAPI> multiplayer_api; + void _print_tree_pretty(const String prefix, const bool last); void _print_tree(const Node *p_node); Node *_get_node(const NodePath &p_path) const; @@ -289,6 +290,7 @@ public: int get_index() const; void print_tree(); + void print_tree_pretty(); void set_filename(const String &p_filename); String get_filename() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 45a969eeda..568a765420 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1814,7 +1814,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (!over) { - OS::get_singleton()->set_cursor_shape(OS::CURSOR_ARROW); + OS::get_singleton()->set_cursor_shape((OS::CursorShape)Input::get_singleton()->get_default_cursor_shape()); return; } diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 07c1036a10..5a42873d79 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -861,7 +861,7 @@ void SurfaceTool::generate_tangents() { } } -void SurfaceTool::generate_normals() { +void SurfaceTool::generate_normals(bool p_flip) { ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES); @@ -887,7 +887,11 @@ void SurfaceTool::generate_normals() { ERR_FAIL_COND(!v[2]); E = v[2]->next(); - Vector3 normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; + Vector3 normal; + if (!p_flip) + normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; + else + normal = Plane(v[2]->get().vertex, v[1]->get().vertex, v[0]->get().vertex).normal; if (smooth) { @@ -980,7 +984,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("index"), &SurfaceTool::index); ClassDB::bind_method(D_METHOD("deindex"), &SurfaceTool::deindex); - ClassDB::bind_method(D_METHOD("generate_normals"), &SurfaceTool::generate_normals); + ClassDB::bind_method(D_METHOD("generate_normals", "flip"), &SurfaceTool::generate_normals, DEFVAL(false)); ClassDB::bind_method(D_METHOD("generate_tangents"), &SurfaceTool::generate_tangents); ClassDB::bind_method(D_METHOD("add_to_format", "flags"), &SurfaceTool::add_to_format); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 7a9aa349bb..459d399380 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -116,7 +116,7 @@ public: void index(); void deindex(); - void generate_normals(); + void generate_normals(bool p_flip = false); void generate_tangents(); void add_to_format(int p_flags) { format |= p_flags; } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 7fdc296bb4..bebbf6e238 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -919,6 +919,8 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &TileSet::tile_get_shape_count); ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &TileSet::_tile_set_shapes); ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &TileSet::_tile_get_shapes); + ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &TileSet::tile_set_tile_mode); + ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &TileSet::tile_get_tile_mode); ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &TileSet::tile_set_navigation_polygon); ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &TileSet::tile_get_navigation_polygon); ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &TileSet::tile_set_navigation_polygon_offset); @@ -948,6 +950,10 @@ void TileSet::_bind_methods() { BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT); BIND_ENUM_CONSTANT(BIND_BOTTOM); BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT); + + BIND_ENUM_CONSTANT(SINGLE_TILE); + BIND_ENUM_CONSTANT(AUTO_TILE); + BIND_ENUM_CONSTANT(ANIMATED_TILE); } TileSet::TileSet() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 46f34b6252..706d04998f 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -238,5 +238,6 @@ public: VARIANT_ENUM_CAST(TileSet::AutotileBindings); VARIANT_ENUM_CAST(TileSet::BitmaskMode); +VARIANT_ENUM_CAST(TileSet::TileMode); #endif // TILE_SET_H |