diff options
201 files changed, 10521 insertions, 1768 deletions
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index 0ede7e87cb..456d29520f 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -76,7 +76,7 @@ Ref<NetworkedMultiplayerPeer> MultiplayerAPI::get_network_peer() const { void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND(root_node == NULL); - ERR_FAIL_COND(p_packet_len < 5); + ERR_FAIL_COND(p_packet_len < 1); uint8_t packet_type = p_packet[0]; @@ -123,6 +123,11 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ } } break; + + case NETWORK_COMMAND_RAW: { + + _process_raw(p_from, p_packet, p_packet_len); + } break; } } @@ -259,6 +264,8 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND(p_packet_len < 2); + String paths; paths.parse_utf8((const char *)&p_packet[1], p_packet_len - 1); @@ -648,6 +655,34 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const _send_rpc(p_node, p_peer_id, p_unreliable, true, p_property, &vptr, 1); } +Error MultiplayerAPI::send_bytes(PoolVector<uint8_t> p_data, int p_to) { + + ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA); + ERR_FAIL_COND_V(!network_peer.is_valid(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(network_peer->get_connection_status() != NetworkedMultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + + MAKE_ROOM(p_data.size() + 1); + PoolVector<uint8_t>::Read r = p_data.read(); + packet_cache[0] = NETWORK_COMMAND_RAW; + memcpy(&packet_cache[1], &r[0], p_data.size()); + network_peer->set_target_peer(p_to); + return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +} + +void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { + + ERR_FAIL_COND(p_packet_len < 2); + + PoolVector<uint8_t> out; + int len = p_packet_len - 1; + out.resize(len); + { + PoolVector<uint8_t>::Write w = out.write(); + memcpy(&w[0], &p_packet[1], len); + } + emit_signal("network_peer_packet", p_from, out); +} + int MultiplayerAPI::get_network_unique_id() const { ERR_FAIL_COND_V(!network_peer.is_valid(), 0); @@ -686,6 +721,7 @@ Vector<int> MultiplayerAPI::get_network_connected_peers() const { void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST)); ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); @@ -708,6 +744,7 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("network_peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::POOL_BYTE_ARRAY, "packet"))); ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index cc60890ced..25f445004d 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -43,6 +43,7 @@ protected: Node *_process_get_node(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); void _send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount); bool _send_confirm_path(NodePath p_path, PathSentCache *psc, int p_from); @@ -53,6 +54,7 @@ public: NETWORK_COMMAND_REMOTE_SET, NETWORK_COMMAND_SIMPLIFY_PATH, NETWORK_COMMAND_CONFIRM_PATH, + NETWORK_COMMAND_RAW, }; void poll(); @@ -60,6 +62,7 @@ public: void set_root_node(Node *p_node); void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_peer); Ref<NetworkedMultiplayerPeer> get_network_peer() const; + Error send_bytes(PoolVector<uint8_t> p_data, int p_to = NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST); // Called by Node.rpc void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); diff --git a/core/object.h b/core/object.h index c405e22557..7963a43fd6 100644 --- a/core/object.h +++ b/core/object.h @@ -55,7 +55,7 @@ enum PropertyHint { PROPERTY_HINT_RANGE, ///< hint_text = "min,max,step,slider; //slider is optional" PROPERTY_HINT_EXP_RANGE, ///< hint_text = "min,max,step", exponential edit PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" - PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) + PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "full" to also include in/out. (ie: "attenuation,inout") PROPERTY_HINT_LENGTH, ///< hint_text= "length" (as integer) PROPERTY_HINT_SPRITE_FRAME, PROPERTY_HINT_KEY_ACCEL, ///< hint_text= "length" (as integer) diff --git a/core/script_language.h b/core/script_language.h index 55a20c7478..b4c55cac9e 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -236,6 +236,8 @@ public: virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const = 0; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) = 0; + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) {} + virtual void remove_named_global_constant(const StringName &p_name) {} /* MULTITHREAD FUNCTIONS */ diff --git a/doc/classes/@GDScript.xml b/doc/classes/@GDScript.xml index acece05510..cddc59ab71 100644 --- a/doc/classes/@GDScript.xml +++ b/doc/classes/@GDScript.xml @@ -485,6 +485,14 @@ Returns True/False whether [code]s[/code] is an infinity value (either positive infinity or negative infinity). </description> </method> + <method name="is_instance_valid"> + <return type="bool"> + </return> + <argument index="0" name="instance" type="Object"> + </argument> + <description> + </description> + </method> <method name="is_nan"> <return type="bool"> </return> @@ -510,9 +518,9 @@ <method name="lerp"> <return type="float"> </return> - <argument index="0" name="from" type="float"> + <argument index="0" name="from" type="Variant"> </argument> - <argument index="1" name="to" type="float"> + <argument index="1" name="to" type="Variant"> </argument> <argument index="2" name="weight" type="float"> </argument> diff --git a/doc/classes/AnimatedSprite.xml b/doc/classes/AnimatedSprite.xml index a6521c0cd1..a41ed0e689 100644 --- a/doc/classes/AnimatedSprite.xml +++ b/doc/classes/AnimatedSprite.xml @@ -60,6 +60,8 @@ <member name="playing" type="bool" setter="_set_playing" getter="_is_playing"> If [code]true[/code] the [member animation] is currently playing. </member> + <member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale"> + </member> </members> <signals> <signal name="animation_finished"> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 38aab5231e..80d7b7783f 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -91,7 +91,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the length in indices of the index array in the requested surface (see [method add_surface]). + Return the length in indices of the index array in the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_array_len" qualifiers="const"> @@ -100,7 +100,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the length in vertices of the vertex array in the requested surface (see [method add_surface]). + Return the length in vertices of the vertex array in the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_arrays" qualifiers="const"> @@ -125,7 +125,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the format mask of the requested surface (see [method add_surface]). + Return the format mask of the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_material" qualifiers="const"> @@ -151,7 +151,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the primitive type of the requested surface (see [method add_surface]). + Return the primitive type of the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_remove"> diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml new file mode 100644 index 0000000000..7e714305cd --- /dev/null +++ b/doc/classes/Bone2D.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Bone2D" inherits="Node2D" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="apply_rest"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="get_index_in_skeleton" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_skeleton_rest" qualifiers="const"> + <return type="Transform2D"> + </return> + <description> + </description> + </method> + </methods> + <members> + <member name="default_length" type="float" setter="set_default_length" getter="get_default_length"> + </member> + <member name="rest" type="Transform2D" setter="set_rest" getter="get_rest"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 1705808c04..c8622be4ad 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -566,6 +566,9 @@ <constant name="BLEND_MODE_PREMULT_ALPHA" value="4" enum="BlendMode"> Mix blending mode. Colors are assumed to be premultiplied by the alpha (opacity) value. </constant> + <constant name="BLEND_MODE_DISABLED" value="5" enum="BlendMode"> + Disable blending mode. Colors including alpha are written as is. Only applicable for render targets with a transparent background. No lighting will be applied. + </constant> <constant name="NOTIFICATION_TRANSFORM_CHANGED" value="29"> Canvas item transform has changed. Only received if requested. </constant> diff --git a/doc/classes/CanvasItemMaterial.xml b/doc/classes/CanvasItemMaterial.xml index 354bc10cd2..fe7194dcfe 100644 --- a/doc/classes/CanvasItemMaterial.xml +++ b/doc/classes/CanvasItemMaterial.xml @@ -36,9 +36,6 @@ <constant name="BLEND_MODE_PREMULT_ALPHA" value="4" enum="BlendMode"> Mix blending mode. Colors are assumed to be premultiplied by the alpha (opacity) value. </constant> - <constant name="BLEND_MODE_DISABLED" value="5" enum="BlendMode"> - Disable blending mode. Colors including alpha are written as is. Only applicable for render targets with a transparent background. No lighting will be applied. - </constant> <constant name="LIGHT_MODE_NORMAL" value="0" enum="LightMode"> Render the material using both light and non-light sensitive material properties. </constant> diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 2617aaef17..104c9e0a98 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -129,9 +129,9 @@ <argument index="3" name="a" type="float" default="1"> </argument> <description> - Constructs a color from an HSV profile. [code]h[/code] is a value between 0 and 360. [code]s[/code] and [code]v[/code] are values between 0 and 1. + Constructs a color from an HSV profile. [code]h[/code], [code]s[/code], and [code]v[/code] are values between 0 and 1. [codeblock] - var c = Color.from_hsv(210, 0.5, 0.79, 0.8) # equivalent to HSV(210, 50, 79, 0.8) or Color8(100, 151, 201, 0.8) + var c = Color.from_hsv(0.58, 0.5, 0.79, 0.8) # equivalent to HSV(210, 50, 79, 0.8) or Color8(100, 151, 201, 0.8) [/codeblock] </description> </method> diff --git a/doc/classes/ColorPickerButton.xml b/doc/classes/ColorPickerButton.xml index eb86dc8af8..656fce587f 100644 --- a/doc/classes/ColorPickerButton.xml +++ b/doc/classes/ColorPickerButton.xml @@ -42,6 +42,10 @@ Emitted when the color changes. </description> </signal> + <signal name="popup_closed"> + <description> + </description> + </signal> </signals> <constants> </constants> diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index 03db9c2ea1..71bdaff688 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -49,6 +49,22 @@ Returns the cache of points as a [PoolVector2Array]. </description> </method> + <method name="get_closest_offset" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="to_point" type="Vector2"> + </argument> + <description> + </description> + </method> + <method name="get_closest_point" qualifiers="const"> + <return type="Vector2"> + </return> + <argument index="0" name="to_point" type="Vector2"> + </argument> + <description> + </description> + </method> <method name="get_point_count" qualifiers="const"> <return type="int"> </return> diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml index f2308de12c..c012e2794e 100644 --- a/doc/classes/Curve3D.xml +++ b/doc/classes/Curve3D.xml @@ -56,6 +56,22 @@ Returns the cache of tilts as a [RealArray]. </description> </method> + <method name="get_closest_offset" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="to_point" type="Vector3"> + </argument> + <description> + </description> + </method> + <method name="get_closest_point" qualifiers="const"> + <return type="Vector3"> + </return> + <argument index="0" name="to_point" type="Vector3"> + </argument> + <description> + </description> + </method> <method name="get_point_count" qualifiers="const"> <return type="int"> </return> diff --git a/doc/classes/DynamicFont.xml b/doc/classes/DynamicFont.xml index 03752c6e89..2e2904c16c 100644 --- a/doc/classes/DynamicFont.xml +++ b/doc/classes/DynamicFont.xml @@ -73,6 +73,10 @@ <member name="font_data" type="DynamicFontData" setter="set_font_data" getter="get_font_data"> The font data. </member> + <member name="outline_color" type="Color" setter="set_outline_color" getter="get_outline_color"> + </member> + <member name="outline_size" type="int" setter="set_outline_size" getter="get_outline_size"> + </member> <member name="size" type="int" setter="set_size" getter="get_size"> The font size. </member> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 846d6f18ff..e8e2c4fd74 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -114,7 +114,7 @@ <argument index="3" name="ud" type="Variant" default="null"> </argument> <description> - Adds a custom menu to 'Project > Tools' as [code]name[/code] that calls [code]callback[/code] on an instance of [code]handler[/code] with a parameter [code]ud[/code] when user activates it. + Adds a custom menu to 'Project > Tools' as [code]name[/code] that calls [code]callback[/code] on an instance of [code]handler[/code] with a parameter [code]ud[/code] when user activates it. </description> </method> <method name="add_tool_submenu_item"> @@ -368,7 +368,7 @@ <argument index="0" name="name" type="String"> </argument> <description> - Removes a menu [code]name[/code] from 'Project > Tools'. + Removes a menu [code]name[/code] from 'Project > Tools'. </description> </method> <method name="save_external_data" qualifiers="virtual"> @@ -424,6 +424,12 @@ Emitted when user change main screen view (2D, 3D, Script, AssetLib). Works also with screens which are defined by plugins. </description> </signal> + <signal name="resource_saved"> + <argument index="0" name="resource" type="Object"> + </argument> + <description> + </description> + </signal> <signal name="scene_changed"> <argument index="0" name="scene_root" type="Object"> </argument> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 6384b4d0fd..cc2ae4e768 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -77,6 +77,8 @@ <member name="iterations_per_second" type="int" setter="set_iterations_per_second" getter="get_iterations_per_second"> The number of fixed iterations per second (for fixed process and physics). </member> + <member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix"> + </member> <member name="target_fps" type="int" setter="set_target_fps" getter="get_target_fps"> The desired frames per second. If the hardware cannot keep up, this setting may not be respected. Defaults to 0, which indicates no limit. </member> diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index 376d234ec3..3e47b167ef 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -24,6 +24,8 @@ </argument> <argument index="4" name="clip_w" type="int" default="-1"> </argument> + <argument index="5" name="outline_modulate" type="Color" default="Color( 1, 1, 1, 1 )"> + </argument> <description> Draw "string" into a canvas item using the font at a given position, with "modulate" color, and optionally clipping the width. "position" specifies the baseline, not the top. To draw from the top, [i]ascent[/i] must be added to the Y axis. </description> @@ -41,6 +43,8 @@ </argument> <argument index="4" name="modulate" type="Color" default="Color( 1, 1, 1, 1 )"> </argument> + <argument index="5" name="outline" type="bool" default="false"> + </argument> <description> Draw character "char" into a canvas item using the font at a given position, with "modulate" color, and optionally kerning if "next" is passed. clipping the width. "position" specifies the baseline, not the top. To draw from the top, [i]ascent[/i] must be added to the Y axis. The width used by the character is returned, making this function useful for drawing strings character by character. </description> @@ -75,6 +79,12 @@ Return the size of a string, taking kerning and advance into account. </description> </method> + <method name="has_outline" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> <method name="is_distance_field_hint" qualifiers="const"> <return type="bool"> </return> diff --git a/doc/classes/Geometry.xml b/doc/classes/Geometry.xml index 78496700dc..ea2e2f7595 100644 --- a/doc/classes/Geometry.xml +++ b/doc/classes/Geometry.xml @@ -160,6 +160,21 @@ <description> </description> </method> + <method name="line_intersects_line_2d"> + <return type="Variant"> + </return> + <argument index="0" name="from_a" type="Vector2"> + </argument> + <argument index="1" name="dir_a" type="Vector2"> + </argument> + <argument index="2" name="from_b" type="Vector2"> + </argument> + <argument index="3" name="dir_b" type="Vector2"> + </argument> + <description> + Checks if the two lines ([code]from_a[/code], [code]dir_a[/code]) and ([code]from_b[/code], [code]dir_b[/code]) intersect. If yes, return the point of intersection as [Vector2]. If no intersection takes place, returns an empty [Variant]. Note that the lines are specified using direction vectors, not end points. + </description> + </method> <method name="make_atlas"> <return type="Dictionary"> </return> @@ -259,21 +274,6 @@ Checks if the two segments ([code]from_a[/code], [code]to_a[/code]) and ([code]from_b[/code], [code]to_b[/code]) intersect. If yes, return the point of intersection as [Vector2]. If no intersection takes place, returns an empty [Variant]. </description> </method> - <method name="line_intersects_line_2d"> - <return type="Variant"> - </return> - <argument index="0" name="from_a" type="Vector2"> - </argument> - <argument index="1" name="dir_a" type="Vector2"> - </argument> - <argument index="2" name="from_b" type="Vector2"> - </argument> - <argument index="3" name="dir_b" type="Vector2"> - </argument> - <description> - Checks if the two lines ([code]from_a[/code], [code]dir_a[/code]) and ([code]from_b[/code], [code]dir_b[/code]) intersect. If yes, return the point of intersection as [Vector2]. If no intersection takes place, returns an empty [Variant]. Note that the lines are specified using direction vectors, not end points. - </description> - </method> <method name="segment_intersects_sphere"> <return type="PoolVector3Array"> </return> diff --git a/doc/classes/GridContainer.xml b/doc/classes/GridContainer.xml index 346ab9d357..8a8a9a2d24 100644 --- a/doc/classes/GridContainer.xml +++ b/doc/classes/GridContainer.xml @@ -11,6 +11,16 @@ <demos> </demos> <methods> + <method name="get_child_control_at_cell"> + <return type="Control"> + </return> + <argument index="0" name="row" type="int"> + </argument> + <argument index="1" name="column" type="int"> + </argument> + <description> + </description> + </method> </methods> <members> <member name="columns" type="int" setter="set_columns" getter="get_columns"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index ea61aced83..ca2d519e8a 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -210,6 +210,8 @@ <method name="generate_mipmaps"> <return type="int" enum="Error"> </return> + <argument index="0" name="renormalize" type="bool" default="false"> + </argument> <description> Generates mipmaps for the image. Mipmaps are pre-calculated and lower resolution copies of the image. Mipmaps are automatically used if the image needs to be scaled down when rendered. This improves image quality and the performance of the rendering. Returns an error if the image is compressed, in a custom format or if the image's width/height is 0. </description> @@ -255,6 +257,14 @@ Returns the color of the pixel at [code](x, y)[/code] if the image is locked. If the image is unlocked it always returns a [Color] with the value [code](0, 0, 0, 1.0)[/code]. </description> </method> + <method name="get_pixelv" qualifiers="const"> + <return type="Color"> + </return> + <argument index="0" name="src" type="Vector2"> + </argument> + <description> + </description> + </method> <method name="get_rect" qualifiers="const"> <return type="Image"> </return> @@ -411,6 +421,16 @@ [/codeblock] </description> </method> + <method name="set_pixelv"> + <return type="void"> + </return> + <argument index="0" name="dst" type="Vector2"> + </argument> + <argument index="1" name="color" type="Color"> + </argument> + <description> + </description> + </method> <method name="shrink_x2"> <return type="void"> </return> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 58cee7b556..f92f8da5dd 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -48,6 +48,14 @@ If the device has an accelerometer, this will return the acceleration. Otherwise, it returns an empty [Vector3]. </description> </method> + <method name="get_action_strength" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="action" type="String"> + </argument> + <description> + </description> + </method> <method name="get_connected_joypads"> <return type="Array"> </return> @@ -287,6 +295,14 @@ Set a custom mouse cursor image, which is only visible inside the game window. The hotspot can also be specified. Passing [code]null[/code] to the image parameter resets to the system cursor. See enum [code]CURSOR_*[/code] for the list of shapes. </description> </method> + <method name="set_default_cursor_shape"> + <return type="void"> + </return> + <argument index="0" name="shape" type="int" enum="Input.CursorShape" default="0"> + </argument> + <description> + </description> + </method> <method name="set_mouse_mode"> <return type="void"> </return> diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index b30b144614..cbed2285df 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -13,20 +13,19 @@ <demos> </demos> <methods> - <method name="action_match" qualifiers="const"> - <return type="bool"> + <method name="as_text" qualifiers="const"> + <return type="String"> </return> - <argument index="0" name="event" type="InputEvent"> - </argument> <description> - Returns [code]true[/code] if this event matches [code]event[/code]. + Returns a [String] representation of the event. </description> </method> - <method name="as_text" qualifiers="const"> - <return type="String"> + <method name="get_action_strength" qualifiers="const"> + <return type="float"> </return> + <argument index="0" name="action" type="String"> + </argument> <description> - Returns a [String] representation of the event. </description> </method> <method name="is_action" qualifiers="const"> diff --git a/doc/classes/InputMap.xml b/doc/classes/InputMap.xml index eeb225d445..3399a3f096 100644 --- a/doc/classes/InputMap.xml +++ b/doc/classes/InputMap.xml @@ -34,6 +34,14 @@ Removes an [InputEvent] from an action. </description> </method> + <method name="action_erase_events"> + <return type="void"> + </return> + <argument index="0" name="action" type="String"> + </argument> + <description> + </description> + </method> <method name="action_has_event"> <return type="bool"> </return> @@ -45,13 +53,26 @@ Returns [true] if an action has an [InputEvent] associated with it. </description> </method> + <method name="action_set_deadzone"> + <return type="void"> + </return> + <argument index="0" name="deadzone" type="String"> + </argument> + <argument index="1" name="arg1" type="float"> + </argument> + <description> + </description> + </method> <method name="add_action"> <return type="void"> </return> <argument index="0" name="action" type="String"> </argument> + <argument index="1" name="deadzone" type="float" default="0.5"> + </argument> <description> - Adds an (empty) action to the [code]InputMap[/code]. An [InputEvent] can then be added to this action with [method action_add_event]. + Adds an (empty) action to the [code]InputMap[/code], with a configurable [code]deadzone[/code]. + An [InputEvent] can then be added to this action with [method action_add_event]. </description> </method> <method name="erase_action"> diff --git a/doc/classes/InstancePlaceholder.xml b/doc/classes/InstancePlaceholder.xml index 5945e1068f..71c859fd9e 100644 --- a/doc/classes/InstancePlaceholder.xml +++ b/doc/classes/InstancePlaceholder.xml @@ -12,6 +12,16 @@ <demos> </demos> <methods> + <method name="create_instance"> + <return type="Node"> + </return> + <argument index="0" name="replace" type="bool" default="false"> + </argument> + <argument index="1" name="custom_scene" type="PackedScene" default="null"> + </argument> + <description> + </description> + </method> <method name="get_instance_path" qualifiers="const"> <return type="String"> </return> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index 24c28fc810..1e78a196b1 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -106,6 +106,8 @@ </theme_item> <theme_item name="font_color_shadow" type="Color"> </theme_item> + <theme_item name="font_outline_modulate" type="Color"> + </theme_item> <theme_item name="line_spacing" type="int"> </theme_item> <theme_item name="normal" type="StyleBox"> diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index c31438283e..d7a0385bb3 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -112,7 +112,7 @@ <member name="secret" type="bool" setter="set_secret" getter="is_secret"> If [code]true[/code], every character is replaced with the secret character (see [member secret_character]). </member> - <member name="secret_character" type="string" setter="set_secret_character" getter="get_secret_character"> + <member name="secret_character" type="String" setter="set_secret_character" getter="get_secret_character"> The character to use to mask secret input (defaults to "*"). Only a single character can be used as the secret character. </member> <member name="text" type="String" setter="set_text" getter="get_text"> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index b2ed712554..f6950d0fbc 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -61,10 +61,21 @@ </return> <description> Method used for polling the MultiplayerAPI. - You only need to worry about this if you are using [memeber Node.custom_multplayer] override. + You only need to worry about this if you are using [member Node.custom_multplayer] override. SceneTree will poll the default MultiplayerAPI for you. </description> </method> + <method name="send_bytes"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="bytes" type="PoolByteArray"> + </argument> + <argument index="1" name="id" type="int" default="0"> + </argument> + <description> + Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method NetworkedMultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers. + </description> + </method> <method name="set_root_node"> <return type="void"> </return> @@ -78,7 +89,7 @@ </methods> <members> <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 MultiplayerAPI 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 MultiplayerAPI's signals. + The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI 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 MultiplayerAPI's signals. </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 MultiplayerAPI's [member network_peer] refuses new incoming connections. @@ -109,9 +120,18 @@ Emitted whenever this MultiplayerAPI's [member network_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. </description> </signal> + <signal name="network_peer_packet"> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="packet" type="PoolByteArray"> + </argument> + <description> + Emitted whenever this MultiplayerAPI's [member network_peer] receive a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet. + </description> + </signal> <signal name="server_disconnected"> <description> - Emitted whenever this MultiplayerAPI's [member network_peer] disconnected from server. Only emitted on clients. + Emitted whenever this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients. </description> </signal> </signals> diff --git a/doc/classes/NetworkedMultiplayerPeer.xml b/doc/classes/NetworkedMultiplayerPeer.xml index 71614c5f76..2780334384 100644 --- a/doc/classes/NetworkedMultiplayerPeer.xml +++ b/doc/classes/NetworkedMultiplayerPeer.xml @@ -46,7 +46,8 @@ <argument index="0" name="id" type="int"> </argument> <description> - The peer to which packets will be sent. Default value: [code]0[/code]. + Sets the peer to which packets will be sent. + The [code]id[/code] can be one of: [code]TARGET_PEER_BROADCAST[/code] to send to all connected peers, [code]TARGET_PEER_SERVER[/code] to send to the peer acting as server, a valid peer ID to send to that specific peer, a negative peer ID to send to all peers except that one. Default: [code]TARGET_PEER_BROADCAST[/code] </description> </method> </methods> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index c11b8aacaa..05ac6b1c0e 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -586,7 +586,7 @@ <argument index="1" name="method" type="String"> </argument> <description> - Sends a [method rpc] to a specific peer identified by [code]peer_id[/code]. Returns an empty [Variant]. + Sends a [method rpc] to a specific peer identified by [code]peer_id[/code] (see [method NetworkedMultiplayerPeer.set_target_peer]). Returns an empty [Variant]. </description> </method> <method name="rpc_unreliable" qualifiers="vararg"> @@ -606,7 +606,7 @@ <argument index="1" name="method" type="String"> </argument> <description> - Sends a [method rpc] to a specific peer identified by [code]peer_id[/code] using an unreliable protocol. Returns an empty [Variant]. + Sends a [method rpc] to a specific peer identified by [code]peer_id[/code] using an unreliable protocol (see [method NetworkedMultiplayerPeer.set_target_peer]). Returns an empty [Variant]. </description> </method> <method name="rset"> @@ -641,7 +641,7 @@ <argument index="2" name="value" type="Variant"> </argument> <description> - Remotely changes the property's value on a specific peer identified by [code]peer_id[/code]. + Remotely changes the property's value on a specific peer identified by [code]peer_id[/code] (see [method NetworkedMultiplayerPeer.set_target_peer]). </description> </method> <method name="rset_unreliable"> @@ -665,7 +665,7 @@ <argument index="2" name="value" type="Variant"> </argument> <description> - Remotely changes property's value on a specific peer identified by [code]peer_id[/code] using an unreliable protocol. + Remotely changes property's value on a specific peer identified by [code]peer_id[/code] using an unreliable protocol (see [method NetworkedMultiplayerPeer.set_target_peer]). </description> </method> <method name="set_display_folded"> @@ -703,7 +703,7 @@ <argument index="0" name="enable" type="bool"> </argument> <description> - Enables or disables internal physics for this node. Internal physics processing happens in isolation from the normal [method]_physics_process[/code] calls and is used by some nodes internally to guarantee proper functioning even if the node is paused or physics processing is disabled for scripting ([method set_physics_process]). Only useful for advanced uses to manipulate built-in nodes behaviour. + Enables or disables internal physics for this node. Internal physics processing happens in isolation from the normal [method _physics_process] calls and is used by some nodes internally to guarantee proper functioning even if the node is paused or physics processing is disabled for scripting ([method set_physics_process]). Only useful for advanced uses to manipulate built-in nodes behaviour. </description> </method> <method name="set_process"> @@ -730,7 +730,7 @@ <argument index="0" name="enable" type="bool"> </argument> <description> - Enables or disabled internal processing for this node. Internal processing happens in isolation from the normal [method]_process[/code] calls and is used by some nodes internally to guarantee proper functioning even if the node is paused or processing is disabled for scripting ([method set_process]). Only useful for advanced uses to manipulate built-in nodes behaviour. + Enables or disabled internal processing for this node. Internal processing happens in isolation from the normal [method _process] calls and is used by some nodes internally to guarantee proper functioning even if the node is paused or processing is disabled for scripting ([method set_process]). Only useful for advanced uses to manipulate built-in nodes behaviour. </description> </method> <method name="set_process_unhandled_input"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index d38a89874c..e4375cfb79 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -430,6 +430,12 @@ 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="get_window_safe_area" qualifiers="const"> + <return type="Rect2"> + </return> + <description> + </description> + </method> <method name="has_environment" qualifiers="const"> <return type="bool"> </return> @@ -698,7 +704,7 @@ <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"> @@ -716,6 +722,8 @@ <member name="window_minimized" type="bool" setter="set_window_minimized" getter="is_window_minimized"> If [code]true[/code] the window is minimized. </member> + <member name="window_per_pixel_transparency_enabled" type="bool" setter="set_window_per_pixel_transparency_enabled" getter="get_window_per_pixel_transparency_enabled"> + </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. </member> diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml index 8cb53dd98e..c58c932b61 100644 --- a/doc/classes/OptionButton.xml +++ b/doc/classes/OptionButton.xml @@ -18,7 +18,7 @@ </argument> <argument index="1" name="label" type="String"> </argument> - <argument index="2" name="id" type="int"> + <argument index="2" name="id" type="int" default="-1"> </argument> <description> Add an item, with a "texture" icon, text "label" and (optionally) id. If no "id" is passed, "id" becomes the item index. New items are appended at the end. diff --git a/doc/classes/PacketPeerUDP.xml b/doc/classes/PacketPeerUDP.xml index 1b821e06cf..d4e3d17de6 100644 --- a/doc/classes/PacketPeerUDP.xml +++ b/doc/classes/PacketPeerUDP.xml @@ -22,14 +22,14 @@ <return type="String"> </return> <description> - Return the IP of the remote peer that sent the last packet(that was received with [method get_packet] or [method get_var]). + Return the IP of the remote peer that sent the last packet(that was received with [method PacketPeer.get_packet] or [method PacketPeer.get_var]). </description> </method> <method name="get_packet_port" qualifiers="const"> <return type="int"> </return> <description> - Return the port of the remote peer that sent the last packet(that was received with [method get_packet] or [method get_var]). + Return the port of the remote peer that sent the last packet(that was received with [method PacketPeer.get_packet] or [method PacketPeer.get_var]). </description> </method> <method name="is_listening" qualifiers="const"> diff --git a/doc/classes/PanoramaSky.xml b/doc/classes/PanoramaSky.xml index eb288d13be..402e65c573 100644 --- a/doc/classes/PanoramaSky.xml +++ b/doc/classes/PanoramaSky.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PanoramaSky" inherits="Sky" category="Core" version="3.1"> <brief_description> + A type of [Sky] used to draw a background texture. </brief_description> <description> + A resource referenced in an [Environment] that is used to draw a background. The Panorama sky functions similar to skyboxes in other engines except it uses a equirectangular sky map instead of a cube map. </description> <tutorials> </tutorials> @@ -12,6 +14,7 @@ </methods> <members> <member name="panorama" type="Texture" setter="set_panorama" getter="get_panorama"> + [Texture] to be applied to the PanoramaSky. </member> </members> <constants> diff --git a/doc/classes/Path.xml b/doc/classes/Path.xml index 621a513da3..5ece747aaf 100644 --- a/doc/classes/Path.xml +++ b/doc/classes/Path.xml @@ -16,6 +16,12 @@ <member name="curve" type="Curve3D" setter="set_curve" getter="get_curve"> </member> </members> + <signals> + <signal name="curve_changed"> + <description> + </description> + </signal> + </signals> <constants> </constants> </class> diff --git a/doc/classes/PhysicalBone.xml b/doc/classes/PhysicalBone.xml new file mode 100644 index 0000000000..80b3c11270 --- /dev/null +++ b/doc/classes/PhysicalBone.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="PhysicalBone" inherits="PhysicsBody" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="is_simulating_physics"> + <return type="bool"> + </return> + <description> + </description> + </method> + </methods> + <members> + <member name="body_offset" type="Transform" setter="set_body_offset" getter="get_body_offset"> + </member> + <member name="bounce" type="float" setter="set_bounce" getter="get_bounce"> + </member> + <member name="friction" type="float" setter="set_friction" getter="get_friction"> + </member> + <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale"> + </member> + <member name="joint_offset" type="Transform" setter="set_joint_offset" getter="get_joint_offset"> + </member> + <member name="joint_type" type="int" setter="set_joint_type" getter="get_joint_type" enum="PhysicalBone.JointType"> + </member> + <member name="mass" type="float" setter="set_mass" getter="get_mass"> + </member> + <member name="simulate_physics" type="bool" setter="set_simulate_physics" getter="get_simulate_physics"> + </member> + <member name="static_body" type="bool" setter="set_static_body" getter="is_static_body"> + </member> + <member name="weight" type="float" setter="set_weight" getter="get_weight"> + </member> + </members> + <constants> + <constant name="JOINT_TYPE_NONE" value="0" enum="JointType"> + </constant> + <constant name="JOINT_TYPE_PIN" value="1" enum="JointType"> + </constant> + <constant name="JOINT_TYPE_CONE" value="2" enum="JointType"> + </constant> + <constant name="JOINT_TYPE_HINGE" value="3" enum="JointType"> + </constant> + <constant name="JOINT_TYPE_SLIDER" value="4" enum="JointType"> + </constant> + <constant name="JOINT_TYPE_6DOF" value="5" enum="JointType"> + </constant> + </constants> +</class> diff --git a/doc/classes/Physics2DServer.xml b/doc/classes/Physics2DServer.xml index 98535dd330..1098b0d6b1 100644 --- a/doc/classes/Physics2DServer.xml +++ b/doc/classes/Physics2DServer.xml @@ -688,7 +688,7 @@ <argument index="1" name="space" type="RID"> </argument> <description> - Assigns a space to the body (see [method create_space]). + Assigns a space to the body (see [method space_create]). </description> </method> <method name="body_set_state"> diff --git a/doc/classes/Polygon2D.xml b/doc/classes/Polygon2D.xml index 429114a128..bf4519fd0a 100644 --- a/doc/classes/Polygon2D.xml +++ b/doc/classes/Polygon2D.xml @@ -11,11 +11,79 @@ <demos> </demos> <methods> + <method name="add_bone"> + <return type="void"> + </return> + <argument index="0" name="path" type="NodePath"> + </argument> + <argument index="1" name="weights" type="PoolRealArray"> + </argument> + <description> + </description> + </method> + <method name="clear_bones"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="erase_bone"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_bone_count" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_bone_path" qualifiers="const"> + <return type="NodePath"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_bone_weights" qualifiers="const"> + <return type="PoolRealArray"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_bone_path"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="path" type="NodePath"> + </argument> + <description> + </description> + </method> + <method name="set_bone_weights"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="weights" type="PoolRealArray"> + </argument> + <description> + </description> + </method> </methods> <members> <member name="antialiased" type="bool" setter="set_antialiased" getter="get_antialiased"> If [code]true[/code] polygon edges will be anti-aliased. Default value: [code]false[/code]. </member> + <member name="bones" type="Array" setter="_set_bones" getter="_get_bones"> + </member> <member name="color" type="Color" setter="set_color" getter="get_color"> The polygon's fill color. If [code]texture[/code] is defined, it will be multiplied by this color. It will also be the default color for vertices not set in [code]vertex_colors[/code]. </member> @@ -31,6 +99,8 @@ <member name="polygon" type="PoolVector2Array" setter="set_polygon" getter="get_polygon"> The polygon's list of vertices. The final point will be connected to the first. </member> + <member name="skeleton" type="NodePath" setter="set_skeleton" getter="get_skeleton"> + </member> <member name="splits" type="PoolIntArray" setter="set_splits" getter="get_splits"> </member> <member name="texture" type="Texture" setter="set_texture" getter="get_texture"> diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml index db8d927c9e..ae5bce5d7d 100644 --- a/doc/classes/Popup.xml +++ b/doc/classes/Popup.xml @@ -50,7 +50,7 @@ </methods> <members> <member name="popup_exclusive" type="bool" setter="set_exclusive" getter="is_exclusive"> - If [code]true[/code] the popup will not be hidden when a click event occurs outside of it, or when it receives the [code]ui_cancel[/code] action event. + If [code]true[/code] the popup will not be hidden when a click event occurs outside of it, or when it receives the [code]ui_cancel[/code] action event. </member> </members> <signals> diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml index c5bc3d6e47..0f0511258b 100644 --- a/doc/classes/PrimitiveMesh.xml +++ b/doc/classes/PrimitiveMesh.xml @@ -19,6 +19,10 @@ </method> </methods> <members> + <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb"> + </member> + <member name="flip_faces" type="bool" setter="set_flip_faces" getter="get_flip_faces"> + </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The current [Material] of the primitive mesh. </member> diff --git a/doc/classes/ProceduralSky.xml b/doc/classes/ProceduralSky.xml index 11af73f953..664c80be5e 100644 --- a/doc/classes/ProceduralSky.xml +++ b/doc/classes/ProceduralSky.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ProceduralSky" inherits="Sky" category="Core" version="3.1"> <brief_description> + Type of [Sky] that is generated procedurally based on user input parameters. </brief_description> <description> + ProceduralSky provides a way to create an effective background quickly by defining procedural parameters for the sun, the sky and the ground. The sky and ground are very similar, they are defined by a color at the horizon, another color, and finally an easing curve to interpolate between these two colors. Similarly the sun is described by a position in the sky, a color, and an easing curve. However, the sun also defines a minimum and maximum angle, these two values define at what distance the easing curve begins and ends from the sun, and thus end up defining the size of the sun in the sky. + The ProceduralSky is updated on the CPU after the parameters change and stored in a texture and then displayed as a background in the scene. This makes it relatively unsuitable for realtime updates during gameplay. But with a small texture size it is still feasible to update relatively frequently becuase it is updated on a background thread when multi-threading is available. </description> <tutorials> </tutorials> @@ -12,36 +15,52 @@ </methods> <members> <member name="ground_bottom_color" type="Color" setter="set_ground_bottom_color" getter="get_ground_bottom_color"> + Color of the ground at the bottom. </member> <member name="ground_curve" type="float" setter="set_ground_curve" getter="get_ground_curve"> + How quickly the [member ground_horizon_color] fades into the [member ground_bottom_color]. </member> <member name="ground_energy" type="float" setter="set_ground_energy" getter="get_ground_energy"> + Amount of energy contribution from the ground. </member> <member name="ground_horizon_color" type="Color" setter="set_ground_horizon_color" getter="get_ground_horizon_color"> + Color of the ground at the horizon. </member> <member name="sky_curve" type="float" setter="set_sky_curve" getter="get_sky_curve"> + How quickly the [member sky_horizon_color] fades into the [member sky_top_color]. </member> <member name="sky_energy" type="float" setter="set_sky_energy" getter="get_sky_energy"> + Amount of energy contribution from the sky. </member> <member name="sky_horizon_color" type="Color" setter="set_sky_horizon_color" getter="get_sky_horizon_color"> + Color of the sky at the horizon. </member> <member name="sky_top_color" type="Color" setter="set_sky_top_color" getter="get_sky_top_color"> + Color of the sky at the top. </member> <member name="sun_angle_max" type="float" setter="set_sun_angle_max" getter="get_sun_angle_max"> + Distance from center of sun where it fades out completely. </member> <member name="sun_angle_min" type="float" setter="set_sun_angle_min" getter="get_sun_angle_min"> + Distance from sun where it goes from solid to starting to fade. </member> <member name="sun_color" type="Color" setter="set_sun_color" getter="get_sun_color"> + Color of the sun. </member> <member name="sun_curve" type="float" setter="set_sun_curve" getter="get_sun_curve"> + How quickly the sun fades away between [member sun_angle_min] and [member sun_angle_max] </member> <member name="sun_energy" type="float" setter="set_sun_energy" getter="get_sun_energy"> + Amount of energy contribution from the sun. </member> <member name="sun_latitude" type="float" setter="set_sun_latitude" getter="get_sun_latitude"> + The suns height using polar coordinates. </member> <member name="sun_longitude" type="float" setter="set_sun_longitude" getter="get_sun_longitude"> + The direction of the sun using polar coordinates. </member> <member name="texture_size" type="int" setter="set_texture_size" getter="get_texture_size" enum="ProceduralSky.TextureSize"> + Size of [Texture] that the ProceduralSky will generate. </member> </members> <constants> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index a6c63cfb76..4ec4bbee4f 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -47,6 +47,13 @@ Clears the tag stack and sets [member bbcode_text] to an empty string. </description> </method> + <method name="get_content_height"> + <return type="int"> + </return> + <description> + Returns the height of the content. + </description> + </method> <method name="get_line_count" qualifiers="const"> <return type="int"> </return> @@ -75,13 +82,6 @@ Returns the number of visible lines. </description> </method> - <method name="get_content_height"> - <return type="int"> - </return> - <description> - Returns the height of the content. - </description> - </method> <method name="newline"> <return type="void"> </return> @@ -328,12 +328,6 @@ </theme_item> <theme_item name="font_color_shadow" type="Color"> </theme_item> - <theme_item name="shadow_as_outline" type="int"> - </theme_item> - <theme_item name="shadow_offset_x" type="int"> - </theme_item> - <theme_item name="shadow_offset_y" type="int"> - </theme_item> <theme_item name="italics_font" type="Font"> </theme_item> <theme_item name="line_separation" type="int"> @@ -346,6 +340,12 @@ </theme_item> <theme_item name="selection_color" type="Color"> </theme_item> + <theme_item name="shadow_as_outline" type="int"> + </theme_item> + <theme_item name="shadow_offset_x" type="int"> + </theme_item> + <theme_item name="shadow_offset_y" type="int"> + </theme_item> <theme_item name="table_hseparation" type="int"> </theme_item> <theme_item name="table_vseparation" type="int"> diff --git a/doc/classes/ScrollContainer.xml b/doc/classes/ScrollContainer.xml index 64b0fcfd83..f16b3920c2 100644 --- a/doc/classes/ScrollContainer.xml +++ b/doc/classes/ScrollContainer.xml @@ -22,7 +22,7 @@ If [code]true[/code], enables horizontal scrolling. </member> <member name="scroll_vertical" type="int" setter="set_v_scroll" getter="get_v_scroll"> - The current horizontal scroll value. + The current vertical scroll value. </member> <member name="scroll_vertical_enabled" type="bool" setter="set_enable_v_scroll" getter="is_v_scroll_enabled"> If [code]true[/code], enables vertical scrolling. diff --git a/doc/classes/Skeleton.xml b/doc/classes/Skeleton.xml index 0208d56cfa..67e10e8f0a 100644 --- a/doc/classes/Skeleton.xml +++ b/doc/classes/Skeleton.xml @@ -131,6 +131,30 @@ <description> </description> </method> + <method name="physical_bones_add_collision_exception"> + <return type="void"> + </return> + <argument index="0" name="exception" type="RID"> + </argument> + <description> + </description> + </method> + <method name="physical_bones_remove_collision_exception"> + <return type="void"> + </return> + <argument index="0" name="exception" type="RID"> + </argument> + <description> + </description> + </method> + <method name="physical_bones_simulation"> + <return type="void"> + </return> + <argument index="0" name="start" type="bool"> + </argument> + <description> + </description> + </method> <method name="set_bone_custom_pose"> <return type="void"> </return> diff --git a/doc/classes/Skeleton2D.xml b/doc/classes/Skeleton2D.xml new file mode 100644 index 0000000000..654c816ba8 --- /dev/null +++ b/doc/classes/Skeleton2D.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Skeleton2D" inherits="Node2D" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_bone"> + <return type="Bone2D"> + </return> + <argument index="0" name="arg0" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_bone_count" qualifiers="const"> + <return type="int"> + </return> + <description> + </description> + </method> + <method name="get_skeleton" qualifiers="const"> + <return type="RID"> + </return> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/SpatialMaterial.xml b/doc/classes/SpatialMaterial.xml index ace1aa846e..5feaf70e9d 100644 --- a/doc/classes/SpatialMaterial.xml +++ b/doc/classes/SpatialMaterial.xml @@ -84,6 +84,8 @@ </member> <member name="flags_albedo_tex_force_srgb" type="bool" setter="set_flag" getter="get_flag"> </member> + <member name="flags_do_not_receive_shadows" type="bool" setter="set_flag" getter="get_flag"> + </member> <member name="flags_fixed_size" type="bool" setter="set_flag" getter="get_flag"> </member> <member name="flags_no_depth_test" type="bool" setter="set_flag" getter="get_flag"> @@ -316,7 +318,9 @@ </constant> <constant name="FLAG_ALBEDO_TEXTURE_FORCE_SRGB" value="13" enum="Flags"> </constant> - <constant name="FLAG_MAX" value="14" enum="Flags"> + <constant name="FLAG_DONT_RECEIVE_SHADOWS" value="14" enum="Flags"> + </constant> + <constant name="FLAG_MAX" value="15" enum="Flags"> </constant> <constant name="DIFFUSE_BURLEY" value="0" enum="DiffuseMode"> </constant> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 76d94fbfbc..11fc00d129 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -99,6 +99,8 @@ </argument> <argument index="2" name="can_be_hidden" type="bool" default="true"> </argument> + <argument index="3" name="wrap_index" type="int" default="0"> + </argument> <description> </description> </method> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index ec67370c79..775fef4fb7 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -16,14 +16,14 @@ <return type="void"> </return> <description> - Clear all cells. + Clears all cells. </description> </method> <method name="fix_invalid_tiles"> <return type="void"> </return> <description> - Clear cells that does not exist in the tileset. + Clears cells that do not exist in the tileset. </description> </method> <method name="get_cell" qualifiers="const"> @@ -34,7 +34,7 @@ <argument index="1" name="y" type="int"> </argument> <description> - Return the tile index of the referenced cell. + Returns the tile index of the given cell. </description> </method> <method name="get_cellv" qualifiers="const"> @@ -43,7 +43,7 @@ <argument index="0" name="position" type="Vector2"> </argument> <description> - Return the tile index of the cell referenced by a Vector2. + Returns the tile index of the cell given by a Vector2. </description> </method> <method name="get_collision_layer_bit" qualifiers="const"> @@ -52,6 +52,7 @@ <argument index="0" name="bit" type="int"> </argument> <description> + Returns [code]true[/code] if the given collision layer bit is set. </description> </method> <method name="get_collision_mask_bit" qualifiers="const"> @@ -60,13 +61,14 @@ <argument index="0" name="bit" type="int"> </argument> <description> + Returns [code]true[/code] if the given collision mask bit is set. </description> </method> <method name="get_used_cells" qualifiers="const"> <return type="Array"> </return> <description> - Return an array of all cells containing a tile from the tileset (i.e. a tile index different from -1). + Returns an array of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]). </description> </method> <method name="get_used_cells_by_id" qualifiers="const"> @@ -75,12 +77,14 @@ <argument index="0" name="id" type="int"> </argument> <description> + Returns an array of all cells with the given tile id. </description> </method> <method name="get_used_rect"> <return type="Rect2"> </return> <description> + Returns a rectangle enclosing the used (non-empty) tiles of the map. </description> </method> <method name="is_cell_transposed" qualifiers="const"> @@ -91,7 +95,7 @@ <argument index="1" name="y" type="int"> </argument> <description> - Return whether the referenced cell is transposed, i.e. the X and Y axes are swapped (mirroring with regard to the (1,1) vector). + Returns [code]true[/code] if the given cell is transposed, i.e. the x and y axes are swapped. </description> </method> <method name="is_cell_x_flipped" qualifiers="const"> @@ -102,7 +106,7 @@ <argument index="1" name="y" type="int"> </argument> <description> - Return whether the referenced cell is flipped over the X axis. + Returns [code]true[/code] if the given cell is flipped in the x axis. </description> </method> <method name="is_cell_y_flipped" qualifiers="const"> @@ -113,7 +117,7 @@ <argument index="1" name="y" type="int"> </argument> <description> - Return whether the referenced cell is flipped over the Y axis. + Returns [code]true[/code] if the given cell is flipped in the y axis. </description> </method> <method name="map_to_world" qualifiers="const"> @@ -124,8 +128,8 @@ <argument index="1" name="ignore_half_ofs" type="bool" default="false"> </argument> <description> - Return the absolute world position corresponding to the tilemap (grid-based) coordinates given as an argument. - Optionally, the tilemap's potential half offset can be ignored. + Returns the global position corresponding to the given tilemap (grid-based) coordinates. + Optionally, the tilemap's half offset can be ignored. </description> </method> <method name="set_cell"> @@ -146,9 +150,9 @@ <argument index="6" name="autotile_coord" type="Vector2" default="Vector2( 0, 0 )"> </argument> <description> - Set the tile index for the cell referenced by its grid-based X and Y coordinates. - A tile index of -1 clears the cell. - Optionally, the tile can also be flipped over the X and Y coordinates, transposed, or be given autotile coordinates. + Sets the tile index for the cell given by a Vector2. + An index of [code]-1[/code] clears the cell. + Optionally, the tile can also be flipped, transposed, or given autotile coordinates. </description> </method> <method name="set_cellv"> @@ -165,9 +169,9 @@ <argument index="4" name="transpose" type="bool" default="false"> </argument> <description> - Set the tile index for the cell referenced by a Vector2 of grid-based coordinates. - A tile index of -1 clears the cell. - Optionally, the tile can also be flipped over the X and Y axes or transposed. + Sets the tile index for the given cell. + An index of [code]-1[/code] clears the cell. + Optionally, the tile can also be flipped or transposed. </description> </method> <method name="set_collision_layer_bit"> @@ -178,7 +182,7 @@ <argument index="1" name="value" type="bool"> </argument> <description> - Set any collision layer to be [code]true[/code] or [code]false[/code]. + Sets the given collision layer bit. </description> </method> <method name="set_collision_mask_bit"> @@ -189,6 +193,7 @@ <argument index="1" name="value" type="bool"> </argument> <description> + Sets the given collision mask bit. </description> </method> <method name="update_bitmask_area"> @@ -197,7 +202,7 @@ <argument index="0" name="position" type="Vector2"> </argument> <description> - Applies autotiling rules to the cell (and its adjacent cells) referenced by its grid-based X and Y coordinates. + Applies autotiling rules to the cell (and its adjacent cells) referenced by its grid-based x and y coordinates. </description> </method> <method name="update_bitmask_region"> @@ -208,8 +213,8 @@ <argument index="1" name="end" type="Vector2" default="Vector2( 0, 0 )"> </argument> <description> - Applies autotiling rules to the cells in the given region (specified by grid-based X and Y coordinates). - Calling with invalid (or missing) parameters applies autotiling rules for the entire TileMap. + Applies autotiling rules to the cells in the given region (specified by grid-based x and y coordinates). + Calling with invalid (or missing) parameters applies autotiling rules for the entire tilemap. </description> </method> <method name="world_to_map" qualifiers="const"> @@ -218,7 +223,7 @@ <argument index="0" name="world_position" type="Vector2"> </argument> <description> - Return the tilemap (grid-based) coordinates corresponding to the absolute world position given as an argument. + Returns the tilemap (grid-based) coordinatescorresponding to the given global position. </description> </method> </methods> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 4e218f5595..8f7969505e 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -262,6 +262,14 @@ <description> </description> </method> + <method name="tile_get_z_index" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </method> <method name="tile_set_light_occluder"> <return type="void"> </return> @@ -428,6 +436,16 @@ <description> </description> </method> + <method name="tile_set_z_index"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="z_index" type="int"> + </argument> + <description> + </description> + </method> </methods> <constants> <constant name="BITMASK_2X2" value="0" enum="BitmaskMode"> diff --git a/doc/classes/Timer.xml b/doc/classes/Timer.xml index c51a52d911..d1c8722901 100644 --- a/doc/classes/Timer.xml +++ b/doc/classes/Timer.xml @@ -24,7 +24,7 @@ <argument index="0" name="time_sec" type="float" default="-1"> </argument> <description> - Starts the timer. Sets [code]wait_time[/code] to [code]time_sec[/code] if [code]time_sec[/code] > 0. This also resets the remaining time to [code]wait_time[/code]. + Starts the timer. Sets [code]wait_time[/code] to [code]time_sec[/code] if [code]time_sec[/code] > 0. This also resets the remaining time to [code]wait_time[/code]. Note: this method will not resume a paused timer. See [method set_paused]. </description> </method> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index 95405cc4f6..2332c1a7aa 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -340,7 +340,7 @@ </methods> <members> <member name="playback_process_mode" type="int" setter="set_tween_process_mode" getter="get_tween_process_mode" enum="Tween.TweenProcessMode"> - The tween's animation process thread. See [enum TweenProcessMode]. Default value: [enum TWEEN_PROCESS_IDLE]. + The tween's animation process thread. See [enum TweenProcessMode]. Default value: [enum TWEEN_PROCESS_IDLE]. </member> <member name="playback_speed" type="float" setter="set_speed_scale" getter="get_speed_scale"> The tween's speed multiplier. For example, set it to [code]1.0[/code] for normal speed, [code]2.0[/code] for two times normal speed, or [code]0.5[/code] for half of the normal speed. A value of [code]0[/code] pauses the animation, but see also [method set_active] or [method stop_all] for this. diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 5ffe807606..ec92dcf900 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Vector2" category="Built-In Types" version="3.1"> <brief_description> - Vector used for 2D Math. + Vector used for 2D math. </brief_description> <description> - 2-element structure that can be used to represent positions in 2d-space, or any other pair of numeric values. + 2-element structure that can be used to represent positions in 2d space or any other pair of numeric values. </description> <tutorials> http://docs.godotengine.org/en/3.0/tutorials/math/index.html @@ -34,8 +34,8 @@ <return type="float"> </return> <description> - Returns the result of atan2 when called with the Vector's x and y as parameters (Math::atan2(x,y)). - Be aware that it therefore returns an angle oriented clockwise with regard to the (0, 1) unit vector, and not an angle oriented counter-clockwise with regard to the (1, 0) unit vector (which would be the typical trigonometric representation of the angle when calling Math::atan2(y,x)). + Returns the vector's angle in radians with respect to the x-axis, or [code](1, 0)[/code] vector. + Equivalent to the result of atan2 when called with the vector's x and y as parameters: [code]atan2(x, y)[/code]. </description> </method> <method name="angle_to"> @@ -60,7 +60,7 @@ <return type="float"> </return> <description> - Returns the ratio of X to Y. + Returns the ratio of x to y. </description> </method> <method name="bounce"> @@ -69,7 +69,13 @@ <argument index="0" name="n" type="Vector2"> </argument> <description> - Bounce returns the vector "bounced off" from the given plane, specified by its normal vector. + Returns the vector "bounced off" from a plane defined by the given normal. + </description> + </method> + <method name="ceil"> + <return type="Vector2"> + </return> + <description> </description> </method> <method name="clamped"> @@ -87,7 +93,7 @@ <argument index="0" name="with" type="Vector2"> </argument> <description> - Returns the 2-dimensional analog of the cross product with the given Vector2. + Returns the 2 dimensional analog of the cross product with the given vector. </description> </method> <method name="cubic_interpolate"> @@ -102,7 +108,7 @@ <argument index="3" name="t" type="float"> </argument> <description> - Cubicly interpolates between this Vector and "b", using "pre_a" and "post_b" as handles, and returning the result at position "t". "t" should be a float of 0.0-1.0, a percentage of how far along the interpolation is. + Cubicly interpolates between this vector and [code]b[/code] using [code]pre_a[/code] and [code]post_b[/code] as handles, and returns the result at position [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], or a percentage of how far along the interpolation is. </description> </method> <method name="distance_squared_to"> @@ -111,7 +117,7 @@ <argument index="0" name="to" type="Vector2"> </argument> <description> - Returns the squared distance to vector "b". Prefer this function over "distance_to" if you need to sort vectors or need the squared distance for some formula. + Returns the squared distance to vector [code]b[/code]. Prefer this function over [method distance_to] if you need to sort vectors or need the squared distance for some formula. </description> </method> <method name="distance_to"> @@ -120,7 +126,7 @@ <argument index="0" name="to" type="Vector2"> </argument> <description> - Returns the distance to vector "b". + Returns the distance to vector [code]b[/code]. </description> </method> <method name="dot"> @@ -129,7 +135,7 @@ <argument index="0" name="with" type="Vector2"> </argument> <description> - Returns the dot product with vector "b". + Returns the dot product with vector [code]b[/code]. </description> </method> <method name="floor"> @@ -143,21 +149,21 @@ <return type="bool"> </return> <description> - Returns whether the vector is normalized or not. + Returns [code]true[/code] if the vector is normalized. </description> </method> <method name="length"> <return type="float"> </return> <description> - Returns the length of the vector. + Returns the vector's length. </description> </method> <method name="length_squared"> <return type="float"> </return> <description> - Returns the squared length of the vector. Prefer this function over "length" if you need to sort vectors or need the squared length for some formula. + Returns the vector's length squared. Prefer this function over [member length] if you need to sort vectors or need the squared length for some formula. </description> </method> <method name="linear_interpolate"> @@ -168,14 +174,14 @@ <argument index="1" name="t" type="float"> </argument> <description> - Returns the result of the linear interpolation between this vector and "b", by amount "t". "t" should be a float of 0.0-1.0, a percentage of how far along the interpolation is. + Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], a percentage of how far along the interpolation is. </description> </method> <method name="normalized"> <return type="Vector2"> </return> <description> - Returns a normalized vector to unit length. + Returns the vector scaled to unit length. Equivalent to [code]v / v.length()[/code]. </description> </method> <method name="reflect"> @@ -184,7 +190,7 @@ <argument index="0" name="n" type="Vector2"> </argument> <description> - Reflects the vector along the given plane, specified by its normal vector. + Returns the vector reflected from a plane defined by the given normal. </description> </method> <method name="rotated"> @@ -193,7 +199,13 @@ <argument index="0" name="phi" type="float"> </argument> <description> - Rotates the vector by "phi" radians. + Returns the vector rotated by [code]phi[/code] radians. + </description> + </method> + <method name="round"> + <return type="Vector2"> + </return> + <description> </description> </method> <method name="slide"> @@ -202,7 +214,7 @@ <argument index="0" name="n" type="Vector2"> </argument> <description> - Slide returns the component of the vector along the given plane, specified by its normal vector. + Returns the component of the vector along a plane defined by the given normal. </description> </method> <method name="snapped"> @@ -211,7 +223,7 @@ <argument index="0" name="by" type="Vector2"> </argument> <description> - Snaps the vector to a grid with the given size. + Returns the vector snapped to a grid with the given size. </description> </method> <method name="tangent"> @@ -224,10 +236,10 @@ </methods> <members> <member name="x" type="float" setter="" getter=""> - X component of the vector. + The vector's x component. </member> <member name="y" type="float" setter="" getter=""> - Y component of the vector. + The vector's y component. </member> </members> <constants> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 940d4dd6cf..a5fc62b6f0 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -38,7 +38,7 @@ <argument index="0" name="to" type="Vector3"> </argument> <description> - Returns the vector's minimum angle to the vector [code]to[/code]. + Returns the minimum angle to the given vector. </description> </method> <method name="bounce"> @@ -47,7 +47,7 @@ <argument index="0" name="n" type="Vector3"> </argument> <description> - Bounce returns the vector "bounced off" from the given plane, specified by its normal vector. + Returns the vector "bounced off" from a plane defined by the given normal. </description> </method> <method name="ceil"> @@ -87,7 +87,7 @@ <argument index="0" name="b" type="Vector3"> </argument> <description> - Returns the squared distance to [code]b[/code]. Prefer this function over distance_to if you need to sort vectors or need the squared distance for some formula. + Returns the squared distance to [code]b[/code]. Prefer this function over [method distance_to] if you need to sort vectors or need the squared distance for some formula. </description> </method> <method name="distance_to"> @@ -96,7 +96,7 @@ <argument index="0" name="b" type="Vector3"> </argument> <description> - Returns the distance to b. + Returns the distance to [code]b[/code]. </description> </method> <method name="dot"> @@ -105,7 +105,7 @@ <argument index="0" name="b" type="Vector3"> </argument> <description> - Returns the dot product with b. + Returns the dot product with [code]b[/code]. </description> </method> <method name="floor"> @@ -119,28 +119,28 @@ <return type="Vector3"> </return> <description> - Returns the inverse of the vector. This is the same as Vector3( 1.0 / v.x, 1.0 / v.y, 1.0 / v.z ) + Returns the inverse of the vector. This is the same as [code]Vector3( 1.0 / v.x, 1.0 / v.y, 1.0 / v.z )[/code]. </description> </method> <method name="is_normalized"> <return type="bool"> </return> <description> - Returns whether the vector is normalized or not. + Returns [code]true[/code] if the vector is normalized. </description> </method> <method name="length"> <return type="float"> </return> <description> - Returns the length of the vector. + Returns the vector's length. </description> </method> <method name="length_squared"> <return type="float"> </return> <description> - Returns the length of the vector, squared. Prefer this function over "length" if you need to sort vectors or need the squared length for some formula. + Returns the vector's length squared. Prefer this function over [method length] if you need to sort vectors or need the squared length for some formula. </description> </method> <method name="linear_interpolate"> @@ -151,28 +151,28 @@ <argument index="1" name="t" type="float"> </argument> <description> - Linearly interpolates the vector to a given one (b), by the given amount (t). (t) should be a float of 0.0-1.0, a percentage of how far along the interpolation is. + Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], a percentage of how far along the interpolation is. </description> </method> <method name="max_axis"> <return type="int"> </return> <description> - Returns AXIS_X, AXIS_Y or AXIS_Z depending on which axis is the largest. + Returns the axis of the vector's largest value. See [code]AXIS_*[/code] constants. </description> </method> <method name="min_axis"> <return type="int"> </return> <description> - Returns AXIS_X, AXIS_Y or AXIS_Z depending on which axis is the smallest. + Returns the axis of the vector's smallest value. See [code]AXIS_*[/code] constants. </description> </method> <method name="normalized"> <return type="Vector3"> </return> <description> - Returns a copy of the normalized vector to unit length. This is the same as v / v.length(). + Returns the vector scaled to unit length. Equivalent to [code]v / v.length()[/code]. </description> </method> <method name="outer"> @@ -181,7 +181,7 @@ <argument index="0" name="b" type="Vector3"> </argument> <description> - Returns the outer product with b. + Returns the outer product with [code]b[/code]. </description> </method> <method name="reflect"> @@ -190,7 +190,7 @@ <argument index="0" name="n" type="Vector3"> </argument> <description> - Reflects the vector along the given plane, specified by its normal vector. + Returns the vector reflected from a plane defined by the given normal. </description> </method> <method name="rotated"> @@ -201,7 +201,13 @@ <argument index="1" name="phi" type="float"> </argument> <description> - Rotates the vector around some axis by phi radians. The axis must be a normalized vector. + Rotates the vector around a given axis by [code]phi[/code] radians. The axis must be a normalized vector. + </description> + </method> + <method name="round"> + <return type="Vector3"> + </return> + <description> </description> </method> <method name="slide"> @@ -210,7 +216,7 @@ <argument index="0" name="n" type="Vector3"> </argument> <description> - Slide returns the component of the vector along the given plane, specified by its normal vector. + Returns the component of the vector along a plane defined by the given normal. </description> </method> <method name="snapped"> @@ -232,18 +238,18 @@ </methods> <members> <member name="x" type="float" setter="" getter=""> - X component of the vector. + The vector's x component. </member> <member name="y" type="float" setter="" getter=""> - Y component of the vector. + The vector's y component. </member> <member name="z" type="float" setter="" getter=""> - Z component of the vector. + The vector's z component. </member> </members> <constants> <constant name="AXIS_X" value="0"> - Enumerated value for the X axis. Returned by functions like max_axis or min_axis. + Enumerated value for the X axis. Returned by [method max_axis] and [method min_axis]. </constant> <constant name="AXIS_Y" value="1"> Enumerated value for the Y axis. diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index baebddf3e0..893db84640 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -388,14 +388,17 @@ </argument> <argument index="4" name="uvs" type="PoolVector2Array" default="PoolVector2Array( )"> </argument> - <argument index="5" name="texture" type="RID"> + <argument index="5" name="bones" type="PoolIntArray" default="PoolIntArray( )"> </argument> - <argument index="6" name="count" type="int" default="-1"> + <argument index="6" name="weights" type="PoolRealArray" default="PoolRealArray( )"> </argument> - <argument index="7" name="normal_map" type="RID"> + <argument index="7" name="texture" type="RID"> + </argument> + <argument index="8" name="count" type="int" default="-1"> + </argument> + <argument index="9" name="normal_map" type="RID"> </argument> <description> - Adds a triangle array to the [CanvasItem]'s draw commands. </description> </method> <method name="canvas_item_clear"> @@ -3330,6 +3333,32 @@ Sets the texture's image data. If it's a CubeMap, it sets the image data at a cube side. </description> </method> + <method name="texture_set_data_partial"> + <return type="void"> + </return> + <argument index="0" name="texture" type="RID"> + </argument> + <argument index="1" name="image" type="Image"> + </argument> + <argument index="2" name="src_x" type="int"> + </argument> + <argument index="3" name="src_y" type="int"> + </argument> + <argument index="4" name="src_w" type="int"> + </argument> + <argument index="5" name="src_h" type="int"> + </argument> + <argument index="6" name="dst_x" type="int"> + </argument> + <argument index="7" name="dst_y" type="int"> + </argument> + <argument index="8" name="dst_mip" type="int"> + </argument> + <argument index="9" name="cube_side" type="int" enum="VisualServer.CubeMapSide" default="0"> + </argument> + <description> + </description> + </method> <method name="texture_set_flags"> <return type="void"> </return> diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index 8074ce45ea..adbd810d11 100644..100755 --- a/doc/tools/makerst.py +++ b/doc/tools/makerst.py @@ -106,6 +106,7 @@ def make_class_list(class_list, columns): f.close() + def rstize_text(text, cclass): # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock" pos = 0 @@ -156,7 +157,7 @@ def rstize_text(text, cclass): # Escape * character to avoid interpreting it as emphasis pos = 0 - next_brac_pos = text.find('['); + next_brac_pos = text.find('[') while True: pos = text.find('*', pos, next_brac_pos) if pos == -1: @@ -258,15 +259,17 @@ def rstize_text(text, cclass): elif cmd == 'code': tag_text = '``' inside_code = True + elif cmd.startswith('enum '): + tag_text = make_enum(cmd[5:]) else: tag_text = make_type(tag_text) escape_post = True # Properly escape things like `[Node]s` - if escape_post and post_text and post_text[0].isalnum(): # not punctuation, escape + if escape_post and post_text and post_text[0].isalnum(): # not punctuation, escape post_text = '\ ' + post_text - next_brac_pos = post_text.find('[',0) + next_brac_pos = post_text.find('[', 0) iter_pos = 0 while not inside_code: iter_pos = post_text.find('*', iter_pos, next_brac_pos) @@ -286,7 +289,6 @@ def rstize_text(text, cclass): else: iter_pos += 1 - text = pre_text + tag_text + post_text pos = len(pre_text) + len(tag_text) @@ -299,16 +301,27 @@ def make_type(t): return ':ref:`' + t + '<class_' + t.lower() + '>`' return t + def make_enum(t): global class_names p = t.find(".") + # Global enums such as Error are relative to @GlobalScope. if p >= 0: c = t[0:p] - e = t[p+1:] - if c in class_names: - return ':ref:`' + e + '<enum_' + c.lower() + '_' + e.lower() + '>`' + e = t[p + 1:] + # Variant enums live in GlobalScope but still use periods. + if c == "Variant": + c = "@GlobalScope" + e = "Variant." + e + else: + # Things in GlobalScope don't have a period. + c = "@GlobalScope" + e = t + if c in class_names: + return ':ref:`' + e + '<enum_' + c.lower() + '_' + e.lower() + '>`' return t + def make_method( f, name, @@ -340,7 +353,10 @@ def make_method( if not event: if -1 in mdata['argidx']: - t += make_type(mdata[-1].attrib['type']) + if 'enum' in mdata[-1].attrib: + t += make_enum(mdata[-1].attrib['enum']) + else: + t += make_type(mdata[-1].attrib['type']) else: t += 'void' t += ' ' @@ -362,7 +378,10 @@ def make_method( else: s += ' ' - s += make_type(arg.attrib['type']) + if 'enum' in arg.attrib: + s += make_enum(arg.attrib['enum']) + else: + s += make_type(arg.attrib['type']) if 'name' in arg.attrib: s += ' ' + arg.attrib['name'] else: diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 54d803f13c..312d5aa378 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -127,7 +127,26 @@ public: String path; }; + struct DummySurface { + uint32_t format; + VS::PrimitiveType primitive; + PoolVector<uint8_t> array; + int vertex_count; + PoolVector<uint8_t> index_array; + int index_count; + AABB aabb; + Vector<PoolVector<uint8_t> > blend_shapes; + Vector<AABB> bone_aabbs; + }; + + struct DummyMesh : public RID_Data { + Vector<DummySurface> surfaces; + int blend_shape_count; + VS::BlendShapeMode blend_shape_mode; + }; + mutable RID_Owner<DummyTexture> texture_owner; + mutable RID_Owner<DummyMesh> mesh_owner; RID texture_create() { @@ -256,46 +275,128 @@ public: /* MESH API */ - RID mesh_create() { return RID(); } - - void mesh_add_surface_from_arrays(RID p_mesh, VS::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), uint32_t p_compress_format = Mesh::ARRAY_COMPRESS_DEFAULT) {} - void mesh_add_surface(RID p_mesh, uint32_t p_format, VS::PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) {} + RID mesh_create() { + DummyMesh *mesh = memnew(DummyMesh); + ERR_FAIL_COND_V(!mesh, RID()); + mesh->blend_shape_count = 0; + mesh->blend_shape_mode = VS::BLEND_SHAPE_MODE_NORMALIZED; + return mesh_owner.make_rid(mesh); + } - void mesh_add_surface_from_mesh_data(RID p_mesh, const Geometry::MeshData &p_mesh_data) {} - void mesh_add_surface_from_planes(RID p_mesh, const PoolVector<Plane> &p_planes) {} + void mesh_add_surface(RID p_mesh, uint32_t p_format, VS::PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + + m->surfaces.push_back(DummySurface()); + DummySurface *s = &m->surfaces[m->surfaces.size() - 1]; + s->format = p_format; + s->primitive = p_primitive; + s->array = p_array; + s->vertex_count = p_vertex_count; + s->index_array = p_index_array; + s->index_count = p_index_count; + s->aabb = p_aabb; + s->blend_shapes = p_blend_shapes; + s->bone_aabbs = p_bone_aabbs; + } - void mesh_set_blend_shape_count(RID p_mesh, int p_amount) {} - int mesh_get_blend_shape_count(RID p_mesh) const { return 0; } + void mesh_set_blend_shape_count(RID p_mesh, int p_amount) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + m->blend_shape_count = p_amount; + } + int mesh_get_blend_shape_count(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + return m->blend_shape_count; + } - void mesh_set_blend_shape_mode(RID p_mesh, VS::BlendShapeMode p_mode) {} - VS::BlendShapeMode mesh_get_blend_shape_mode(RID p_mesh) const { return VS::BLEND_SHAPE_MODE_NORMALIZED; } + void mesh_set_blend_shape_mode(RID p_mesh, VS::BlendShapeMode p_mode) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + m->blend_shape_mode = p_mode; + } + VS::BlendShapeMode mesh_get_blend_shape_mode(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, VS::BLEND_SHAPE_MODE_NORMALIZED); + return m->blend_shape_mode; + } void mesh_surface_update_region(RID p_mesh, int p_surface, int p_offset, const PoolVector<uint8_t> &p_data) {} void mesh_surface_set_material(RID p_mesh, int p_surface, RID p_material) {} RID mesh_surface_get_material(RID p_mesh, int p_surface) const { return RID(); } - int mesh_surface_get_array_len(RID p_mesh, int p_surface) const { return 0; } - int mesh_surface_get_array_index_len(RID p_mesh, int p_surface) const { return 0; } + int mesh_surface_get_array_len(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].vertex_count; + } + int mesh_surface_get_array_index_len(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].index_count; + } PoolVector<uint8_t> mesh_surface_get_array(RID p_mesh, int p_surface) const { - PoolVector<uint8_t> p; - return p; + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, PoolVector<uint8_t>()); + + return m->surfaces[p_surface].array; } PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const { - PoolVector<uint8_t> p; - return p; + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, PoolVector<uint8_t>()); + + return m->surfaces[p_surface].index_array; + } + + uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].format; + } + VS::PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, VS::PRIMITIVE_POINTS); + + return m->surfaces[p_surface].primitive; + } + + AABB mesh_surface_get_aabb(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, AABB()); + + return m->surfaces[p_surface].aabb; } + Vector<PoolVector<uint8_t> > mesh_surface_get_blend_shapes(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, Vector<PoolVector<uint8_t> >()); - uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const { return 0; } - VS::PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const { return VS::PRIMITIVE_POINTS; } + return m->surfaces[p_surface].blend_shapes; + } + Vector<AABB> mesh_surface_get_skeleton_aabb(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, Vector<AABB>()); + + return m->surfaces[p_surface].bone_aabbs; + } - AABB mesh_surface_get_aabb(RID p_mesh, int p_surface) const { return AABB(); } - Vector<PoolVector<uint8_t> > mesh_surface_get_blend_shapes(RID p_mesh, int p_surface) const { return Vector<PoolVector<uint8_t> >(); } - Vector<AABB> mesh_surface_get_skeleton_aabb(RID p_mesh, int p_surface) const { return Vector<AABB>(); } + void mesh_remove_surface(RID p_mesh, int p_index) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + ERR_FAIL_COND(p_index >= m->surfaces.size()); - void mesh_remove_surface(RID p_mesh, int p_index) {} - int mesh_get_surface_count(RID p_mesh) const { return 0; } + m->surfaces.remove(p_index); + } + int mesh_get_surface_count(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + return m->surfaces.size(); + } void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) {} AABB mesh_get_custom_aabb(RID p_mesh) const { return AABB(); } @@ -598,7 +699,14 @@ public: RID canvas_light_occluder_create() { return RID(); } void canvas_light_occluder_set_polylines(RID p_occluder, const PoolVector<Vector2> &p_lines) {} - VS::InstanceType get_base_type(RID p_rid) const { return VS::INSTANCE_NONE; } + VS::InstanceType get_base_type(RID p_rid) const { + if (mesh_owner.owns(p_rid)) { + return VS::INSTANCE_MESH; + } + + return VS::INSTANCE_NONE; + } + bool free(RID p_rid) { if (texture_owner.owns(p_rid)) { diff --git a/drivers/dummy/texture_loader_dummy.cpp b/drivers/dummy/texture_loader_dummy.cpp new file mode 100644 index 0000000000..6d3e176bbb --- /dev/null +++ b/drivers/dummy/texture_loader_dummy.cpp @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* texture_loader_dummy.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 "texture_loader_dummy.h" +#include "core/os/file_access.h" +#include "print_string.h" +#include <string.h> + +RES ResourceFormatDummyTexture::load(const String &p_path, const String &p_original_path, Error *r_error) { + unsigned int width = 8; + unsigned int height = 8; + + //We just use some format + Image::Format fmt = Image::FORMAT_RGB8; + int rowsize = 3 * width; + + PoolVector<uint8_t> dstbuff; + + dstbuff.resize(rowsize * height); + + PoolVector<uint8_t>::Write dstbuff_write = dstbuff.write(); + + uint8_t *data = dstbuff_write.ptr(); + + uint8_t **row_p = memnew_arr(uint8_t *, height); + + for (unsigned int i = 0; i < height; i++) { + row_p[i] = 0; //No colors any more, I want them to turn black + } + + memdelete_arr(row_p); + + Ref<Image> img = memnew(Image(width, height, 0, fmt, dstbuff)); + + Ref<ImageTexture> texture = memnew(ImageTexture); + texture->create_from_image(img); + + if (r_error) + *r_error = OK; + + return texture; +} + +void ResourceFormatDummyTexture::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("png"); + p_extensions->push_back("hdr"); + p_extensions->push_back("jpg"); + p_extensions->push_back("tga"); +} + +bool ResourceFormatDummyTexture::handles_type(const String &p_type) const { + return ClassDB::is_parent_class(p_type, "Texture"); +} + +String ResourceFormatDummyTexture::get_resource_type(const String &p_path) const { + String extension = p_path.get_extension().to_lower(); + if (extension == "png" || extension == "hdr" || extension == "jpg" || extension == "tga") + return "ImageTexture"; + return ""; +} diff --git a/drivers/dummy/texture_loader_dummy.h b/drivers/dummy/texture_loader_dummy.h new file mode 100644 index 0000000000..f0a355ec12 --- /dev/null +++ b/drivers/dummy/texture_loader_dummy.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* texture_loader_dummy.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 TEXTURE_LOADER_DUMMY_H +#define TEXTURE_LOADER_DUMMY_H + +#include "core/io/resource_loader.h" +#include "scene/resources/texture.h" + +class ResourceFormatDummyTexture : public ResourceFormatLoader { +public: + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; + + virtual ~ResourceFormatDummyTexture() {} +}; + +#endif // TEXTURE_LOADER_DUMMY_H diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 1e34d63b13..05dfd69f58 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -244,7 +244,7 @@ OS::TimeZoneInfo OS_Unix::get_time_zone_info() const { void OS_Unix::delay_usec(uint32_t p_usec) const { - struct timespec rem = { p_usec / 1000000, (p_usec % 1000000) * 1000 }; + struct timespec rem = { static_cast<time_t>(p_usec / 1000000), static_cast<long>((p_usec % 1000000) * 1000) }; while (nanosleep(&rem, &rem) == EINTR) { } } diff --git a/editor/animation_editor.cpp b/editor/animation_editor.cpp index f7c8cac93f..a03bf76d1b 100644 --- a/editor/animation_editor.cpp +++ b/editor/animation_editor.cpp @@ -2969,6 +2969,7 @@ void AnimationKeyEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { + update_keying(); EditorNode::get_singleton()->update_keying(); emit_signal("keying_changed"); } break; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 24e86770bf..93c86d920a 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -779,6 +779,7 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_draw_breakpoint_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/show_breakpoint_gutter")); text_editor->set_hiding_enabled(EditorSettings::get_singleton()->get("text_editor/line_numbers/code_folding")); text_editor->set_draw_fold_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/code_folding")); + text_editor->set_wrap_enabled(EditorSettings::get_singleton()->get("text_editor/line_numbers/word_wrap")); text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret")); text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/open_scripts/smooth_scrolling")); text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/open_scripts/v_scroll_speed")); diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 953d787322..c4a17d5402 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -504,25 +504,25 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector< void DependencyRemoveDialog::ok_pressed() { + for (int i = 0; i < files_to_delete.size(); ++i) { + if (ResourceCache::has(files_to_delete[i])) { + Resource *res = ResourceCache::get(files_to_delete[i]); + res->set_path(""); + } + String path = OS::get_singleton()->get_resource_dir() + files_to_delete[i].replace_first("res://", "/"); + print_line("Moving to trash: " + path); + Error err = OS::get_singleton()->move_to_trash(path); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + files_to_delete[i] + "\n"); + } + } + if (dirs_to_delete.size() == 0) { //If we only deleted files we should only need to tell the file system about the files we touched. for (int i = 0; i < files_to_delete.size(); ++i) EditorFileSystem::get_singleton()->update_file(files_to_delete[i]); } else { - for (int i = 0; i < files_to_delete.size(); ++i) { - if (ResourceCache::has(files_to_delete[i])) { - Resource *res = ResourceCache::get(files_to_delete[i]); - res->set_path(""); - } - String path = OS::get_singleton()->get_resource_dir() + files_to_delete[i].replace_first("res://", "/"); - print_line("Moving to trash: " + path); - Error err = OS::get_singleton()->move_to_trash(path); - if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + files_to_delete[i] + "\n"); - } - } - for (int i = 0; i < dirs_to_delete.size(); ++i) { String path = OS::get_singleton()->get_resource_dir() + dirs_to_delete[i].replace_first("res://", "/"); print_line("Moving to trash: " + path); diff --git a/editor/doc/doc_data.cpp b/editor/doc/doc_data.cpp index 3434aa33f9..c992ac5f16 100644 --- a/editor/doc/doc_data.cpp +++ b/editor/doc/doc_data.cpp @@ -567,6 +567,9 @@ void DocData::generate(bool p_basic_types) { PropertyDoc pd; Engine::Singleton &s = E->get(); + if (!s.ptr) { + continue; + } pd.name = s.name; pd.type = s.ptr->get_class(); while (String(ClassDB::get_parent_class(pd.type)) != "Object") diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index a2f5c1aa1a..708bff252a 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -33,6 +33,8 @@ #include "editor_node.h" #include "global_constants.h" #include "project_settings.h" +#include "scene/main/viewport.h" +#include "scene/resources/packed_scene.h" #define PREVIEW_LIST_MAX_SIZE 10 @@ -155,8 +157,8 @@ void EditorAutoloadSettings::_autoload_edited() { undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", selected_autoload, order); undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name); - undo_redo->add_do_method(this, "update_autoload"); - undo_redo->add_undo_method(this, "update_autoload"); + undo_redo->add_do_method(this, "call_deferred", "update_autoload"); + undo_redo->add_undo_method(this, "call_deferred", "update_autoload"); undo_redo->add_do_method(this, "emit_signal", autoload_changed); undo_redo->add_undo_method(this, "emit_signal", autoload_changed); @@ -187,8 +189,8 @@ void EditorAutoloadSettings::_autoload_edited() { undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", base, order); undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", base, order); - undo_redo->add_do_method(this, "update_autoload"); - undo_redo->add_undo_method(this, "update_autoload"); + undo_redo->add_do_method(this, "call_deferred", "update_autoload"); + undo_redo->add_undo_method(this, "call_deferred", "update_autoload"); undo_redo->add_do_method(this, "emit_signal", autoload_changed); undo_redo->add_undo_method(this, "emit_signal", autoload_changed); @@ -296,6 +298,18 @@ void EditorAutoloadSettings::update_autoload() { updating_autoload = true; + Map<String, AutoLoadInfo> to_remove; + Map<String, AutoLoadInfo> to_remove_singleton; + List<AutoLoadInfo> to_add; + List<String> to_add_singleton; // Only for when the node is still the same + + for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) { + to_remove.insert(E->get().name, E->get()); + if (E->get().is_singleton) { + to_remove_singleton.insert(E->get().name, E->get()); + } + } + autoload_cache.clear(); tree->clear(); @@ -317,19 +331,44 @@ void EditorAutoloadSettings::update_autoload() { if (name.empty()) continue; + AutoLoadInfo old_info; + if (to_remove.has(name)) { + old_info = to_remove[name]; + } + AutoLoadInfo info; - info.name = pi.name; - info.order = ProjectSettings::get_singleton()->get_order(pi.name); + info.is_singleton = path.begins_with("*"); - autoload_cache.push_back(info); + if (info.is_singleton) { + path = path.substr(1, path.length()); + } - bool global = false; + info.name = name; + info.path = path; + info.order = ProjectSettings::get_singleton()->get_order(pi.name); - if (path.begins_with("*")) { - global = true; - path = path.substr(1, path.length()); + if (old_info.name == info.name) { + if (old_info.path == info.path) { + // Still the same resource, check singleton status + to_remove.erase(name); + if (info.is_singleton) { + if (old_info.is_singleton) { + to_remove_singleton.erase(name); + } else { + to_add_singleton.push_back(name); + } + } + } else { + // Resource changed + to_add.push_back(info); + } + } else { + // New autoload + to_add.push_back(info); } + autoload_cache.push_back(info); + TreeItem *item = tree->create_item(root); item->set_text(0, name); item->set_editable(0, true); @@ -340,7 +379,7 @@ void EditorAutoloadSettings::update_autoload() { item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK); item->set_editable(2, true); item->set_text(2, TTR("Enable")); - item->set_checked(2, global); + item->set_checked(2, info.is_singleton); item->add_button(3, get_icon("FileList", "EditorIcons"), BUTTON_OPEN); item->add_button(3, get_icon("MoveUp", "EditorIcons"), BUTTON_MOVE_UP); item->add_button(3, get_icon("MoveDown", "EditorIcons"), BUTTON_MOVE_DOWN); @@ -348,6 +387,77 @@ void EditorAutoloadSettings::update_autoload() { item->set_selectable(3, false); } + // Remove autoload constants + for (Map<String, AutoLoadInfo>::Element *E = to_remove_singleton.front(); E; E = E->next()) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->remove_named_global_constant(E->get().name); + } + } + + // Remove obsolete nodes from the tree + for (Map<String, AutoLoadInfo>::Element *E = to_remove.front(); E; E = E->next()) { + AutoLoadInfo &info = E->get(); + Node *al = get_node("/root/" + info.name); + ERR_CONTINUE(!al); + get_tree()->get_root()->remove_child(al); + memdelete(al); + } + + // Register new singletons already in the tree + for (List<String>::Element *E = to_add_singleton.front(); E; E = E->next()) { + Node *al = get_node("/root/" + E->get()); + ERR_CONTINUE(!al); + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(E->get(), al); + } + } + + // Add new nodes to the tree + List<Node *> nodes_to_add; + for (List<AutoLoadInfo>::Element *E = to_add.front(); E; E = E->next()) { + AutoLoadInfo &info = E->get(); + + RES res = ResourceLoader::load(info.path); + ERR_EXPLAIN("Can't autoload: " + info.path); + ERR_CONTINUE(res.is_null()); + Node *n = NULL; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> s = res; + StringName ibt = s->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_EXPLAIN("Script does not inherit a Node: " + info.path); + ERR_CONTINUE(!valid_type); + + Object *obj = ClassDB::instance(ibt); + + ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); + ERR_CONTINUE(obj == NULL); + + n = Object::cast_to<Node>(obj); + n->set_script(s.get_ref_ptr()); + } + + ERR_EXPLAIN("Path in autoload not a node or script: " + info.path); + ERR_CONTINUE(!n); + n->set_name(info.name); + + //defer so references are all valid on _ready() + nodes_to_add.push_back(n); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(info.name, n); + } + } + } + + for (List<Node *>::Element *E = nodes_to_add.front(); E; E = E->next()) { + get_tree()->get_root()->add_child(E->get()); + } + updating_autoload = false; } @@ -592,6 +702,36 @@ void EditorAutoloadSettings::_bind_methods() { EditorAutoloadSettings::EditorAutoloadSettings() { + // Make first cache + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + const PropertyInfo &pi = E->get(); + + if (!pi.name.begins_with("autoload/")) + continue; + + String name = pi.name.get_slice("/", 1); + String path = ProjectSettings::get_singleton()->get(pi.name); + + if (name.empty()) + continue; + + AutoLoadInfo info; + info.is_singleton = path.begins_with("*"); + + if (info.is_singleton) { + path = path.substr(1, path.length()); + } + + info.name = name; + info.path = path; + info.order = ProjectSettings::get_singleton()->get_order(pi.name); + + autoload_cache.push_back(info); + } + autoload_changed = "autoload_changed"; updating_autoload = false; diff --git a/editor/editor_autoload_settings.h b/editor/editor_autoload_settings.h index 6f622de6d5..1797c10e61 100644 --- a/editor/editor_autoload_settings.h +++ b/editor/editor_autoload_settings.h @@ -50,6 +50,8 @@ class EditorAutoloadSettings : public VBoxContainer { struct AutoLoadInfo { String name; + String path; + bool is_singleton; int order; bool operator==(const AutoLoadInfo &p_info) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp new file mode 100644 index 0000000000..21ad71fe06 --- /dev/null +++ b/editor/editor_inspector.cpp @@ -0,0 +1,1797 @@ +#include "editor_inspector.h" +#include "array_property_edit.h" +#include "dictionary_property_edit.h" +#include "editor_node.h" +#include "editor_scale.h" +#include "multi_node_edit.h" +#include "scene/resources/packed_scene.h" + +// TODO: +// arrays + +Size2 EditorProperty::get_minimum_size() const { + + Size2 ms; + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible()) + continue; + Size2 minsize = c->get_combined_minimum_size(); + ms.width = MAX(ms.width, minsize.width); + ms.height = MAX(ms.height, minsize.height); + } + + if (keying) { + Ref<Texture> key = get_icon("Key", "EditorIcons"); + ms.width += key->get_width() + get_constant("hseparator", "Tree"); + } + + if (checkable) { + Ref<Texture> check = get_icon("checked", "CheckBox"); + ms.width += check->get_width() + get_constant("hseparator", "Tree"); + } + + if (label_layout == LABEL_LAYOUT_TOP) { + Ref<Font> font = get_font("font", "Tree"); + ms.height += font->get_height(); + ms.height += get_constant("vseparation", "Tree"); + } + + return ms; +} + +void EditorProperty::_notification(int p_what) { + + if (p_what == NOTIFICATION_SORT_CHILDREN) { + + Size2 size = get_size(); + Rect2 rect; + + if (label_layout == LABEL_LAYOUT_LEFT) { + int child_room = size.width / 2; + + //compute room needed + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + + Size2 minsize = c->get_combined_minimum_size(); + child_room = MAX(child_room, minsize.width); + } + + text_size = MAX(0, size.width - child_room + 4 * EDSCALE); + + rect = Rect2(text_size, 0, size.width - text_size, size.height); + } else { + Ref<Font> font = get_font("font", "Tree"); + + text_size = size.width; + rect.position.x = 0; + rect.position.y = font->get_height() + get_constant("vseparation", "Tree"); + rect.size = get_size(); + rect.size.height -= rect.position.y; + } + + if (keying) { + Ref<Texture> key; + + if (use_keying_next()) { + key = get_icon("KeyNext", "EditorIcons"); + } else { + key = get_icon("Key", "EditorIcons"); + } + + rect.size.x -= key->get_width() + get_constant("hseparator", "Tree"); + } + + //set children + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + + fit_child_in_rect(c, rect); + } + + update(); //need to redraw text + } + + if (p_what == NOTIFICATION_DRAW) { + Ref<Font> font = get_font("font", "Tree"); + + Size2 size = get_size(); + if (label_layout == LABEL_LAYOUT_TOP) { + size.height = font->get_height(); + } else if (label_reference) { + size.height = label_reference->get_size().height; + } + + if (selected) { + Ref<StyleBox> sb = get_stylebox("selected", "Tree"); + draw_style_box(sb, Rect2(Vector2(), size)); + } + + Color color; + if (draw_red) { + color = get_color("error_color", "Editor"); + } else { + color = get_color("font_color", "Tree"); + } + if (label.find(".") != -1) { + color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides + } + + int ofs = 0; + if (checkable) { + Ref<Texture> checkbox; + if (checked) + checkbox = get_icon("checked", "CheckBox"); + else + checkbox = get_icon("unchecked", "CheckBox"); + + Color color(1, 1, 1); + if (check_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + check_rect = Rect2(ofs, ((size.height - checkbox->get_height()) / 2), checkbox->get_width(), checkbox->get_height()); + draw_texture(checkbox, check_rect.position, color); + ofs += get_constant("hseparator", "Tree"); + ofs += checkbox->get_width(); + } else { + check_rect = Rect2(); + } + + int text_limit = text_size; + + if (can_revert) { + Ref<Texture> reload_icon = get_icon("ReloadSmall", "EditorIcons"); + text_limit -= reload_icon->get_width() + get_constant("hseparator", "Tree") * 2; + revert_rect = Rect2(text_limit + get_constant("hseparator", "Tree"), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); + + Color color(1, 1, 1); + if (revert_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + + draw_texture(reload_icon, revert_rect.position, color); + } else { + revert_rect = Rect2(); + } + + int v_ofs = (size.height - font->get_height()) / 2; + draw_string(font, Point2(ofs, v_ofs + font->get_ascent()), label, color, text_limit); + + if (keying) { + Ref<Texture> key; + + if (use_keying_next()) { + key = get_icon("KeyNext", "EditorIcons"); + } else { + key = get_icon("Key", "EditorIcons"); + } + + ofs = size.width - key->get_width() - get_constant("hseparator", "Tree"); + + Color color(1, 1, 1); + if (keying_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height()); + draw_texture(key, keying_rect.position, color); + } else { + keying_rect = Rect2(); + } + + //int vs = get_constant("vseparation", "Tree"); + Color guide_color = get_color("guide_color", "Tree"); + int vs_height = get_size().height; // vs / 2; + draw_line(Point2(0, vs_height), Point2(get_size().width, vs_height), guide_color); + } +} + +void EditorProperty::set_label(const String &p_label) { + label = p_label; + update(); +} + +String EditorProperty::get_label() const { + return label; +} + +Object *EditorProperty::get_edited_object() { + return object; +} + +StringName EditorProperty::get_edited_property() { + return property; +} + +void EditorProperty::update_property() { + if (get_script_instance()) + get_script_instance()->call("update_property"); +} + +void EditorProperty::set_read_only(bool p_read_only) { + read_only = p_read_only; +} + +bool EditorProperty::is_read_only() const { + return read_only; +} + +bool EditorProperty::_might_be_in_instance() { + + if (!object) + return false; + + Node *node = Object::cast_to<Node>(object); + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + bool might_be = false; + + while (node) { + + if (node->get_scene_instance_state().is_valid()) { + might_be = true; + break; + } + if (node == edited_scene) { + if (node->get_scene_inherited_state().is_valid()) { + might_be = true; + break; + } + might_be = false; + break; + } + node = node->get_owner(); + } + + return might_be; // or might not be +} + +bool EditorProperty::_get_instanced_node_original_property(const StringName &p_prop, Variant &value) { + + Node *node = Object::cast_to<Node>(object); + + if (!node) + return false; + + Node *orig = node; + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + bool found = false; + + while (node) { + + Ref<SceneState> ss; + + if (node == edited_scene) { + ss = node->get_scene_inherited_state(); + + } else { + ss = node->get_scene_instance_state(); + } + + if (ss.is_valid()) { + + NodePath np = node->get_path_to(orig); + int node_idx = ss->find_node_by_path(np); + if (node_idx >= 0) { + bool lfound = false; + Variant lvar; + lvar = ss->get_property_value(node_idx, p_prop, lfound); + if (lfound) { + + found = true; + value = lvar; + } + } + } + if (node == edited_scene) { + //just in case + break; + } + node = node->get_owner(); + } + + return found; +} + +bool EditorProperty::_is_property_different(const Variant &p_current, const Variant &p_orig, int p_usage) { + + // this is a pretty difficult function, because a property may not be saved but may have + // the flag to not save if one or if zero + + { + Node *node = Object::cast_to<Node>(object); + if (!node) + return false; + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + bool found_state = false; + + while (node) { + + Ref<SceneState> ss; + + if (node == edited_scene) { + ss = node->get_scene_inherited_state(); + + } else { + ss = node->get_scene_instance_state(); + } + + if (ss.is_valid()) { + found_state = true; + } + if (node == edited_scene) { + //just in case + break; + } + node = node->get_owner(); + } + + if (!found_state) + return false; //pointless to check if we are not comparing against anything. + } + + if (p_orig.get_type() == Variant::NIL) { + // not found (was not saved) + // check if it was not saved due to being zero or one + if (p_current.is_zero() && property_usage & PROPERTY_USAGE_STORE_IF_NONZERO) + return false; + if (p_current.is_one() && property_usage & PROPERTY_USAGE_STORE_IF_NONONE) + return false; + } + + if (p_current.get_type() == Variant::REAL && p_orig.get_type() == Variant::REAL) { + float a = p_current; + float b = p_orig; + + return Math::abs(a - b) > CMP_EPSILON; //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error + } + + return bool(Variant::evaluate(Variant::OP_NOT_EQUAL, p_current, p_orig)); +} + +bool EditorProperty::_is_instanced_node_with_original_property_different() { + + bool mbi = _might_be_in_instance(); + if (mbi) { + Variant vorig; + int usage = property_usage & (PROPERTY_USAGE_STORE_IF_NONONE | PROPERTY_USAGE_STORE_IF_NONZERO); + if (_get_instanced_node_original_property(property, vorig) || usage) { + Variant v = object->get(property); + + if (_is_property_different(v, vorig, usage)) { + return true; + } + } + } + return false; +} + +void EditorProperty::update_reload_status() { + + if (property == StringName()) + return; //no property, so nothing to do + + bool has_reload = false; + + if (_is_instanced_node_with_original_property_different()) { + has_reload = true; + } + + if (object->call("property_can_revert", property).operator bool()) { + + has_reload = true; + } + + if (!has_reload && !object->get_script().is_null()) { + Ref<Script> scr = object->get_script(); + Variant orig_value; + if (scr->get_property_default_value(property, orig_value)) { + if (orig_value != object->get(property)) { + has_reload = true; + } + } + } + + if (has_reload != can_revert) { + can_revert = has_reload; + update(); + } +} + +bool EditorProperty::use_keying_next() const { + return false; +} +void EditorProperty::set_checkable(bool p_checkable) { + + checkable = p_checkable; + update(); + queue_sort(); +} + +bool EditorProperty::is_checkable() const { + + return checkable; +} + +void EditorProperty::set_checked(bool p_checked) { + + checked = p_checked; + update(); +} + +bool EditorProperty::is_checked() const { + + return checked; +} + +void EditorProperty::set_draw_red(bool p_draw_red) { + + draw_red = p_draw_red; + update(); +} + +void EditorProperty::set_keying(bool p_keying) { + keying = p_keying; + update(); + queue_sort(); +} + +bool EditorProperty::is_keying() const { + return keying; +} + +bool EditorProperty::is_draw_red() const { + + return draw_red; +} + +void EditorProperty::_focusable_focused(int p_index) { + + bool already_selected = selected; + selected = true; + selected_focusable = p_index; + update(); + if (!already_selected && selected) { + emit_signal("selected", property, selected_focusable); + } +} + +void EditorProperty::add_focusable(Control *p_control) { + + p_control->connect("focus_entered", this, "_focusable_focused", varray(focusables.size())); + focusables.push_back(p_control); +} + +void EditorProperty::select(int p_focusable) { + + bool already_selected = selected; + + if (p_focusable >= 0) { + ERR_FAIL_INDEX(p_focusable, focusables.size()); + focusables[p_focusable]->grab_focus(); + } else { + selected = true; + update(); + } + + if (!already_selected && selected) { + emit_signal("selected", property, selected_focusable); + } +} + +void EditorProperty::deselect() { + selected = false; + selected_focusable = -1; + update(); +} + +bool EditorProperty::is_selected() const { + return selected; +} + +void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) { + + if (property == StringName()) + return; + + Ref<InputEventMouse> me = p_event; + + if (me.is_valid()) { + + bool button_left = me->get_button_mask() & BUTTON_MASK_LEFT; + + bool new_keying_hover = keying_rect.has_point(me->get_position()) && !button_left; + if (new_keying_hover != keying_hover) { + keying_hover = new_keying_hover; + update(); + } + + bool new_revert_hover = revert_rect.has_point(me->get_position()) && !button_left; + if (new_revert_hover != revert_hover) { + revert_hover = new_revert_hover; + update(); + } + + bool new_check_hover = check_rect.has_point(me->get_position()) && !button_left; + if (new_check_hover != check_hover) { + check_hover = new_check_hover; + update(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (!selected) { + selected = true; + emit_signal("selected", property, -1); + update(); + } + + if (keying_rect.has_point(mb->get_position())) { + emit_signal("property_keyed", property); + } + + if (revert_rect.has_point(mb->get_position())) { + + Variant vorig; + + if (_might_be_in_instance() && _get_instanced_node_original_property(property, vorig)) { + + emit_signal("property_changed", property, vorig.duplicate(true)); + update_property(); + return; + } + + if (object->call("property_can_revert", property).operator bool()) { + Variant rev = object->call("property_get_revert", property); + emit_signal("property_changed", property, rev); + update_property(); + } + + if (!object->get_script().is_null()) { + Ref<Script> scr = object->get_script(); + Variant orig_value; + if (scr->get_property_default_value(property, orig_value)) { + emit_signal("property_changed", property, orig_value); + update_property(); + } + } + } + if (check_rect.has_point(mb->get_position())) { + checked = !checked; + update(); + emit_signal("property_checked", property, checked); + } + } +} + +void EditorProperty::set_label_reference(Control *p_control) { + + label_reference = p_control; +} + +Variant EditorProperty::get_drag_data(const Point2 &p_point) { + + if (property == StringName()) + return Variant(); + + Dictionary dp; + dp["type"] = "obj_property"; + dp["object"] = object; + dp["property"] = property; + dp["value"] = object->get(property); + + Label *label = memnew(Label); + label->set_text(property); + set_drag_preview(label); + return dp; +} + +void EditorProperty::set_label_layout(LabelLayout p_layout) { + label_layout = p_layout; + queue_sort(); + update(); +} + +void EditorProperty::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label); + ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label); + + ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only); + ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only); + + ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable); + ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable); + + ClassDB::bind_method(D_METHOD("set_checked", "checked"), &EditorProperty::set_checked); + ClassDB::bind_method(D_METHOD("is_checked"), &EditorProperty::is_checked); + + ClassDB::bind_method(D_METHOD("set_draw_red", "draw_red"), &EditorProperty::set_draw_red); + ClassDB::bind_method(D_METHOD("is_draw_red"), &EditorProperty::is_draw_red); + + ClassDB::bind_method(D_METHOD("set_keying", "keying"), &EditorProperty::set_keying); + ClassDB::bind_method(D_METHOD("is_keying"), &EditorProperty::is_keying); + + ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property); + ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object); + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorProperty::_gui_input); + ClassDB::bind_method(D_METHOD("_focusable_focused"), &EditorProperty::_focusable_focused); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_red"), "set_draw_red", "is_draw_red"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying"); + ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); + ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::POOL_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value"))); + ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::STRING, "bool"))); + ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); + ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx"))); + + MethodInfo vm; + vm.name = "update_property"; + BIND_VMETHOD(vm); +} + +EditorProperty::EditorProperty() { + + text_size = 0; + read_only = false; + checkable = false; + checked = false; + draw_red = false; + keying = false; + keying_hover = false; + revert_hover = false; + check_hover = false; + can_revert = false; + property_usage = 0; + selected = false; + selected_focusable = -1; + label_reference = NULL; + label_layout = LABEL_LAYOUT_LEFT; +} +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorPlugin::add_custom_control(Control *control) { + + AddedEditor ae; + ae.property_editor = control; + added_editors.push_back(ae); +} + +void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop) { + + ERR_FAIL_COND(Object::cast_to<EditorProperty>(p_prop) == NULL); + + AddedEditor ae; + ae.properties.push_back(p_for_property); + ae.property_editor = p_prop; + added_editors.push_back(ae); +} + +void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop) { + + AddedEditor ae; + ae.properties = p_properties; + ae.property_editor = p_prop; + ae.label = p_label; + added_editors.push_back(ae); +} + +bool EditorInspectorPlugin::can_handle(Object *p_object) { + + if (get_script_instance()) { + return get_script_instance()->call("can_handle", p_object); + } + return false; +} +void EditorInspectorPlugin::parse_begin(Object *p_object) { + + if (get_script_instance()) { + get_script_instance()->call("parse_begin", p_object); + } +} +bool EditorInspectorPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + if (get_script_instance()) { + Variant arg[6] = { + p_object, p_type, p_path, p_hint, p_hint_text, p_usage + }; + const Variant *argptr[6] = { + &arg[0], &arg[1], &arg[2], &arg[3], &arg[4], &arg[5] + }; + + Variant::CallError err; + return get_script_instance()->call("parse_property", (const Variant **)&argptr, 6, err); + } + return false; +} +void EditorInspectorPlugin::parse_end() { + + if (get_script_instance()) { + get_script_instance()->call("parse_end"); + } +} + +void EditorInspectorPlugin::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_custom_control", "control"), &EditorInspectorPlugin::add_custom_control); + ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor"), &EditorInspectorPlugin::add_property_editor); + ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties); + + MethodInfo vm; + vm.name = "can_handle"; + vm.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); + BIND_VMETHOD(vm); + vm.name = "parse_begin"; + BIND_VMETHOD(vm); + vm.name = "parse_property"; + vm.return_val.type = Variant::BOOL; + vm.arguments.push_back(PropertyInfo(Variant::INT, "type")); + vm.arguments.push_back(PropertyInfo(Variant::STRING, "path")); + vm.arguments.push_back(PropertyInfo(Variant::INT, "hint")); + vm.arguments.push_back(PropertyInfo(Variant::STRING, "hint_text")); + vm.arguments.push_back(PropertyInfo(Variant::INT, "usage")); + BIND_VMETHOD(vm); + vm.arguments.clear(); + vm.return_val.type = Variant::NIL; + vm.name = "parse_end"; + BIND_VMETHOD(vm); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorCategory::_notification(int p_what) { + + if (p_what == NOTIFICATION_DRAW) { + + draw_rect(Rect2(Vector2(), get_size()), bg_color); + Ref<Font> font = get_font("font", "Tree"); + + int hs = get_constant("hseparation", "Tree"); + + int w = font->get_string_size(label).width; + if (icon.is_valid()) { + w += hs + icon->get_width(); + } + + int ofs = (get_size().width - w) / 2; + + if (icon.is_valid()) { + draw_texture(icon, Point2(ofs, (get_size().height - icon->get_height()) / 2).floor()); + ofs += hs + icon->get_width(); + } + + Color color = get_color("font_color", "Tree"); + draw_string(font, Point2(ofs, font->get_ascent() + (get_size().height - font->get_height()) / 2).floor(), label, color, get_size().width); + } +} + +Size2 EditorInspectorCategory::get_minimum_size() const { + + Ref<Font> font = get_font("font", "Tree"); + + Size2 ms; + ms.width = 1; + ms.height = font->get_height(); + if (icon.is_valid()) { + ms.height = MAX(icon->get_height(), ms.height); + } + ms.height += get_constant("vseparation", "Tree"); + + return ms; +} + +EditorInspectorCategory::EditorInspectorCategory() { +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorSection::_notification(int p_what) { + + if (p_what == NOTIFICATION_SORT_CHILDREN) { + + Ref<Font> font = get_font("font", "Tree"); + Ref<Texture> arrow; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); + } + } +#endif + + Size2 size = get_size(); + Point2 offset; + offset.y = font->get_height(); + if (arrow.is_valid()) { + offset.y = MAX(offset.y, arrow->get_height()); + } + + offset.y += get_constant("vseparation", "Tree"); + offset.x += get_constant("item_margin", "Tree"); + + Rect2 rect(offset, size - offset); + + //set children + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible_in_tree()) + continue; + + fit_child_in_rect(c, rect); + } + + update(); //need to redraw text + } + + if (p_what == NOTIFICATION_DRAW) { + + Ref<Texture> arrow; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); + } + } +#endif + + Ref<Font> font = get_font("font", "Tree"); + + int h = font->get_height(); + if (arrow.is_valid()) { + h = MAX(h, arrow->get_height()); + } + h += get_constant("vseparation", "Tree"); + + draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), bg_color); + + int hs = get_constant("hseparation", "Tree"); + + int ofs = 0; + if (arrow.is_valid()) { + draw_texture(arrow, Point2(ofs, (h - arrow->get_height()) / 2).floor()); + ofs += hs + arrow->get_width(); + } + + Color color = get_color("font_color", "Tree"); + draw_string(font, Point2(ofs, font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); + } +} + +Size2 EditorInspectorSection::get_minimum_size() const { + + Size2 ms; + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible()) + continue; + Size2 minsize = c->get_combined_minimum_size(); + ms.width = MAX(ms.width, minsize.width); + ms.height = MAX(ms.height, minsize.height); + } + + Ref<Font> font = get_font("font", "Tree"); + ms.height += font->get_ascent() + get_constant("vseparation", "Tree"); + ms.width += get_constant("item_margin", "Tree"); + + return ms; +} + +void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable) { + + section = p_section; + label = p_label; + object = p_object; + bg_color = p_bg_color; + foldable = p_foldable; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + vbox->show(); + } else { + vbox->hide(); + } + } + // void editor_set_section_unfold(const String &p_section, bool p_unfolded); + +#endif +} + +void EditorInspectorSection::_gui_input(const Ref<InputEvent> &p_event) { + + if (!foldable) + return; + +#ifdef TOOLS_ENABLED + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + bool unfold = !object->editor_is_section_unfolded(section); + object->editor_set_section_unfold(section, unfold); + if (unfold) { + vbox->show(); + } else { + vbox->hide(); + } + } +#endif +} + +VBoxContainer *EditorInspectorSection::get_vbox() { + return vbox; +} + +void EditorInspectorSection::unfold() { + + if (!foldable) + return; +#ifdef TOOLS_ENABLED + + object->editor_set_section_unfold(section, true); + vbox->show(); + update(); +#endif +} + +void EditorInspectorSection::fold() { + if (!foldable) + return; + +#ifdef TOOLS_ENABLED + + object->editor_set_section_unfold(section, false); + vbox->hide(); + update(); +#endif +} + +void EditorInspectorSection::_bind_methods() { + + ClassDB::bind_method(D_METHOD("setup", "section", "label", "object", "bg_color", "foldable"), &EditorInspectorSection::setup); + ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox); + ClassDB::bind_method(D_METHOD("unfold"), &EditorInspectorSection::unfold); + ClassDB::bind_method(D_METHOD("fold"), &EditorInspectorSection::fold); + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorInspectorSection::_gui_input); +} + +EditorInspectorSection::EditorInspectorSection() { + object = NULL; + foldable = false; + vbox = memnew(VBoxContainer); + add_child(vbox); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; +int EditorInspector::inspector_plugin_count = 0; + +void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { + + ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); + + for (int i = 0; i < inspector_plugin_count; i++) { + if (inspector_plugins[i] == p_plugin) + return; //already exists + } + inspector_plugins[inspector_plugin_count++] = p_plugin; +} + +void EditorInspector::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { + + ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); + + int idx = -1; + for (int i = 0; i < inspector_plugin_count; i++) { + if (inspector_plugins[i] == p_plugin) { + idx = i; + break; + } + } + + for (int i = idx; i < inspector_plugin_count - 1; i++) { + inspector_plugins[i] = inspector_plugins[i + 1]; + } + inspector_plugin_count--; +} + +void EditorInspector::cleanup_plugins() { + for (int i = 0; i < inspector_plugin_count; i++) { + inspector_plugins[i].unref(); + } + inspector_plugin_count = 0; +} + +void EditorInspector::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +String EditorInspector::get_selected_path() const { + + return property_selected; +} + +void EditorInspector::update_tree() { + + //to update properly if all is refreshed + StringName current_selected = property_selected; + int current_focusable = property_focusable; + + _clear(); + + if (!object) + return; + + List<Ref<EditorInspectorPlugin> > valid_plugins; + + for (int i = inspector_plugin_count - 1; i >= 0; i--) { //start by last, so lastly added can override newly added + if (!inspector_plugins[i]->can_handle(object)) + continue; + valid_plugins.push_back(inspector_plugins[i]); + } + + bool draw_red = false; + + { + Node *nod = Object::cast_to<Node>(object); + Node *es = EditorNode::get_singleton()->get_edited_scene(); + if (nod && es != nod && nod->get_owner() != es) { + draw_red = true; + } + } + + // TreeItem *current_category = NULL; + + String filter = search_box ? search_box->get_text() : ""; + String group; + String group_base; + + List<PropertyInfo> plist; + object->get_property_list(&plist, true); + + HashMap<String, VBoxContainer *> item_path; + item_path[""] = main_vbox; + + Color sscolor = get_color("prop_subsection", "Editor"); + + for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { + + PropertyInfo &p = I->get(); + + //make sure the property can be edited + + if (p.usage & PROPERTY_USAGE_GROUP) { + + group = p.name; + group_base = p.hint_string; + + continue; + + } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + + group = ""; + group_base = ""; + + if (!show_categories) + continue; + + List<PropertyInfo>::Element *N = I->next(); + bool valid = true; + //if no properties in category, skip + while (N) { + if (N->get().usage & PROPERTY_USAGE_EDITOR) + break; + if (N->get().usage & PROPERTY_USAGE_CATEGORY) { + valid = false; + break; + } + N = N->next(); + } + if (!valid) + continue; //empty, ignore + + EditorInspectorCategory *category = memnew(EditorInspectorCategory); + main_vbox->add_child(category); + + String type = p.name; + if (has_icon(type, "EditorIcons")) + category->icon = get_icon(type, "EditorIcons"); + else + category->icon = get_icon("Object", "EditorIcons"); + category->label = type; + + category->bg_color = get_color("prop_category", "Editor"); + if (use_doc_hints) { + StringName type = p.name; + if (!class_descr_cache.has(type)) { + + String descr; + DocData *dd = EditorHelp::get_doc_data(); + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(type); + if (E) { + descr = E->get().brief_description; + } + class_descr_cache[type] = descr.word_wrap(80); + } + + category->set_tooltip(TTR("Class:") + " " + p.name + (class_descr_cache[type] == "" ? "" : "\n\n" + class_descr_cache[type])); + } + continue; + + } else if (!(p.usage & PROPERTY_USAGE_EDITOR)) + continue; + + if (hide_script && p.name == "script") + continue; + + String basename = p.name; + if (group != "") { + if (group_base != "") { + if (basename.begins_with(group_base)) { + basename = basename.replace_first(group_base, ""); + } else if (group_base.begins_with(basename)) { + //keep it, this is used pretty often + } else { + group = ""; //no longer using group base, clear + } + } + } + + if (group != "") { + basename = group + "/" + basename; + } + + String name = (basename.find("/") != -1) ? basename.right(basename.find_last("/") + 1) : basename; + + if (capitalize_paths) { + int dot = name.find("."); + if (dot != -1) { + String ov = name.right(dot); + name = name.substr(0, dot); + name = name.camelcase_to_underscore().capitalize(); + name += ov; + + } else { + name = name.camelcase_to_underscore().capitalize(); + } + } + + String path = basename.left(basename.find_last("/")); + + if (use_filter && filter != "") { + + String cat = path; + + if (capitalize_paths) + cat = cat.capitalize(); + + if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name)) + continue; + } + + VBoxContainer *current_vbox = main_vbox; + + { + + String acc_path = ""; + int level = 1; + for (int i = 0; i < path.get_slice_count("/"); i++) { + String path_name = path.get_slice("/", i); + if (i > 0) + acc_path += "/"; + acc_path += path_name; + if (!item_path.has(acc_path)) { + EditorInspectorSection *section = memnew(EditorInspectorSection); + current_vbox->add_child(section); + sections.push_back(section); + + if (capitalize_paths) + path_name = path_name.capitalize(); + Color c = sscolor; + c.a /= level; + section->setup(path_name, acc_path, object, c, use_folding); + + item_path[acc_path] = section->get_vbox(); + } + current_vbox = item_path[acc_path]; + level = (MIN(level + 1, 4)); + } + } + + bool checkable = false; + bool checked = false; + if (p.usage & PROPERTY_USAGE_CHECKABLE) { + checkable = true; + checked = p.usage & PROPERTY_USAGE_CHECKED; + } + + String doc_hint; + + if (use_doc_hints) { + + StringName classname = object->get_class_name(); + StringName propname = p.name; + String descr; + bool found = false; + + Map<StringName, Map<StringName, String> >::Element *E = descr_cache.find(classname); + if (E) { + Map<StringName, String>::Element *F = E->get().find(propname); + if (F) { + found = true; + descr = F->get(); + } + } + + if (!found) { + DocData *dd = EditorHelp::get_doc_data(); + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(classname); + while (E && descr == String()) { + for (int i = 0; i < E->get().properties.size(); i++) { + if (E->get().properties[i].name == propname.operator String()) { + descr = E->get().properties[i].description.strip_edges().word_wrap(80); + break; + } + } + if (!E->get().inherits.empty()) { + E = dd->class_list.find(E->get().inherits); + } else { + break; + } + } + descr_cache[classname][propname] = descr; + } + + doc_hint = descr; + } + +#if 0 + if (p.name == selected_property) { + + item->select(1); + } +#endif + for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) { + Ref<EditorInspectorPlugin> ped = E->get(); + ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage); + for (List<EditorInspectorPlugin::AddedEditor>::Element *F = ped->added_editors.front(); F; F = F->next()) { + + EditorProperty *ep = Object::cast_to<EditorProperty>(F->get().property_editor); + current_vbox->add_child(F->get().property_editor); + + if (ep) { + + ep->object = object; + ep->connect("property_changed", this, "_property_changed"); + ep->connect("property_keyed", this, "_property_keyed"); + ep->connect("property_checked", this, "_property_checked"); + ep->connect("selected", this, "_property_selected"); + ep->connect("multiple_properties_changed", this, "_multiple_properties_changed"); + ep->connect("resource_selected", this, "_resource_selected", varray(), CONNECT_DEFERRED); + ep->connect("object_id_selected", this, "_object_id_selected", varray(), CONNECT_DEFERRED); + ep->set_tooltip(doc_hint); + ep->set_draw_red(draw_red); + ep->set_checkable(checkable); + ep->set_checked(checked); + ep->set_keying(keying); + + if (F->get().properties.size()) { + + if (F->get().properties.size() == 1) { + //since it's one, associate: + ep->property = F->get().properties[0]; + ep->property_usage = p.usage; + //and set label? + } + + if (F->get().label != String()) { + ep->set_label(F->get().label); + } else { + //use existin one + ep->set_label(name); + } + for (int i = 0; i < F->get().properties.size(); i++) { + String prop = F->get().properties[i]; + + if (!editor_property_map.has(prop)) { + editor_property_map[prop] = List<EditorProperty *>(); + } + editor_property_map[prop].push_back(ep); + } + } + + ep->set_read_only(read_only); + ep->update_property(); + ep->update_reload_status(); + + if (current_selected && ep->property == current_selected) { + ep->select(current_focusable); + } + } + } + ped->added_editors.clear(); + } + } + + //see if this property exists and should be kept +} +void EditorInspector::update_property(const String &p_prop) { + if (!editor_property_map.has(p_prop)) + return; + + for (List<EditorProperty *>::Element *E = editor_property_map[p_prop].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } +} + +void EditorInspector::_clear() { + + editor_property_map.clear(); + sections.clear(); + pending.clear(); + property_selected = StringName(); + property_focusable = -1; + while (main_vbox->get_child_count()) { + memdelete(main_vbox->get_child(0)); + } +} + +void EditorInspector::refresh() { + + if (refresh_countdown > 0) + return; + refresh_countdown = EditorSettings::get_singleton()->get("docks/property_editor/auto_refresh_interval"); +} + +void EditorInspector::edit(Object *p_object) { + if (object != p_object) { + _clear(); + } + + if (object) { + + object->remove_change_receptor(this); + } + + object = p_object; + if (object) { + object->add_change_receptor(this); + update_tree(); + } +} + +void EditorInspector::set_keying(bool p_active) { + if (keying == p_active) + return; + keying = p_active; + update_tree(); +} +void EditorInspector::set_read_only(bool p_read_only) { + read_only = p_read_only; + update_tree(); +} + +bool EditorInspector::is_capitalize_paths_enabled() const { + + return capitalize_paths; +} +void EditorInspector::set_enable_capitalize_paths(bool p_capitalize) { + capitalize_paths = p_capitalize; + update_tree(); +} + +void EditorInspector::set_autoclear(bool p_enable) { + autoclear = p_enable; +} + +void EditorInspector::set_show_categories(bool p_show) { + show_categories = p_show; + update_tree(); +} + +void EditorInspector::set_use_doc_hints(bool p_enable) { + use_doc_hints = p_enable; + update_tree(); +} +void EditorInspector::set_hide_script(bool p_hide) { + hide_script = p_hide; + update_tree(); +} +void EditorInspector::set_use_filter(bool p_use) { + use_filter = p_use; + update_tree(); +} +void EditorInspector::register_text_enter(Node *p_line_edit) { + search_box = Object::cast_to<LineEdit>(p_line_edit); + if (search_box) + search_box->connect("text_changed", this, "_filter_changed"); +} + +void EditorInspector::_filter_changed(const String &p_text) { + + update_tree(); +} + +void EditorInspector::set_subsection_selectable(bool p_selectable) { +} + +void EditorInspector::set_property_selectable(bool p_selectable) { +} + +void EditorInspector::set_use_folding(bool p_enable) { + use_folding = p_enable; + update_tree(); +} + +void EditorInspector::collapse_all_folding() { + + for (List<EditorInspectorSection *>::Element *E = sections.front(); E; E = E->next()) { + E->get()->fold(); + } +} + +void EditorInspector::expand_all_folding() { + for (List<EditorInspectorSection *>::Element *E = sections.front(); E; E = E->next()) { + E->get()->unfold(); + } +} + +void EditorInspector::set_scroll_offset(int p_offset) { + set_v_scroll(p_offset); +} + +int EditorInspector::get_scroll_offset() const { + return get_v_scroll(); +} + +void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { + + if (object != p_object) //may be undoing/redoing for a non edited object, so ignore + return; + + if (changing) + return; + + if (p_property == String()) + update_tree_pending = true; + else { + pending.insert(p_property); + } +} + +void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field) { + + if (autoclear && editor_property_map.has(p_name)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_name].front(); E; E = E->next()) { + if (E->get()->is_checkable()) { + E->get()->set_checked(true); + } + } + } + + if (!undo_redo || Object::cast_to<ArrayPropertyEdit>(object) || Object::cast_to<DictionaryPropertyEdit>(object)) { //kind of hacky + + object->set(p_name, p_value); + if (p_refresh_all) + _edit_request_change(object, ""); + else + _edit_request_change(object, p_name); + + emit_signal(_prop_edited, p_name); + + } else if (Object::cast_to<MultiNodeEdit>(object)) { + + Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field); + _edit_request_change(object, p_name); + emit_signal(_prop_edited, p_name); + } else { + + undo_redo->create_action(TTR("Set") + " " + p_name, UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(object, p_name, p_value); + undo_redo->add_undo_property(object, p_name, object->get(p_name)); + + if (p_refresh_all) { + undo_redo->add_do_method(this, "_edit_request_change", object, ""); + undo_redo->add_undo_method(this, "_edit_request_change", object, ""); + } else { + + undo_redo->add_do_method(this, "_edit_request_change", object, p_name); + undo_redo->add_undo_method(this, "_edit_request_change", object, p_name); + } + + Resource *r = Object::cast_to<Resource>(object); + if (r) { + if (!r->is_edited() && String(p_name) != "resource/edited") { + undo_redo->add_do_method(r, "set_edited", true); + undo_redo->add_undo_method(r, "set_edited", false); + } + + if (String(p_name) == "resource_local_to_scene") { + bool prev = object->get(p_name); + bool next = p_value; + if (next) { + undo_redo->add_do_method(r, "setup_local_to_scene"); + } + if (prev) { + undo_redo->add_undo_method(r, "setup_local_to_scene"); + } + } + } + undo_redo->add_do_method(this, "emit_signal", _prop_edited, p_name); + undo_redo->add_undo_method(this, "emit_signal", _prop_edited, p_name); + changing++; + undo_redo->commit_action(); + changing--; + } + + if (editor_property_map.has(p_name)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_name].front(); E; E = E->next()) { + E->get()->update_reload_status(); + } + } +} + +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value) { + + _edit_set(p_path, p_value, false, ""); +} + +void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { + + ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); + ERR_FAIL_COND(p_paths.size() != p_values.size()); + String names; + for (int i = 0; i < p_paths.size(); i++) { + if (i > 0) + names += ","; + names += p_paths[i]; + } + undo_redo->create_action(TTR("Set Multiple:") + " " + names, UndoRedo::MERGE_ENDS); + for (int i = 0; i < p_paths.size(); i++) { + _edit_set(p_paths[i], p_values[i], false, ""); + } + changing++; + undo_redo->commit_action(); + changing--; +} + +void EditorInspector::_property_keyed(const String &p_path) { + + if (!object) + return; + + emit_signal("property_keyed", p_path, object->get(p_path), false); //second param is deprecated +} + +void EditorInspector::_property_checked(const String &p_path, bool p_checked) { + + if (!object) + return; + + //property checked + if (autoclear) { + + if (!p_checked) { + object->set(p_path, Variant()); + } else { + + Variant to_create; + List<PropertyInfo> pinfo; + object->get_property_list(&pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().name == p_path) { + Variant::CallError ce; + to_create = Variant::construct(E->get().type, NULL, 0, ce); + break; + } + } + object->set(p_path, to_create); + } + + if (editor_property_map.has(p_path)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_path].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + + } else { + emit_signal("property_toggled", p_path, p_checked); + } +} + +void EditorInspector::_property_selected(const String &p_path, int p_focusable) { + + property_selected = p_path; + property_focusable = p_focusable; + //deselect the others + for (Map<StringName, List<EditorProperty *> >::Element *F = editor_property_map.front(); F; F = F->next()) { + if (F->key() == property_selected) + continue; + for (List<EditorProperty *>::Element *E = F->get().front(); E; E = E->next()) { + if (E->get()->is_selected()) + E->get()->deselect(); + } + } +} + +void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) { + + emit_signal("object_id_selected", p_id); +} + +void EditorInspector::_resource_selected(const String &p_path, RES p_resource) { + emit_signal("resource_selected", p_resource, p_path); +} + +void EditorInspector::_node_removed(Node *p_node) { + + if (p_node == object) { + edit(NULL); + } +} + +void EditorInspector::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + get_tree()->connect("node_removed", this, "_node_removed"); + add_style_override("bg", get_stylebox("bg", "Tree")); + } + if (p_what == NOTIFICATION_EXIT_TREE) { + + get_tree()->disconnect("node_removed", this, "_node_removed"); + edit(NULL); + } + + if (p_what == NOTIFICATION_PROCESS) { + + if (refresh_countdown > 0) { + refresh_countdown -= get_process_delta_time(); + if (refresh_countdown <= 0) { + for (Map<StringName, List<EditorProperty *> >::Element *F = editor_property_map.front(); F; F = F->next()) { + for (List<EditorProperty *>::Element *E = F->get().front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + } + } + + changing++; + + if (update_tree_pending) { + + update_tree(); + update_tree_pending = false; + pending.clear(); + + } else { + + while (pending.size()) { + StringName prop = pending.front()->get(); + if (editor_property_map.has(prop)) { + for (List<EditorProperty *>::Element *E = editor_property_map[prop].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + pending.erase(pending.front()); + } + } + + changing--; + } + + if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { + update_tree(); + } +} + +void EditorInspector::_changed_callback(Object *p_changed, const char *p_prop) { + //this is called when property change is notified via _change_notify() + _edit_request_change(p_changed, p_prop); +} + +void EditorInspector::_bind_methods() { + + ClassDB::bind_method("_multiple_properties_changed", &EditorInspector::_multiple_properties_changed); + ClassDB::bind_method("_property_changed", &EditorInspector::_property_changed); + ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change); + ClassDB::bind_method("_node_removed", &EditorInspector::_node_removed); + ClassDB::bind_method("_filter_changed", &EditorInspector::_filter_changed); + ClassDB::bind_method("_property_keyed", &EditorInspector::_property_keyed); + ClassDB::bind_method("_property_checked", &EditorInspector::_property_checked); + ClassDB::bind_method("_property_selected", &EditorInspector::_property_selected); + ClassDB::bind_method("_resource_selected", &EditorInspector::_resource_selected); + ClassDB::bind_method("_object_id_selected", &EditorInspector::_object_id_selected); + + ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "res"), PropertyInfo(Variant::STRING, "prop"))); + ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::INT, "id"))); +} + +EditorInspector::EditorInspector() { + object = NULL; + undo_redo = NULL; + main_vbox = memnew(VBoxContainer); + main_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(main_vbox); + main_vbox->set_name("pipirulo"); + set_h_scroll(false); + set_v_scroll(true); + + show_categories = false; + hide_script = true; + use_doc_hints = false; + capitalize_paths = false; + use_filter = false; + autoclear = false; + changing = 0; + use_folding = false; + update_all_pending = false; + update_tree_pending = false; + refresh_countdown = 0; + read_only = false; + search_box = NULL; + keying = false; + _prop_edited = "property_edited"; + set_process(true); + property_focusable = -1; +} diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h new file mode 100644 index 0000000000..64be316b0e --- /dev/null +++ b/editor/editor_inspector.h @@ -0,0 +1,283 @@ +#ifndef EDITOR_INSPECTOR_H +#define EDITOR_INSPECTOR_H + +#include "editor_data.h" +#include "scene/gui/scroll_container.h" + +class EditorProperty : public Container { + + GDCLASS(EditorProperty, Container) +public: + enum LabelLayout { + LABEL_LAYOUT_LEFT, + LABEL_LAYOUT_TOP, + }; + +private: + String label; + int text_size; + friend class EditorInspector; + Object *object; + StringName property; + + LabelLayout label_layout; + + int property_usage; + + bool read_only; + bool checkable; + bool checked; + bool draw_red; + bool keying; + + Rect2 keying_rect; + bool keying_hover; + Rect2 revert_rect; + bool revert_hover; + Rect2 check_rect; + bool check_hover; + + bool can_revert; + + bool _might_be_in_instance(); + bool _is_property_different(const Variant &p_current, const Variant &p_orig, int p_usage); + bool _is_instanced_node_with_original_property_different(); + bool _get_instanced_node_original_property(const StringName &p_prop, Variant &value); + void _focusable_focused(int p_index); + + bool selected; + int selected_focusable; + + Vector<Control *> focusables; + Control *label_reference; + +protected: + void _notification(int p_what); + static void _bind_methods(); + + void _gui_input(const Ref<InputEvent> &p_event); + +public: + virtual Size2 get_minimum_size() const; + + void set_label(const String &p_label); + String get_label() const; + + void set_read_only(bool p_read_only); + bool is_read_only() const; + + Object *get_edited_object(); + StringName get_edited_property(); + + virtual void update_property(); + void update_reload_status(); + + virtual bool use_keying_next() const; + + void set_checkable(bool p_checkable); + bool is_checkable() const; + + void set_checked(bool p_checked); + bool is_checked() const; + + void set_draw_red(bool p_draw_red); + bool is_draw_red() const; + + void set_keying(bool p_keying); + bool is_keying() const; + + void add_focusable(Control *p_control); + void select(int p_focusable = -1); + void deselect(); + bool is_selected() const; + + void set_label_reference(Control *p_control); + + virtual Variant get_drag_data(const Point2 &p_point); + + void set_label_layout(LabelLayout p_layout); + EditorProperty(); +}; + +class EditorInspectorPlugin : public Reference { + GDCLASS(EditorInspectorPlugin, Reference) + + friend class EditorInspector; + struct AddedEditor { + Control *property_editor; + Vector<String> properties; + String label; + }; + + List<AddedEditor> added_editors; + +protected: + static void _bind_methods(); + +public: + void add_custom_control(Control *control); + void add_property_editor(const String &p_for_property, Control *p_prop); + void add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop); + + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +class EditorInspectorCategory : public Control { + GDCLASS(EditorInspectorCategory, Control); + + friend class EditorInspector; + Ref<Texture> icon; + String label; + Color bg_color; + +protected: + void _notification(int p_what); + +public: + virtual Size2 get_minimum_size() const; + + EditorInspectorCategory(); +}; + +class EditorInspectorSection : public Container { + GDCLASS(EditorInspectorSection, Container); + + String label; + String section; + Object *object; + VBoxContainer *vbox; + Color bg_color; + bool foldable; + +protected: + void _notification(int p_what); + static void _bind_methods(); + void _gui_input(const Ref<InputEvent> &p_event); + +public: + virtual Size2 get_minimum_size() const; + + void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable); + VBoxContainer *get_vbox(); + void unfold(); + void fold(); + + Object *get_edited_object(); + + EditorInspectorSection(); +}; + +class EditorInspector : public ScrollContainer { + GDCLASS(EditorInspector, ScrollContainer); + + UndoRedo *undo_redo; + enum { + MAX_PLUGINS = 1024 + }; + static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS]; + static int inspector_plugin_count; + + VBoxContainer *main_vbox; + + //map use to cache the instanced editors + Map<StringName, List<EditorProperty *> > editor_property_map; + List<EditorInspectorSection *> sections; + Set<StringName> pending; + + void _clear(); + Object *object; + + // + + LineEdit *search_box; + bool show_categories; + bool hide_script; + bool use_doc_hints; + bool capitalize_paths; + bool use_filter; + bool autoclear; + bool use_folding; + int changing; + bool update_all_pending; + bool read_only; + bool keying; + + int refresh_countdown; + bool update_tree_pending; + StringName _prop_edited; + StringName property_selected; + int property_focusable; + + Map<StringName, Map<StringName, String> > descr_cache; + Map<StringName, String> class_descr_cache; + + void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); + + void _property_changed(const String &p_path, const Variant &p_value); + void _multiple_properties_changed(Vector<String> p_paths, Array p_values); + void _property_keyed(const String &p_path); + void _property_checked(const String &p_path, bool p_checked); + + void _resource_selected(const String &p_path, RES p_resource); + void _property_selected(const String &p_path, int p_focusable); + void _object_id_selected(const String &p_path, ObjectID p_id); + + void _node_removed(Node *p_node); + + void _changed_callback(Object *p_changed, const char *p_prop); + void _edit_request_change(Object *p_changed, const String &p_prop); + + void _filter_changed(const String &p_text); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + static void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); + static void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); + static void cleanup_plugins(); + + void set_undo_redo(UndoRedo *p_undo_redo); + + String get_selected_path() const; + + void update_tree(); + void update_property(const String &p_prop); + + void refresh(); + + void edit(Object *p_object); + + void set_keying(bool p_active); + void set_read_only(bool p_read_only); + + bool is_capitalize_paths_enabled() const; + void set_enable_capitalize_paths(bool p_capitalize); + void set_autoclear(bool p_enable); + + void set_show_categories(bool p_show); + void set_use_doc_hints(bool p_enable); + void set_hide_script(bool p_hide); + + void set_use_filter(bool p_use); + void register_text_enter(Node *p_line_edit); + + void set_subsection_selectable(bool p_selectable); + void set_property_selectable(bool p_selectable); + + void set_use_folding(bool p_enable); + + void collapse_all_folding(); + void expand_all_folding(); + + void set_scroll_offset(int p_offset); + int get_scroll_offset() const; + + EditorInspector(); +}; + +#endif // INSPECTOR_H diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index fd5a6dffc9..49f70e3215 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -56,6 +56,7 @@ #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_initialize_ssl.h" +#include "editor/editor_properties.h" #include "editor/editor_settings.h" #include "editor/editor_themes.h" #include "editor/import/editor_import_collada.h" @@ -483,7 +484,7 @@ void EditorNode::_fs_changed() { // come during the export export_defer.preset = ""; Error err = OK; - if (!preset->is_runnable() && (export_defer.path.ends_with(".pck") || export_defer.path.ends_with(".zip"))) { + if (export_defer.path.ends_with(".pck") || export_defer.path.ends_with(".zip")) { if (export_defer.path.ends_with(".zip")) { err = platform->export_zip(preset, export_defer.debug, export_defer.path); } else if (export_defer.path.ends_with(".pck")) { @@ -548,8 +549,8 @@ void EditorNode::_vp_resized() { void EditorNode::_node_renamed() { - if (property_editor) - property_editor->update_tree(); + if (inspector) + inspector->update_tree(); } void EditorNode::_editor_select_next() { @@ -1351,7 +1352,7 @@ void EditorNode::_dialog_action(String p_file) { void EditorNode::push_item(Object *p_object, const String &p_property) { if (!p_object) { - property_editor->edit(NULL); + inspector->edit(NULL); node_dock->set_node(NULL); scene_tree_dock->set_selected(NULL); return; @@ -1444,12 +1445,12 @@ void EditorNode::_property_editor_back() { void EditorNode::_menu_collapseall() { - property_editor->collapse_all_folding(); + inspector->collapse_all_folding(); } void EditorNode::_menu_expandall() { - property_editor->expand_all_folding(); + inspector->expand_all_folding(); } void EditorNode::_save_default_environment() { @@ -1515,7 +1516,7 @@ void EditorNode::_edit_current() { if (!current_obj) { scene_tree_dock->set_selected(NULL); - property_editor->edit(NULL); + inspector->edit(NULL); node_dock->set_node(NULL); object_menu->set_disabled(true); @@ -1536,7 +1537,7 @@ void EditorNode::_edit_current() { Resource *current_res = Object::cast_to<Resource>(current_obj); ERR_FAIL_COND(!current_res); scene_tree_dock->set_selected(NULL); - property_editor->edit(current_res); + inspector->edit(current_res); node_dock->set_node(NULL); object_menu->set_disabled(false); EditorNode::get_singleton()->get_import_dock()->set_edit_path(current_res->get_path()); @@ -1561,7 +1562,7 @@ void EditorNode::_edit_current() { Node *current_node = Object::cast_to<Node>(current_obj); ERR_FAIL_COND(!current_node); - property_editor->edit(current_node); + inspector->edit(current_node); if (current_node->is_inside_tree()) { node_dock->set_node(current_node); scene_tree_dock->set_selected(current_node); @@ -1585,7 +1586,7 @@ void EditorNode::_edit_current() { capitalize = false; } - property_editor->edit(current_obj); + inspector->edit(current_obj); node_dock->set_node(NULL); } @@ -1594,8 +1595,8 @@ void EditorNode::_edit_current() { property_editable_warning_dialog->set_text(editable_warning); } - if (property_editor->is_capitalize_paths_enabled() != capitalize) { - property_editor->set_enable_capitalize_paths(capitalize); + if (inspector->is_capitalize_paths_enabled() != capitalize) { + inspector->set_enable_capitalize_paths(capitalize); } /* Take care of PLUGIN EDITOR */ @@ -2939,7 +2940,7 @@ Dictionary EditorNode::_get_main_scene_state() { Dictionary state; state["main_tab"] = _get_current_main_editor(); state["scene_tree_offset"] = scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value(); - state["property_edit_offset"] = get_property_editor()->get_scene_tree()->get_vscroll_bar()->get_value(); + state["property_edit_offset"] = get_inspector()->get_scroll_offset(); state["saved_version"] = saved_version; state["node_filter"] = scene_tree_dock->get_filter(); return state; @@ -2985,7 +2986,7 @@ void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) { if (p_state.has("scene_tree_offset")) scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["scene_tree_offset"]); if (p_state.has("property_edit_offset")) - get_property_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["property_edit_offset"]); + get_inspector()->set_scroll_offset(p_state["property_edit_offset"]); if (p_state.has("node_filter")) scene_tree_dock->set_filter(p_state["node_filter"]); @@ -3278,9 +3279,7 @@ void EditorNode::update_keying() { } } - property_editor->set_keying(valid); - - AnimationPlayerEditor::singleton->get_key_editor()->update_keying(); + inspector->set_keying(valid); } void EditorNode::_close_messages() { @@ -3425,6 +3424,9 @@ void EditorNode::register_editor_types() { ClassDB::register_class<EditorExportPlugin>(); ClassDB::register_class<EditorResourceConversionPlugin>(); ClassDB::register_class<EditorSceneImporter>(); + ClassDB::register_class<EditorInspector>(); + ClassDB::register_class<EditorInspectorPlugin>(); + ClassDB::register_class<EditorProperty>(); // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class<EditorScenePostImport>(); @@ -4236,7 +4238,7 @@ void EditorNode::_scene_tab_changed(int p_tab) { void EditorNode::_toggle_search_bar(bool p_pressed) { - property_editor->set_use_filter(p_pressed); + inspector->set_use_filter(p_pressed); if (p_pressed) { @@ -4255,7 +4257,7 @@ void EditorNode::_clear_search_box() { return; search_box->clear(); - property_editor->update_tree(); + inspector->update_tree(); } ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { @@ -5007,6 +5009,12 @@ EditorNode::EditorNode() { ResourceFormatImporter::get_singleton()->add_importer(import_bitmap); } + { + Ref<EditorInspectorDefaultPlugin> eidp; + eidp.instance(); + EditorInspector::add_inspector_plugin(eidp); + } + _pvrtc_register_compressors(); editor_selection = memnew(EditorSelection); @@ -5665,21 +5673,21 @@ EditorNode::EditorNode() { property_editable_warning->hide(); property_editable_warning->connect("pressed", this, "_property_editable_warning_pressed"); - property_editor = memnew(PropertyEditor); - property_editor->set_autoclear(true); - property_editor->set_show_categories(true); - property_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - property_editor->set_use_doc_hints(true); - property_editor->set_hide_script(false); - property_editor->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/editor/capitalize_properties", true))); - property_editor->set_use_folding(!bool(EDITOR_DEF("interface/editor/disable_inspector_folding", false))); + inspector = memnew(EditorInspector); + inspector->set_autoclear(true); + inspector->set_show_categories(true); + inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); + inspector->set_use_doc_hints(true); + inspector->set_hide_script(false); + inspector->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/editor/capitalize_properties", true))); + inspector->set_use_folding(!bool(EDITOR_DEF("interface/editor/disable_inspector_folding", false))); - property_editor->hide_top_label(); - property_editor->register_text_enter(search_box); + // inspector->hide_top_label(); + inspector->register_text_enter(search_box); Button *property_editable_warning; - prop_editor_base->add_child(property_editor); - property_editor->set_undo_redo(&editor_data.get_undo_redo()); + prop_editor_base->add_child(inspector); + inspector->set_undo_redo(&editor_data.get_undo_redo()); import_dock = memnew(ImportDock); dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(import_dock); @@ -5815,8 +5823,8 @@ EditorNode::EditorNode() { file->connect("file_selected", this, "_dialog_action"); file_templates->connect("file_selected", this, "_dialog_action"); - property_editor->connect("resource_selected", this, "_resource_selected"); - property_editor->connect("property_keyed", this, "_property_keyed"); + inspector->connect("resource_selected", this, "_resource_selected"); + inspector->connect("property_keyed", this, "_property_keyed"); //plugin stuff @@ -6039,6 +6047,8 @@ EditorNode::EditorNode() { EditorNode::~EditorNode() { + EditorInspector::cleanup_plugins(); + remove_print_handler(&print_handler); memdelete(EditorHelp::get_doc_data()); memdelete(editor_selection); diff --git a/editor/editor_node.h b/editor/editor_node.h index f774fa0a2e..86b85663ab 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -37,6 +37,7 @@ #include "editor/editor_about.h" #include "editor/editor_data.h" #include "editor/editor_export.h" +#include "editor/editor_inspector.h" #include "editor/editor_log.h" #include "editor/editor_name_dialog.h" #include "editor/editor_path.h" @@ -80,7 +81,6 @@ #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" #include "scene/gui/viewport_container.h" - /** @author Juan Linietsky <reduzio@gmail.com> */ @@ -267,7 +267,7 @@ private: Button *property_back; Button *property_forward; SceneTreeDock *scene_tree_dock; - PropertyEditor *property_editor; + EditorInspector *inspector; Button *property_editable_warning; AcceptDialog *property_editable_warning_dialog; void _property_editable_warning_pressed(); @@ -640,7 +640,7 @@ public: EditorPluginList *get_editor_plugins_over() { return editor_plugins_over; } EditorPluginList *get_editor_plugins_force_over() { return editor_plugins_force_over; } EditorPluginList *get_editor_plugins_force_input_forwarding() { return editor_plugins_force_input_forwarding; } - PropertyEditor *get_property_editor() { return property_editor; } + EditorInspector *get_inspector() { return inspector; } VBoxContainer *get_property_editor_vb() { return prop_editor_vb; } ProjectSettingsEditor *get_project_settings() { return project_settings; } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp new file mode 100644 index 0000000000..51be64bd3f --- /dev/null +++ b/editor/editor_properties.cpp @@ -0,0 +1,2411 @@ +#include "editor_properties.h" +#include "editor/editor_resource_preview.h" +#include "editor_node.h" +#include "scene/main/viewport.h" +///////////////////// TEXT ///////////////////////// +void EditorPropertyText::_text_changed(const String &p_string) { + if (updating) + return; + + emit_signal("property_changed", get_edited_property(), p_string); +} + +void EditorPropertyText::update_property() { + String s = get_edited_object()->get(get_edited_property()); + updating = true; + text->set_text(s); + text->set_editable(!is_read_only()); + updating = false; +} + +void EditorPropertyText::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed", "txt"), &EditorPropertyText::_text_changed); +} + +EditorPropertyText::EditorPropertyText() { + text = memnew(LineEdit); + add_child(text); + add_focusable(text); + text->connect("text_changed", this, "_text_changed"); + updating = false; +} + +///////////////////// MULTILINE TEXT ///////////////////////// + +void EditorPropertyMultilineText::_big_text_changed() { + text->set_text(big_text->get_text()); + emit_signal("property_changed", get_edited_property(), big_text->get_text()); +} + +void EditorPropertyMultilineText::_text_changed() { + + emit_signal("property_changed", get_edited_property(), text->get_text()); +} + +void EditorPropertyMultilineText::_open_big_text() { + + if (!big_text_dialog) { + big_text = memnew(TextEdit); + big_text->connect("text_changed", this, "_big_text_changed"); + big_text_dialog = memnew(AcceptDialog); + big_text_dialog->add_child(big_text); + big_text_dialog->set_title("Edit Text:"); + add_child(big_text_dialog); + } + + big_text->set_text(text->get_text()); + big_text_dialog->popup_centered_ratio(); +} + +void EditorPropertyMultilineText::update_property() { + String t = get_edited_object()->get(get_edited_property()); + text->set_text(t); + if (big_text && big_text->is_visible_in_tree()) { + big_text->set_text(t); + } +} + +void EditorPropertyMultilineText::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + Ref<Texture> df = get_icon("DistractionFree", "EditorIcons"); + open_big_text->set_icon(df); + Ref<Font> font = get_font("font", "Label"); + text->set_custom_minimum_size(Vector2(0, font->get_height() * 6)); + + } break; + } +} + +void EditorPropertyMultilineText::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed"), &EditorPropertyMultilineText::_text_changed); + ClassDB::bind_method(D_METHOD("_big_text_changed"), &EditorPropertyMultilineText::_big_text_changed); + ClassDB::bind_method(D_METHOD("_open_big_text"), &EditorPropertyMultilineText::_open_big_text); +} + +EditorPropertyMultilineText::EditorPropertyMultilineText() { + HBoxContainer *hb = memnew(HBoxContainer); + set_label_layout(LABEL_LAYOUT_TOP); + add_child(hb); + text = memnew(TextEdit); + text->connect("text_changed", this, "_text_changed"); + add_focusable(text); + hb->add_child(text); + text->set_h_size_flags(SIZE_EXPAND_FILL); + open_big_text = memnew(ToolButton); + open_big_text->connect("pressed", this, "_open_big_text"); + hb->add_child(open_big_text); + big_text_dialog = NULL; + big_text = NULL; +} + +///////////////////// TEXT ENUM ///////////////////////// + +void EditorPropertyTextEnum::_option_selected(int p_which) { + + emit_signal("property_changed", get_edited_property(), options->get_item_text(p_which)); +} + +void EditorPropertyTextEnum::update_property() { + + String which = get_edited_object()->get(get_edited_property()); + for (int i = 0; i < options->get_item_count(); i++) { + String t = options->get_item_text(i); + if (t == which) { + options->select(i); + return; + } + } +} + +void EditorPropertyTextEnum::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyTextEnum::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyTextEnum::_option_selected); +} + +EditorPropertyTextEnum::EditorPropertyTextEnum() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} +///////////////////// PATH ///////////////////////// + +void EditorPropertyPath::_path_selected(const String &p_path) { + + emit_signal("property_changed", get_edited_property(), p_path); + update_property(); +} +void EditorPropertyPath::_path_pressed() { + + if (!dialog) { + dialog = memnew(EditorFileDialog); + dialog->connect("file_selected", this, "_path_selected"); + dialog->connect("dir_selected", this, "_path_selected"); + add_child(dialog); + } + + String full_path = get_edited_object()->get(get_edited_property()); + + dialog->clear_filters(); + + if (global) { + dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + } else { + dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); + } + + if (folder) { + dialog->set_mode(EditorFileDialog::MODE_OPEN_DIR); + dialog->set_current_dir(full_path); + } else { + dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + for (int i = 0; i < extensions.size(); i++) { + String e = extensions[i].strip_edges(); + if (e != String()) { + dialog->add_filter(extensions[i].strip_edges()); + } + } + dialog->set_current_path(full_path); + } + + dialog->popup_centered_ratio(); +} + +void EditorPropertyPath::update_property() { + + String full_path = get_edited_object()->get(get_edited_property()); + path->set_text(full_path); + path->set_tooltip(full_path); +} + +void EditorPropertyPath::setup(const Vector<String> &p_extensions, bool p_folder, bool p_global) { + + extensions = p_extensions; + folder = p_folder; + global = p_global; +} + +void EditorPropertyPath::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_path_pressed"), &EditorPropertyPath::_path_pressed); + ClassDB::bind_method(D_METHOD("_path_selected"), &EditorPropertyPath::_path_selected); +} + +EditorPropertyPath::EditorPropertyPath() { + path = memnew(Button); + path->set_clip_text(true); + add_child(path); + add_focusable(path); + dialog = NULL; + path->connect("pressed", this, "_path_pressed"); + folder = false; + global = false; +} + +///////////////////// MEMBER ///////////////////////// + +void EditorPropertyMember::_property_selected(const String &p_selected) { + + emit_signal("property_changed", get_edited_property(), p_selected); + update_property(); +} + +void EditorPropertyMember::_property_select() { + + if (!selector) { + selector = memnew(PropertySelector); + selector->connect("selected", this, "_property_selected"); + add_child(selector); + } + + String current = get_edited_object()->get(get_edited_property()); + + if (hint == MEMBER_METHOD_OF_VARIANT_TYPE) { + + Variant::Type type = Variant::NIL; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (hint_text == Variant::get_type_name(Variant::Type(i))) { + type = Variant::Type(i); + } + } + if (type) + selector->select_method_from_basic_type(type, current); + + } else if (hint == MEMBER_METHOD_OF_BASE_TYPE) { + + selector->select_method_from_base_type(hint_text, current); + + } else if (hint == MEMBER_METHOD_OF_INSTANCE) { + + Object *instance = ObjectDB::get_instance(hint_text.to_int64()); + if (instance) + selector->select_method_from_instance(instance, current); + + } else if (hint == MEMBER_METHOD_OF_SCRIPT) { + + Object *obj = ObjectDB::get_instance(hint_text.to_int64()); + if (Object::cast_to<Script>(obj)) { + selector->select_method_from_script(Object::cast_to<Script>(obj), current); + } + + } else if (hint == MEMBER_PROPERTY_OF_VARIANT_TYPE) { + + Variant::Type type = Variant::NIL; + String tname = hint_text; + if (tname.find(".") != -1) + tname = tname.get_slice(".", 0); + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (tname == Variant::get_type_name(Variant::Type(i))) { + type = Variant::Type(Variant::Type(i)); + } + } + + if (type != Variant::NIL) + selector->select_property_from_basic_type(type, current); + + } else if (hint == MEMBER_PROPERTY_OF_BASE_TYPE) { + + selector->select_property_from_base_type(hint_text, current); + + } else if (hint == MEMBER_PROPERTY_OF_INSTANCE) { + + Object *instance = ObjectDB::get_instance(hint_text.to_int64()); + if (instance) + selector->select_property_from_instance(instance, current); + + } else if (hint == MEMBER_PROPERTY_OF_SCRIPT) { + + Object *obj = ObjectDB::get_instance(hint_text.to_int64()); + if (Object::cast_to<Script>(obj)) { + selector->select_property_from_script(Object::cast_to<Script>(obj), current); + } + } +} + +void EditorPropertyMember::setup(Type p_hint, const String &p_hint_text) { + hint = p_hint; + hint_text = p_hint_text; +} + +void EditorPropertyMember::update_property() { + + String full_path = get_edited_object()->get(get_edited_property()); + property->set_text(full_path); +} + +void EditorPropertyMember::_bind_methods() { + ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyMember::_property_selected); + ClassDB::bind_method(D_METHOD("_property_select"), &EditorPropertyMember::_property_select); +} + +EditorPropertyMember::EditorPropertyMember() { + selector = NULL; + property = memnew(Button); + property->set_clip_text(true); + add_child(property); + add_focusable(property); + property->connect("pressed", this, "_property_select"); +} + +///////////////////// CHECK ///////////////////////// +void EditorPropertyCheck::_checkbox_pressed() { + + emit_signal("property_changed", get_edited_property(), checkbox->is_pressed()); +} + +void EditorPropertyCheck::update_property() { + bool c = get_edited_object()->get(get_edited_property()); + checkbox->set_pressed(c); + checkbox->set_disabled(is_read_only()); +} + +void EditorPropertyCheck::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_checkbox_pressed"), &EditorPropertyCheck::_checkbox_pressed); +} + +EditorPropertyCheck::EditorPropertyCheck() { + checkbox = memnew(CheckBox); + checkbox->set_text(TTR("On")); + add_child(checkbox); + add_focusable(checkbox); + checkbox->connect("pressed", this, "_checkbox_pressed"); +} + +///////////////////// ENUM ///////////////////////// + +void EditorPropertyEnum::_option_selected(int p_which) { + + emit_signal("property_changed", get_edited_property(), p_which); +} + +void EditorPropertyEnum::update_property() { + + int which = get_edited_object()->get(get_edited_property()); + options->select(which); +} + +void EditorPropertyEnum::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyEnum::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyEnum::_option_selected); +} + +EditorPropertyEnum::EditorPropertyEnum() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} + +///////////////////// FLAGS ///////////////////////// + +void EditorPropertyFlags::_flag_toggled() { + + uint32_t value = 0; + for (int i = 0; i < flags.size(); i++) { + if (flags[i]->is_pressed()) { + uint32_t val = 1; + val <<= flag_indices[i]; + value |= val; + } + } + + emit_signal("property_changed", get_edited_property(), value); +} + +void EditorPropertyFlags::update_property() { + + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flags.size(); i++) { + uint32_t val = 1; + val <<= flag_indices[i]; + if (value & val) { + + flags[i]->set_pressed(true); + } else { + flags[i]->set_pressed(false); + } + } +} + +void EditorPropertyFlags::setup(const Vector<String> &p_options) { + ERR_FAIL_COND(flags.size()); + + bool first = true; + for (int i = 0; i < p_options.size(); i++) { + String option = p_options[i].strip_edges(); + if (option != "") { + CheckBox *cb = memnew(CheckBox); + cb->set_text(option); + cb->set_clip_text(true); + cb->connect("pressed", this, "_flag_toggled"); + add_focusable(cb); + vbox->add_child(cb); + flags.push_back(cb); + flag_indices.push_back(i); + if (first) { + set_label_reference(cb); + first = false; + } + } + } +} + +void EditorPropertyFlags::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_flag_toggled"), &EditorPropertyFlags::_flag_toggled); +} + +EditorPropertyFlags::EditorPropertyFlags() { + + vbox = memnew(VBoxContainer); + add_child(vbox); +} + +///////////////////// LAYERS ///////////////////////// + +class EditorPropertyLayersGrid : public Control { + GDCLASS(EditorPropertyLayersGrid, Control) +public: + uint32_t value; + Vector<Rect2> flag_rects; + Vector<String> names; + + virtual Size2 get_minimum_size() const { + Ref<Font> font = get_font("font", "Label"); + return Vector2(0, font->get_height() * 2); + } + + virtual String get_tooltip(const Point2 &p_pos) const { + for (int i = 0; i < flag_rects.size(); i++) { + if (flag_rects[i].has_point(p_pos) && i < names.size()) { + return names[i]; + } + } + return String(); + } + void _gui_input(const Ref<InputEvent> &p_ev) { + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + for (int i = 0; i < flag_rects.size(); i++) { + if (flag_rects[i].has_point(mb->get_position())) { + //toggle + if (value & (1 << i)) { + value &= ~(1 << i); + } else { + value |= (1 << i); + } + emit_signal("flag_changed", value); + update(); + } + } + } + } + + void _notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + + Rect2 rect; + rect.size = get_size(); + flag_rects.clear(); + + int bsize = (rect.size.height * 80 / 100) / 2; + + int h = bsize * 2 + 1; + int vofs = (rect.size.height - h) / 2; + + for (int i = 0; i < 2; i++) { + + Point2 ofs(4, vofs); + if (i == 1) + ofs.y += bsize + 1; + + ofs += rect.position; + for (int j = 0; j < 10; j++) { + + Point2 o = ofs + Point2(j * (bsize + 1), 0); + if (j >= 5) + o.x += 1; + + uint32_t idx = i * 10 + j; + bool on = value & (1 << idx); + Rect2 rect = Rect2(o, Size2(bsize, bsize)); + draw_rect(rect, Color(0, 0, 0, on ? 0.8 : 0.3)); + flag_rects.push_back(rect); + } + } + } + } + + void set_flag(uint32_t p_flag) { + value = p_flag; + update(); + } + + static void _bind_methods() { + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input); + ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag"))); + } + + EditorPropertyLayersGrid() { + value = 0; + } +}; +void EditorPropertyLayers::_grid_changed(uint32_t p_grid) { + + emit_signal("property_changed", get_edited_property(), p_grid); +} + +void EditorPropertyLayers::update_property() { + + uint32_t value = get_edited_object()->get(get_edited_property()); + + grid->set_flag(value); +} + +void EditorPropertyLayers::setup(LayerType p_layer_type) { + + String basename; + switch (p_layer_type) { + case LAYER_RENDER_2D: + basename = "layer_names/2d_render"; + break; + case LAYER_PHYSICS_2D: + basename = "layer_names/2d_physics"; + break; + case LAYER_RENDER_3D: + basename = "layer_names/3d_render"; + break; + case LAYER_PHYSICS_3D: + basename = "layer_names/3d_physics"; + break; + } + + Vector<String> names; + for (int i = 0; i < 20; i++) { + String name; + + if (ProjectSettings::get_singleton()->has_setting(basename + "/layer_" + itos(i + 1))) { + name = ProjectSettings::get_singleton()->get(basename + "/layer_" + itos(i + 1)); + } + + if (name == "") { + name = "Layer " + itos(i + 1); + } + + names.push_back(name); + } + + grid->names = names; +} + +void EditorPropertyLayers::_button_pressed() { + + layers->clear(); + for (int i = 0; i < 20; i++) { + if (i == 5 || i == 10 || i == 15) { + layers->add_separator(); + } + layers->add_check_item(grid->names[i], i); + int idx = layers->get_item_index(i); + layers->set_item_checked(idx, grid->value & (1 << i)); + } + + Rect2 gp = button->get_global_rect(); + Vector2 popup_pos = gp.position - Vector2(layers->get_combined_minimum_size().x, 0); + layers->set_global_position(popup_pos); + layers->popup(); +} + +void EditorPropertyLayers::_menu_pressed(int p_menu) { + if (grid->value & (1 << p_menu)) { + grid->value &= ~(1 << p_menu); + } else { + grid->value |= (1 << p_menu); + } + grid->update(); + _grid_changed(grid->value); +} + +void EditorPropertyLayers::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_grid_changed"), &EditorPropertyLayers::_grid_changed); + ClassDB::bind_method(D_METHOD("_button_pressed"), &EditorPropertyLayers::_button_pressed); + ClassDB::bind_method(D_METHOD("_menu_pressed"), &EditorPropertyLayers::_menu_pressed); +} + +EditorPropertyLayers::EditorPropertyLayers() { + + HBoxContainer *hb = memnew(HBoxContainer); + add_child(hb); + grid = memnew(EditorPropertyLayersGrid); + grid->connect("flag_changed", this, "_grid_changed"); + grid->set_h_size_flags(SIZE_EXPAND_FILL); + hb->add_child(grid); + button = memnew(Button); + button->set_text(".."); + button->connect("pressed", this, "_button_pressed"); + hb->add_child(button); + set_label_layout(LABEL_LAYOUT_TOP); + layers = memnew(PopupMenu); + add_child(layers); + layers->connect("id_pressed", this, "_menu_pressed"); +} +///////////////////// INT ///////////////////////// + +void EditorPropertyInteger::_value_changed(double val) { + if (setting) + return; + emit_signal("property_changed", get_edited_property(), int(val)); +} + +void EditorPropertyInteger::update_property() { + int val = get_edited_object()->get(get_edited_property()); + setting = true; + spin->set_value(val); + setting = false; +} + +void EditorPropertyInteger::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyInteger::_value_changed); +} + +void EditorPropertyInteger::setup(int p_min, int p_max) { + spin->set_min(p_min); + spin->set_max(p_max); + spin->set_step(1); +} + +EditorPropertyInteger::EditorPropertyInteger() { + spin = memnew(EditorSpinSlider); + add_child(spin); + add_focusable(spin); + spin->connect("value_changed", this, "_value_changed"); + setting = false; +} + +///////////////////// OBJECT ID ///////////////////////// + +void EditorPropertyObjectID::_edit_pressed() { + + emit_signal("object_id_selected", get_edited_property(), get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyObjectID::update_property() { + String type = base_type; + if (type == "") + type = "Object"; + + String icon_type = type; + if (has_icon(icon_type, "EditorIcons")) { + type = icon_type; + } else { + type = "Object"; + } + + ObjectID id = get_edited_object()->get(get_edited_property()); + if (id != 0) { + edit->set_text(type + " ID: " + itos(id)); + edit->set_disabled(false); + edit->set_icon(get_icon(icon_type, "EditorIcons")); + } else { + edit->set_text(TTR("[Empty]")); + edit->set_disabled(true); + edit->set_icon(Ref<Texture>()); + } +} + +void EditorPropertyObjectID::setup(const String &p_base_type) { + base_type = p_base_type; +} + +void EditorPropertyObjectID::_bind_methods() { + ClassDB::bind_method(D_METHOD("_edit_pressed"), &EditorPropertyObjectID::_edit_pressed); +} + +EditorPropertyObjectID::EditorPropertyObjectID() { + edit = memnew(Button); + add_child(edit); + add_focusable(edit); + edit->connect("pressed", this, "_edit_pressed"); +} + +///////////////////// FLOAT ///////////////////////// + +void EditorPropertyFloat::_value_changed(double val) { + if (setting) + return; + + emit_signal("property_changed", get_edited_property(), val); +} + +void EditorPropertyFloat::update_property() { + double val = get_edited_object()->get(get_edited_property()); + setting = true; + spin->set_value(val); + setting = false; +} + +void EditorPropertyFloat::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyFloat::_value_changed); +} + +void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range) { + + spin->set_min(p_min); + spin->set_max(p_max); + spin->set_step(p_step); + spin->set_hide_slider(p_no_slider); + spin->set_exp_ratio(p_exp_range); +} + +EditorPropertyFloat::EditorPropertyFloat() { + spin = memnew(EditorSpinSlider); + add_child(spin); + add_focusable(spin); + spin->connect("value_changed", this, "_value_changed"); + setting = false; +} + +///////////////////// EASING ///////////////////////// + +void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + + Ref<InputEventMouseMotion> mm = p_ev; + + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + + float rel = mm->get_relative().x; + if (rel == 0) + return; + + if (flip) + rel = -rel; + + float val = get_edited_object()->get(get_edited_property()); + if (val == 0) + return; + bool sg = val < 0; + val = Math::absf(val); + + val = Math::log(val) / Math::log((float)2.0); + //logspace + val += rel * 0.05; + + val = Math::pow(2.0f, val); + if (sg) + val = -val; + + emit_signal("property_changed", get_edited_property(), val); + easing_draw->update(); + } +} + +void EditorPropertyEasing::_draw_easing() { + + RID ci = easing_draw->get_canvas_item(); + + Size2 s = easing_draw->get_size(); + Rect2 r(Point2(), s); + r = r.grow(3); + get_stylebox("normal", "LineEdit")->draw(ci, r); + + int points = 48; + + float prev = 1.0; + float exp = get_edited_object()->get(get_edited_property()); + + Ref<Font> f = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + + for (int i = 1; i <= points; i++) { + + float ifl = i / float(points); + float iflp = (i - 1) / float(points); + + float h = 1.0 - Math::ease(ifl, exp); + + if (flip) { + ifl = 1.0 - ifl; + iflp = 1.0 - iflp; + } + + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(iflp * s.width, prev * s.height), Point2(ifl * s.width, h * s.height), color); + prev = h; + } + + f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), color); +} + +void EditorPropertyEasing::update_property() { + easing_draw->update(); +} + +void EditorPropertyEasing::_set_preset(float p_val) { + emit_signal("property_changed", get_edited_property(), p_val); + easing_draw->update(); +} + +void EditorPropertyEasing::setup(bool p_full, bool p_flip) { + + flip = p_flip; + if (p_full) { + HBoxContainer *hb2 = memnew(HBoxContainer); + vb->add_child(hb2); + button_out_in = memnew(ToolButton); + button_out_in->set_tooltip(TTR("Out-In")); + button_out_in->set_h_size_flags(SIZE_EXPAND_FILL); + button_out_in->connect("pressed", this, "_set_preset", varray(-0.5)); + hb2->add_child(button_out_in); + + button_in_out = memnew(ToolButton); + button_in_out->set_tooltip(TTR("In")); + button_in_out->set_h_size_flags(SIZE_EXPAND_FILL); + button_in_out->connect("pressed", this, "_set_preset", varray(-2)); + hb2->add_child(button_in_out); + } +} + +void EditorPropertyEasing::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + easing_draw->set_custom_minimum_size(Size2(0, get_font("font", "Label")->get_height() * 2)); + button_linear->set_icon(get_icon("CurveLinear", "EditorIcons")); + button_out->set_icon(get_icon("CurveOut", "EditorIcons")); + button_in->set_icon(get_icon("CurveIn", "EditorIcons")); + button_constant->set_icon(get_icon("CurveConstant", "EditorIcons")); + if (button_out_in) + button_out_in->set_icon(get_icon("CurveOutIn", "EditorIcons")); + if (button_in_out) + button_in_out->set_icon(get_icon("CurveInOut", "EditorIcons")); + } break; + } +} + +void EditorPropertyEasing::_bind_methods() { + + ClassDB::bind_method("_draw_easing", &EditorPropertyEasing::_draw_easing); + ClassDB::bind_method("_drag_easing", &EditorPropertyEasing::_drag_easing); + ClassDB::bind_method("_set_preset", &EditorPropertyEasing::_set_preset); +} + +EditorPropertyEasing::EditorPropertyEasing() { + + vb = memnew(VBoxContainer); + add_child(vb); + HBoxContainer *hb = memnew(HBoxContainer); + set_label_reference(hb); + + vb->add_child(hb); + + button_linear = memnew(ToolButton); + button_linear->set_tooltip(TTR("Linear")); + button_linear->set_h_size_flags(SIZE_EXPAND_FILL); + button_linear->connect("pressed", this, "_set_preset", varray(1)); + hb->add_child(button_linear); + + button_constant = memnew(ToolButton); + button_constant->set_tooltip(TTR("Linear")); + button_constant->set_h_size_flags(SIZE_EXPAND_FILL); + button_constant->connect("pressed", this, "_set_preset", varray(0)); + hb->add_child(button_constant); + + button_out = memnew(ToolButton); + button_out->set_tooltip(TTR("Out")); + button_out->set_h_size_flags(SIZE_EXPAND_FILL); + button_out->connect("pressed", this, "_set_preset", varray(0.5)); + hb->add_child(button_out); + + button_in = memnew(ToolButton); + button_in->set_tooltip(TTR("In")); + button_in->set_h_size_flags(SIZE_EXPAND_FILL); + button_in->connect("pressed", this, "_set_preset", varray(2)); + hb->add_child(button_in); + + button_in_out = NULL; + button_out_in = NULL; + + easing_draw = memnew(Control); + easing_draw->connect("draw", this, "_draw_easing"); + easing_draw->connect("gui_input", this, "_drag_easing"); + easing_draw->set_default_cursor_shape(Control::CURSOR_MOVE); + vb->add_child(easing_draw); + + flip = false; +} + +///////////////////// VECTOR2 ///////////////////////// + +void EditorPropertyVector2::_value_changed(double val) { + if (setting) + return; + + Vector2 v2; + v2.x = spin[0]->get_value(); + v2.y = spin[1]->get_value(); + emit_signal("property_changed", get_edited_property(), v2); +} + +void EditorPropertyVector2::update_property() { + Vector2 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + setting = false; +} + +void EditorPropertyVector2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector2::_value_changed); +} + +void EditorPropertyVector2::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 2; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyVector2::EditorPropertyVector2() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[2] = { "x", "y" }; + for (int i = 0; i < 2; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// RECT2 ///////////////////////// + +void EditorPropertyRect2::_value_changed(double val) { + if (setting) + return; + + Rect2 r2; + r2.position.x = spin[0]->get_value(); + r2.position.x = spin[1]->get_value(); + r2.size.y = spin[2]->get_value(); + r2.size.y = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), r2); +} + +void EditorPropertyRect2::update_property() { + Rect2 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.position.x); + spin[1]->set_value(val.position.y); + spin[2]->set_value(val.size.x); + spin[3]->set_value(val.size.y); + setting = false; +} + +void EditorPropertyRect2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyRect2::_value_changed); +} + +void EditorPropertyRect2::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyRect2::EditorPropertyRect2() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "w", "h" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} +///////////////////// VECTOR3 ///////////////////////// + +void EditorPropertyVector3::_value_changed(double val) { + if (setting) + return; + + Vector3 v3; + v3.x = spin[0]->get_value(); + v3.y = spin[1]->get_value(); + v3.z = spin[2]->get_value(); + emit_signal("property_changed", get_edited_property(), v3); +} + +void EditorPropertyVector3::update_property() { + Vector3 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + spin[2]->set_value(val.z); + setting = false; +} + +void EditorPropertyVector3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector3::_value_changed); +} + +void EditorPropertyVector3::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 3; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyVector3::EditorPropertyVector3() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[3] = { "x", "y", "z" }; + for (int i = 0; i < 3; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} +///////////////////// PLANE ///////////////////////// + +void EditorPropertyPlane::_value_changed(double val) { + if (setting) + return; + + Plane p; + p.normal.x = spin[0]->get_value(); + p.normal.y = spin[1]->get_value(); + p.normal.z = spin[2]->get_value(); + p.d = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyPlane::update_property() { + Plane val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.normal.x); + spin[1]->set_value(val.normal.y); + spin[2]->set_value(val.normal.z); + spin[3]->set_value(val.d); + setting = false; +} + +void EditorPropertyPlane::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyPlane::_value_changed); +} + +void EditorPropertyPlane::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyPlane::EditorPropertyPlane() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "z", "d" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// QUAT ///////////////////////// + +void EditorPropertyQuat::_value_changed(double val) { + if (setting) + return; + + Quat p; + p.x = spin[0]->get_value(); + p.y = spin[1]->get_value(); + p.z = spin[2]->get_value(); + p.w = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyQuat::update_property() { + Quat val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + spin[2]->set_value(val.z); + spin[3]->set_value(val.w); + setting = false; +} + +void EditorPropertyQuat::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyQuat::_value_changed); +} + +void EditorPropertyQuat::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyQuat::EditorPropertyQuat() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "z", "w" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// AABB ///////////////////////// + +void EditorPropertyAABB::_value_changed(double val) { + if (setting) + return; + + AABB p; + p.position.x = spin[0]->get_value(); + p.position.y = spin[1]->get_value(); + p.position.z = spin[2]->get_value(); + p.size.x = spin[3]->get_value(); + p.size.y = spin[4]->get_value(); + p.size.z = spin[5]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyAABB::update_property() { + AABB val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.position.x); + spin[1]->set_value(val.position.y); + spin[2]->set_value(val.position.z); + spin[3]->set_value(val.size.x); + spin[4]->set_value(val.size.y); + spin[5]->set_value(val.size.z); + + setting = false; +} + +void EditorPropertyAABB::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyAABB::_value_changed); +} + +void EditorPropertyAABB::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 6; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyAABB::EditorPropertyAABB() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[6] = { "x", "y", "z", "w", "h", "d" }; + for (int i = 0; i < 6; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// TRANSFORM2D ///////////////////////// + +void EditorPropertyTransform2D::_value_changed(double val) { + if (setting) + return; + + Transform2D p; + p[0][0] = spin[0]->get_value(); + p[0][1] = spin[1]->get_value(); + p[1][0] = spin[2]->get_value(); + p[1][1] = spin[3]->get_value(); + p[2][0] = spin[4]->get_value(); + p[2][1] = spin[5]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyTransform2D::update_property() { + Transform2D val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val[0][0]); + spin[1]->set_value(val[0][1]); + spin[2]->set_value(val[1][0]); + spin[3]->set_value(val[1][1]); + spin[4]->set_value(val[2][0]); + spin[5]->set_value(val[2][1]); + + setting = false; +} + +void EditorPropertyTransform2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform2D::_value_changed); +} + +void EditorPropertyTransform2D::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 6; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyTransform2D::EditorPropertyTransform2D() { + GridContainer *g = memnew(GridContainer); + g->set_columns(2); + add_child(g); + + static const char *desc[6] = { "xx", "xy", "yx", "yy", "ox", "oy" }; + for (int i = 0; i < 6; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// BASIS ///////////////////////// + +void EditorPropertyBasis::_value_changed(double val) { + if (setting) + return; + + Basis p; + p[0][0] = spin[0]->get_value(); + p[1][0] = spin[1]->get_value(); + p[2][0] = spin[2]->get_value(); + p[0][1] = spin[3]->get_value(); + p[1][1] = spin[4]->get_value(); + p[2][1] = spin[5]->get_value(); + p[0][2] = spin[6]->get_value(); + p[1][2] = spin[7]->get_value(); + p[2][2] = spin[8]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyBasis::update_property() { + Basis val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val[0][0]); + spin[1]->set_value(val[1][0]); + spin[2]->set_value(val[2][0]); + spin[3]->set_value(val[0][1]); + spin[4]->set_value(val[1][1]); + spin[5]->set_value(val[2][1]); + spin[6]->set_value(val[0][2]); + spin[7]->set_value(val[1][2]); + spin[8]->set_value(val[2][2]); + + setting = false; +} + +void EditorPropertyBasis::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyBasis::_value_changed); +} + +void EditorPropertyBasis::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 9; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyBasis::EditorPropertyBasis() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[9] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz" }; + for (int i = 0; i < 9; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// TRANSFORM ///////////////////////// + +void EditorPropertyTransform::_value_changed(double val) { + if (setting) + return; + + Transform p; + p.basis[0][0] = spin[0]->get_value(); + p.basis[1][0] = spin[1]->get_value(); + p.basis[2][0] = spin[2]->get_value(); + p.basis[0][1] = spin[3]->get_value(); + p.basis[1][1] = spin[4]->get_value(); + p.basis[2][1] = spin[5]->get_value(); + p.basis[0][2] = spin[6]->get_value(); + p.basis[1][2] = spin[7]->get_value(); + p.basis[2][2] = spin[8]->get_value(); + p.origin[0] = spin[9]->get_value(); + p.origin[1] = spin[10]->get_value(); + p.origin[2] = spin[11]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyTransform::update_property() { + Transform val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.basis[0][0]); + spin[1]->set_value(val.basis[1][0]); + spin[2]->set_value(val.basis[2][0]); + spin[3]->set_value(val.basis[0][1]); + spin[4]->set_value(val.basis[1][1]); + spin[5]->set_value(val.basis[2][1]); + spin[6]->set_value(val.basis[0][2]); + spin[7]->set_value(val.basis[1][2]); + spin[8]->set_value(val.basis[2][2]); + spin[9]->set_value(val.origin[0]); + spin[10]->set_value(val.origin[1]); + spin[11]->set_value(val.origin[2]); + + setting = false; +} + +void EditorPropertyTransform::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform::_value_changed); +} + +void EditorPropertyTransform::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 12; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyTransform::EditorPropertyTransform() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[12] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz", "ox", "oy", "oz" }; + for (int i = 0; i < 12; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +////////////// COLOR PICKER ////////////////////// + +void EditorPropertyColor::_color_changed(const Color &p_color) { + + emit_signal("property_changed", get_edited_property(), p_color); +} + +void EditorPropertyColor::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_color_changed"), &EditorPropertyColor::_color_changed); +} + +void EditorPropertyColor::update_property() { + + picker->set_pick_color(get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyColor::setup(bool p_show_alpha) { + picker->set_edit_alpha(p_show_alpha); +} + +EditorPropertyColor::EditorPropertyColor() { + + picker = memnew(ColorPickerButton); + add_child(picker); + picker->set_flat(true); + picker->connect("color_changed", this, "_color_changed"); +} + +////////////// NODE PATH ////////////////////// + +void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { + + emit_signal("property_changed", get_edited_property(), p_path); + update_property(); +} + +void EditorPropertyNodePath::_node_assign() { + if (!scene_tree) { + scene_tree = memnew(SceneTreeDialog); + add_child(scene_tree); + scene_tree->connect("selected", this, "_node_selected"); + } + scene_tree->popup_centered_ratio(); +} + +void EditorPropertyNodePath::_node_clear() { + + emit_signal("property_changed", get_edited_property(), NodePath()); + update_property(); +} + +void EditorPropertyNodePath::update_property() { + + NodePath p = get_edited_object()->get(get_edited_property()); + + assign->set_tooltip(p); + if (p == NodePath()) { + assign->set_icon(Ref<Texture>()); + assign->set_text(TTR("Assign..")); + assign->set_flat(false); + return; + } + assign->set_flat(true); + + Node *base_node = NULL; + if (base_hint != NodePath()) { + if (get_tree()->get_root()->has_node(base_hint)) { + base_node = get_tree()->get_root()->get_node(base_hint); + } + } else { + base_node = Object::cast_to<Node>(get_edited_object()); + } + + if (!base_node || !base_node->has_node(p)) { + assign->set_icon(Ref<Texture>()); + assign->set_text(p); + return; + } + + Node *target_node = base_node->get_node(p); + ERR_FAIL_COND(!target_node); + + assign->set_text(target_node->get_name()); + + Ref<Texture> icon; + if (has_icon(target_node->get_class(), "EditorIcons")) + icon = get_icon(target_node->get_class(), "EditorIcons"); + else + icon = get_icon("Node", "EditorIcons"); + + assign->set_icon(icon); +} + +void EditorPropertyNodePath::setup(const NodePath &p_base_hint) { + + base_hint = p_base_hint; +} + +void EditorPropertyNodePath::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Ref<Texture> t = get_icon("Clear", "EditorIcons"); + clear->set_icon(t); + } +} + +void EditorPropertyNodePath::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_node_selected"), &EditorPropertyNodePath::_node_selected); + ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyNodePath::_node_assign); + ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyNodePath::_node_clear); +} + +EditorPropertyNodePath::EditorPropertyNodePath() { + + HBoxContainer *hbc = memnew(HBoxContainer); + add_child(hbc); + assign = memnew(Button); + assign->set_flat(true); + assign->set_h_size_flags(SIZE_EXPAND_FILL); + assign->set_clip_text(true); + assign->connect("pressed", this, "_node_assign"); + hbc->add_child(assign); + + clear = memnew(Button); + clear->set_flat(true); + clear->connect("pressed", this, "_node_clear"); + hbc->add_child(clear); + + scene_tree = NULL; //do not allocate unnecesarily +} + +////////////// RESOURCE ////////////////////// + +void EditorPropertyResource::_file_selected(const String &p_path) { + + RES res = ResourceLoader::load(p_path); + emit_signal("property_changed", get_edited_property(), res); + update_property(); +} + +void EditorPropertyResource::_menu_option(int p_which) { + + // scene_tree->popup_centered_ratio(); + switch (p_which) { + case OBJ_MENU_LOAD: { + + if (!file) { + file = memnew(EditorFileDialog); + file->connect("file_selected", this, "_file_selected"); + add_child(file); + } + file->set_mode(EditorFileDialog::MODE_OPEN_FILE); + String type = base_type; + + List<String> extensions; + for (int i = 0; i < type.get_slice_count(","); i++) { + + ResourceLoader::get_recognized_extensions_for_type(type.get_slice(",", i), &extensions); + } + + Set<String> valid_extensions; + for (List<String>::Element *E = extensions.front(); E; E = E->next()) { + valid_extensions.insert(E->get()); + } + + file->clear_filters(); + for (Set<String>::Element *E = valid_extensions.front(); E; E = E->next()) { + + file->add_filter("*." + E->get() + " ; " + E->get().to_upper()); + } + + file->popup_centered_ratio(); + } break; + + case OBJ_MENU_EDIT: { + + RES res = get_edited_object()->get(get_edited_property()); + + if (!res.is_null()) { + + emit_signal("resource_selected", get_edited_property(), res); + } + } break; + case OBJ_MENU_CLEAR: { + + emit_signal("property_changed", get_edited_property(), RES()); + update_property(); + + } break; + + case OBJ_MENU_MAKE_UNIQUE: { + + RES res_orig = get_edited_object()->get(get_edited_property()); + if (res_orig.is_null()) + return; + + List<PropertyInfo> property_list; + res_orig->get_property_list(&property_list); + List<Pair<String, Variant> > propvalues; + + for (List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { + + Pair<String, Variant> p; + PropertyInfo &pi = E->get(); + if (pi.usage & PROPERTY_USAGE_STORAGE) { + + p.first = pi.name; + p.second = res_orig->get(pi.name); + } + + propvalues.push_back(p); + } + + String orig_type = res_orig->get_class(); + + Object *inst = ClassDB::instance(orig_type); + + Ref<Resource> res = Ref<Resource>(Object::cast_to<Resource>(inst)); + + ERR_FAIL_COND(res.is_null()); + + for (List<Pair<String, Variant> >::Element *E = propvalues.front(); E; E = E->next()) { + + Pair<String, Variant> &p = E->get(); + res->set(p.first, p.second); + } + + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + + case OBJ_MENU_COPY: { + RES res = get_edited_object()->get(get_edited_property()); + + EditorSettings::get_singleton()->set_resource_clipboard(res); + + } break; + case OBJ_MENU_PASTE: { + + RES res = EditorSettings::get_singleton()->get_resource_clipboard(); + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + case OBJ_MENU_NEW_SCRIPT: { + + if (Object::cast_to<Node>(get_edited_object())) { + EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(get_edited_object())); + } + + } break; + case OBJ_MENU_SHOW_IN_FILE_SYSTEM: { + RES res = get_edited_object()->get(get_edited_property()); + + FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); + file_system_dock->navigate_to_path(res->get_path()); + // Ensure that the FileSystem dock is visible. + TabContainer *tab_container = (TabContainer *)file_system_dock->get_parent_control(); + tab_container->set_current_tab(file_system_dock->get_position_in_parent()); + } break; + default: { + + RES res = get_edited_object()->get(get_edited_property()); + + if (p_which >= CONVERT_BASE_ID) { + + int to_type = p_which - CONVERT_BASE_ID; + + Vector<Ref<EditorResourceConversionPlugin> > conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(res); + + ERR_FAIL_INDEX(to_type, conversions.size()); + + Ref<Resource> new_res = conversions[to_type]->convert(res); + + emit_signal("property_changed", get_edited_property(), new_res); + update_property(); + break; + } + ERR_FAIL_COND(inheritors_array.empty()); + + String intype = inheritors_array[p_which - TYPE_BASE_ID]; + + if (intype == "ViewportTexture") { + + if (!scene_tree) { + scene_tree = memnew(SceneTreeDialog); + add_child(scene_tree); + scene_tree->connect("selected", this, "_viewport_selected"); + scene_tree->set_title(TTR("Pick a Viewport")); + } + scene_tree->popup_centered_ratio(); + + return; + } + + Object *obj = ClassDB::instance(intype); + + if (!obj) { + obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource"); + } + + ERR_BREAK(!obj); + Resource *resp = Object::cast_to<Resource>(obj); + ERR_BREAK(!resp); + if (get_edited_object() && base_type != String() && base_type == "Script") { + //make visual script the right type + res->call("set_instance_base_type", get_edited_object()->get_class()); + } + + res = Ref<Resource>(resp); + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + } +} + +void EditorPropertyResource::_resource_preview(const String &p_path, const Ref<Texture> &p_preview, ObjectID p_obj) { + + RES p = get_edited_object()->get(get_edited_property()); + if (p.is_valid() && p->get_instance_id() == p_obj) { + if (p_preview.is_valid()) { + assign->set_icon(p_preview); + } + } +} + +void EditorPropertyResource::_update_menu() { + //////////////////// UPDATE MENU ////////////////////////// + RES res = get_edited_object()->get(get_edited_property()); + + menu->clear(); + + if (get_edited_property() == "script" && base_type == "Script" && Object::cast_to<Node>(get_edited_object())) { + menu->add_icon_item(get_icon("Script", "EditorIcons"), TTR("New Script"), OBJ_MENU_NEW_SCRIPT); + menu->add_separator(); + } else if (base_type != "") { + int idx = 0; + + Vector<EditorData::CustomType> custom_resources; + + if (EditorNode::get_editor_data().get_custom_types().has("Resource")) { + custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"]; + } + + for (int i = 0; i < base_type.get_slice_count(","); i++) { + + String base = base_type.get_slice(",", i); + + Set<String> valid_inheritors; + valid_inheritors.insert(base); + List<StringName> inheritors; + ClassDB::get_inheriters_from_class(base.strip_edges(), &inheritors); + + for (int i = 0; i < custom_resources.size(); i++) { + inheritors.push_back(custom_resources[i].name); + } + + List<StringName>::Element *E = inheritors.front(); + while (E) { + valid_inheritors.insert(E->get()); + E = E->next(); + } + + for (Set<String>::Element *E = valid_inheritors.front(); E; E = E->next()) { + String t = E->get(); + + bool is_custom_resource = false; + Ref<Texture> icon; + if (!custom_resources.empty()) { + for (int i = 0; i < custom_resources.size(); i++) { + if (custom_resources[i].name == t) { + is_custom_resource = true; + if (custom_resources[i].icon.is_valid()) + icon = custom_resources[i].icon; + break; + } + } + } + + if (!is_custom_resource && !ClassDB::can_instance(t)) + continue; + + inheritors_array.push_back(t); + + int id = TYPE_BASE_ID + idx; + + if (!icon.is_valid() && has_icon(t, "EditorIcons")) { + icon = get_icon(t, "EditorIcons"); + } + + if (icon.is_valid()) { + + menu->add_icon_item(icon, vformat(TTR("New %s"), t), id); + } else { + + menu->add_item(vformat(TTR("New %s"), t), id); + } + + idx++; + } + } + + if (menu->get_item_count()) + menu->add_separator(); + } + + menu->add_icon_item(get_icon("Load", "EditorIcons"), TTR("Load"), OBJ_MENU_LOAD); + + if (!res.is_null()) { + + menu->add_icon_item(get_icon("Edit", "EditorIcons"), TTR("Edit"), OBJ_MENU_EDIT); + menu->add_icon_item(get_icon("Clear", "EditorIcons"), TTR("Clear"), OBJ_MENU_CLEAR); + menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE); + RES r = res; + if (r.is_valid() && r->get_path().is_resource_file()) { + menu->add_separator(); + menu->add_item(TTR("Show in File System"), OBJ_MENU_SHOW_IN_FILE_SYSTEM); + } + } else { + } + + RES cb = EditorSettings::get_singleton()->get_resource_clipboard(); + bool paste_valid = false; + if (cb.is_valid()) { + if (base_type == "") + paste_valid = true; + else + for (int i = 0; i < base_type.get_slice_count(","); i++) + if (ClassDB::is_parent_class(cb->get_class(), base_type.get_slice(",", i))) { + paste_valid = true; + break; + } + } + + if (!res.is_null() || paste_valid) { + menu->add_separator(); + + if (!res.is_null()) { + + menu->add_item(TTR("Copy"), OBJ_MENU_COPY); + } + + if (paste_valid) { + + menu->add_item(TTR("Paste"), OBJ_MENU_PASTE); + } + } + + if (!res.is_null()) { + + Vector<Ref<EditorResourceConversionPlugin> > conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(res); + if (conversions.size()) { + menu->add_separator(); + } + for (int i = 0; i < conversions.size(); i++) { + String what = conversions[i]->converts_to(); + Ref<Texture> icon; + if (has_icon(what, "EditorIcons")) { + + icon = get_icon(what, "EditorIcons"); + } else { + + icon = get_icon(what, "Resource"); + } + + menu->add_icon_item(icon, vformat(TTR("Convert To %s"), what), CONVERT_BASE_ID + i); + } + } + + Rect2 gt = get_global_rect(); + int ms = menu->get_combined_minimum_size().width; + Vector2 popup_pos = gt.position + gt.size - Vector2(ms, 0); + menu->set_position(popup_pos); + menu->popup(); +} + +void EditorPropertyResource::update_property() { + + RES res = get_edited_object()->get(get_edited_property()); + + if (res == RES()) { + assign->set_icon(Ref<Texture>()); + assign->set_text(TTR("[empty]")); + assign->set_disabled(true); + } else { + assign->set_disabled(false); + + Ref<Texture> icon; + if (has_icon(res->get_class(), "EditorIcons")) + icon = get_icon(res->get_class(), "EditorIcons"); + else + icon = get_icon("Node", "EditorIcons"); + + assign->set_icon(icon); + + if (res->get_name() != String()) { + assign->set_text(res->get_name()); + } else if (res->get_path().is_resource_file()) { + assign->set_text(res->get_name()); + assign->set_tooltip(res->get_path()); + } else { + assign->set_text(res->get_class()); + } + + if (res->get_path().is_resource_file()) { + assign->set_tooltip(res->get_path()); + } + + //preview will override the above, so called at the end + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_resource_preview", res->get_instance_id()); + } +} + +void EditorPropertyResource::_resource_selected() { + RES res = get_edited_object()->get(get_edited_property()); + + if (!res.is_null()) { + + emit_signal("resource_selected", get_edited_property(), res); + } +} + +void EditorPropertyResource::setup(const String &p_base_type) { + base_type = p_base_type; +} + +void EditorPropertyResource::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Ref<Texture> t = get_icon("select_arrow", "Tree"); + edit->set_icon(t); + } +} + +void EditorPropertyResource::_viewport_selected(const NodePath &p_path) { + + Node *to_node = get_node(p_path); + if (!Object::cast_to<Viewport>(to_node)) { + EditorNode::get_singleton()->show_warning(TTR("Selected node is not a Viewport!")); + return; + } + + Ref<ViewportTexture> vt; + vt.instance(); + vt->set_viewport_path_in_scene(get_tree()->get_edited_scene_root()->get_path_to(to_node)); + vt->setup_local_to_scene(); + + emit_signal("property_changed", get_edited_property(), vt); + update_property(); +} +void EditorPropertyResource::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_file_selected"), &EditorPropertyResource::_file_selected); + ClassDB::bind_method(D_METHOD("_menu_option"), &EditorPropertyResource::_menu_option); + ClassDB::bind_method(D_METHOD("_update_menu"), &EditorPropertyResource::_update_menu); + ClassDB::bind_method(D_METHOD("_resource_preview"), &EditorPropertyResource::_resource_preview); + ClassDB::bind_method(D_METHOD("_resource_selected"), &EditorPropertyResource::_resource_selected); + ClassDB::bind_method(D_METHOD("_viewport_selected"), &EditorPropertyResource::_viewport_selected); +} + +EditorPropertyResource::EditorPropertyResource() { + + HBoxContainer *hbc = memnew(HBoxContainer); + add_child(hbc); + assign = memnew(Button); + assign->set_flat(true); + assign->set_h_size_flags(SIZE_EXPAND_FILL); + assign->set_clip_text(true); + assign->connect("pressed", this, "_resource_selected"); + hbc->add_child(assign); + + menu = memnew(PopupMenu); + add_child(menu); + edit = memnew(Button); + edit->set_flat(true); + menu->connect("id_pressed", this, "_menu_option"); + edit->connect("pressed", this, "_update_menu"); + hbc->add_child(edit); + + file = NULL; + scene_tree = NULL; +} + +////////////// DEFAULT PLUGIN ////////////////////// + +bool EditorInspectorDefaultPlugin::can_handle(Object *p_object) { + return true; //can handle everything +} + +void EditorInspectorDefaultPlugin::parse_begin(Object *p_object) { + //do none +} + +bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + switch (p_type) { + + // atomic types + case Variant::BOOL: { + EditorPropertyCheck *editor = memnew(EditorPropertyCheck); + add_property_editor(p_path, editor); + } break; + case Variant::INT: { + + if (p_hint == PROPERTY_HINT_ENUM) { + EditorPropertyEnum *editor = memnew(EditorPropertyEnum); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + } else if (p_hint == PROPERTY_HINT_FLAGS) { + EditorPropertyFlags *editor = memnew(EditorPropertyFlags); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + } else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_2D_RENDER || p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER) { + + EditorPropertyLayers::LayerType lt; + switch (p_hint) { + case PROPERTY_HINT_LAYERS_2D_RENDER: + lt = EditorPropertyLayers::LAYER_RENDER_2D; + break; + case PROPERTY_HINT_LAYERS_2D_PHYSICS: + lt = EditorPropertyLayers::LAYER_PHYSICS_2D; + break; + case PROPERTY_HINT_LAYERS_3D_RENDER: + lt = EditorPropertyLayers::LAYER_RENDER_3D; + break; + case PROPERTY_HINT_LAYERS_3D_PHYSICS: + lt = EditorPropertyLayers::LAYER_PHYSICS_3D; + break; + default: {} //compiler could be smarter here and realize this cant happen + } + EditorPropertyLayers *editor = memnew(EditorPropertyLayers); + editor->setup(lt); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_OBJECT_ID) { + + EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); + editor->setup(p_hint_text); + add_property_editor(p_path, editor); + + } else { + EditorPropertyInteger *editor = memnew(EditorPropertyInteger); + int min = 0, max = 65535; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_int(); + max = p_hint_text.get_slice(",", 1).to_int(); + } + + editor->setup(min, max); + + add_property_editor(p_path, editor); + } + } break; + case Variant::REAL: { + + if (p_hint == PROPERTY_HINT_EXP_EASING) { + EditorPropertyEasing *editor = memnew(EditorPropertyEasing); + bool full = true; + bool flip = false; + Vector<String> hints = p_hint_text.split(","); + for (int i = 0; i < hints.size(); i++) { + String h = hints[i].strip_edges(); + if (h == "attenuation") { + flip = true; + } + if (h == "inout") { + full = true; + } + } + + editor->setup(full, flip); + add_property_editor(p_path, editor); + + } else { + EditorPropertyFloat *editor = memnew(EditorPropertyFloat); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + bool exp_range = false; + + if ((p_hint == PROPERTY_HINT_RANGE || p_hint == PROPERTY_HINT_EXP_RANGE) && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + exp_range = p_hint == PROPERTY_HINT_EXP_RANGE; + } + + editor->setup(min, max, step, hide_slider, exp_range); + + add_property_editor(p_path, editor); + } + } break; + case Variant::STRING: { + + if (p_hint == PROPERTY_HINT_ENUM) { + EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_MULTILINE_TEXT) { + EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE) { + + Vector<String> extensions = p_hint_text.split(","); + bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE; + bool folder = p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_GLOBAL_DIR; + EditorPropertyPath *editor = memnew(EditorPropertyPath); + editor->setup(extensions, folder, global); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_METHOD_OF_VARIANT_TYPE || + p_hint == PROPERTY_HINT_METHOD_OF_BASE_TYPE || + p_hint == PROPERTY_HINT_METHOD_OF_INSTANCE || + p_hint == PROPERTY_HINT_METHOD_OF_SCRIPT || + p_hint == PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE || + p_hint == PROPERTY_HINT_PROPERTY_OF_BASE_TYPE || + p_hint == PROPERTY_HINT_PROPERTY_OF_INSTANCE || + p_hint == PROPERTY_HINT_PROPERTY_OF_SCRIPT) { + + EditorPropertyMember *editor = memnew(EditorPropertyMember); + + EditorPropertyMember::Type type = EditorPropertyMember::MEMBER_METHOD_OF_BASE_TYPE; + switch (p_hint) { + case PROPERTY_HINT_METHOD_OF_BASE_TYPE: type = EditorPropertyMember::MEMBER_METHOD_OF_BASE_TYPE; break; + case PROPERTY_HINT_METHOD_OF_INSTANCE: type = EditorPropertyMember::MEMBER_METHOD_OF_INSTANCE; break; + case PROPERTY_HINT_METHOD_OF_SCRIPT: type = EditorPropertyMember::MEMBER_METHOD_OF_SCRIPT; break; + case PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_VARIANT_TYPE; break; + case PROPERTY_HINT_PROPERTY_OF_BASE_TYPE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_BASE_TYPE; break; + case PROPERTY_HINT_PROPERTY_OF_INSTANCE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_INSTANCE; break; + case PROPERTY_HINT_PROPERTY_OF_SCRIPT: type = EditorPropertyMember::MEMBER_PROPERTY_OF_SCRIPT; break; + default: {} + } + editor->setup(type, p_hint_text); + add_property_editor(p_path, editor); + + } else { + + EditorPropertyText *editor = memnew(EditorPropertyText); + add_property_editor(p_path, editor); + } + } break; + + // math types + + case Variant::VECTOR2: { + EditorPropertyVector2 *editor = memnew(EditorPropertyVector2); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; // 5 + case Variant::RECT2: { + EditorPropertyRect2 *editor = memnew(EditorPropertyRect2); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::VECTOR3: { + EditorPropertyVector3 *editor = memnew(EditorPropertyVector3); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + case Variant::TRANSFORM2D: { + EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + case Variant::PLANE: { + EditorPropertyPlane *editor = memnew(EditorPropertyPlane); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::QUAT: { + EditorPropertyQuat *editor = memnew(EditorPropertyQuat); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; // 10 + case Variant::AABB: { + EditorPropertyAABB *editor = memnew(EditorPropertyAABB); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::BASIS: { + EditorPropertyBasis *editor = memnew(EditorPropertyBasis); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::TRANSFORM: { + EditorPropertyTransform *editor = memnew(EditorPropertyTransform); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + + // misc types + case Variant::COLOR: { + EditorPropertyColor *editor = memnew(EditorPropertyColor); + editor->setup(p_hint != PROPERTY_HINT_COLOR_NO_ALPHA); + add_property_editor(p_path, editor); + } break; + case Variant::NODE_PATH: { + + EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath); + if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { + editor->setup(p_hint_text); + } + add_property_editor(p_path, editor); + + } break; // 15 + case Variant::_RID: { + } break; + case Variant::OBJECT: { + EditorPropertyResource *editor = memnew(EditorPropertyResource); + editor->setup(p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); + add_property_editor(p_path, editor); + + } break; + case Variant::DICTIONARY: { + } break; + case Variant::ARRAY: { + } break; + + // arrays + case Variant::POOL_BYTE_ARRAY: { + } break; // 20 + case Variant::POOL_INT_ARRAY: { + } break; + case Variant::POOL_REAL_ARRAY: { + } break; + case Variant::POOL_STRING_ARRAY: { + } break; + case Variant::POOL_VECTOR2_ARRAY: { + } break; + case Variant::POOL_VECTOR3_ARRAY: { + } break; // 25 + case Variant::POOL_COLOR_ARRAY: { + } break; + default: {} + } + + return false; //can be overriden, although it will most likely be last anyway +} + +void EditorInspectorDefaultPlugin::parse_end() { + //do none +} diff --git a/editor/editor_properties.h b/editor/editor_properties.h new file mode 100644 index 0000000000..0a831bfcfe --- /dev/null +++ b/editor/editor_properties.h @@ -0,0 +1,490 @@ +#ifndef EDITOR_PROPERTIES_H +#define EDITOR_PROPERTIES_H + +#include "editor/create_dialog.h" +#include "editor/editor_file_system.h" +#include "editor/editor_inspector.h" +#include "editor/editor_spin_slider.h" +#include "editor/property_selector.h" +#include "editor/scene_tree_editor.h" +#include "scene/gui/color_picker.h" + +class EditorPropertyText : public EditorProperty { + GDCLASS(EditorPropertyText, EditorProperty) + LineEdit *text; + + bool updating; + void _text_changed(const String &p_string); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyText(); +}; + +class EditorPropertyMultilineText : public EditorProperty { + GDCLASS(EditorPropertyMultilineText, EditorProperty) + TextEdit *text; + + AcceptDialog *big_text_dialog; + TextEdit *big_text; + Button *open_big_text; + + void _big_text_changed(); + void _text_changed(); + void _open_big_text(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyMultilineText(); +}; + +class EditorPropertyTextEnum : public EditorProperty { + GDCLASS(EditorPropertyTextEnum, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyTextEnum(); +}; + +class EditorPropertyPath : public EditorProperty { + GDCLASS(EditorPropertyPath, EditorProperty) + Vector<String> extensions; + bool folder; + bool global; + EditorFileDialog *dialog; + Button *path; + + void _path_selected(const String &p_path); + void _path_pressed(); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_extensions, bool p_folder, bool p_global); + virtual void update_property(); + EditorPropertyPath(); +}; + +class EditorPropertyMember : public EditorProperty { + GDCLASS(EditorPropertyMember, EditorProperty) +public: + enum Type { + MEMBER_METHOD_OF_VARIANT_TYPE, ///< a method of a type + MEMBER_METHOD_OF_BASE_TYPE, ///< a method of a base type + MEMBER_METHOD_OF_INSTANCE, ///< a method of an instance + MEMBER_METHOD_OF_SCRIPT, ///< a method of a script & base + MEMBER_PROPERTY_OF_VARIANT_TYPE, ///< a property of a type + MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type + MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance + MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base + + }; + +private: + Type hint; + PropertySelector *selector; + Button *property; + String hint_text; + + void _property_selected(const String &p_selected); + void _property_select(); + +protected: + static void _bind_methods(); + +public: + void setup(Type p_hint, const String &p_hint_text); + virtual void update_property(); + EditorPropertyMember(); +}; + +class EditorPropertyCheck : public EditorProperty { + GDCLASS(EditorPropertyCheck, EditorProperty) + CheckBox *checkbox; + + void _checkbox_pressed(); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyCheck(); +}; + +class EditorPropertyEnum : public EditorProperty { + GDCLASS(EditorPropertyEnum, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyEnum(); +}; + +class EditorPropertyFlags : public EditorProperty { + GDCLASS(EditorPropertyFlags, EditorProperty) + VBoxContainer *vbox; + Vector<CheckBox *> flags; + Vector<int> flag_indices; + + void _flag_toggled(); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyFlags(); +}; + +class EditorPropertyLayersGrid; + +class EditorPropertyLayers : public EditorProperty { + GDCLASS(EditorPropertyLayers, EditorProperty) +public: + enum LayerType { + LAYER_PHYSICS_2D, + LAYER_RENDER_2D, + LAYER_PHYSICS_3D, + LAYER_RENDER_3D, + }; + +private: + EditorPropertyLayersGrid *grid; + void _grid_changed(uint32_t p_grid); + LayerType layer_type; + PopupMenu *layers; + Button *button; + + void _button_pressed(); + void _menu_pressed(int p_menu); + +protected: + static void _bind_methods(); + +public: + void setup(LayerType p_layer_type); + virtual void update_property(); + EditorPropertyLayers(); +}; + +class EditorPropertyInteger : public EditorProperty { + GDCLASS(EditorPropertyInteger, EditorProperty) + EditorSpinSlider *spin; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(int p_min, int p_max); + EditorPropertyInteger(); +}; + +class EditorPropertyObjectID : public EditorProperty { + GDCLASS(EditorPropertyObjectID, EditorProperty) + Button *edit; + String base_type; + void _edit_pressed(); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(const String &p_base_type); + EditorPropertyObjectID(); +}; + +class EditorPropertyFloat : public EditorProperty { + GDCLASS(EditorPropertyFloat, EditorProperty) + EditorSpinSlider *spin; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range); + EditorPropertyFloat(); +}; + +class EditorPropertyEasing : public EditorProperty { + GDCLASS(EditorPropertyEasing, EditorProperty) + Control *easing_draw; + ToolButton *button_out, *button_in, *button_linear, *button_constant; + ToolButton *button_in_out, *button_out_in; + VBoxContainer *vb; + + bool flip; + + void _drag_easing(const Ref<InputEvent> &p_ev); + void _draw_easing(); + void _notification(int p_what); + void _set_preset(float p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(bool p_full, bool p_flip); + EditorPropertyEasing(); +}; + +class EditorPropertyVector2 : public EditorProperty { + GDCLASS(EditorPropertyVector2, EditorProperty) + EditorSpinSlider *spin[2]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyVector2(); +}; + +class EditorPropertyRect2 : public EditorProperty { + GDCLASS(EditorPropertyRect2, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyRect2(); +}; + +class EditorPropertyVector3 : public EditorProperty { + GDCLASS(EditorPropertyVector3, EditorProperty) + EditorSpinSlider *spin[3]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyVector3(); +}; + +class EditorPropertyPlane : public EditorProperty { + GDCLASS(EditorPropertyPlane, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyPlane(); +}; + +class EditorPropertyQuat : public EditorProperty { + GDCLASS(EditorPropertyQuat, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyQuat(); +}; + +class EditorPropertyAABB : public EditorProperty { + GDCLASS(EditorPropertyAABB, EditorProperty) + EditorSpinSlider *spin[6]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyAABB(); +}; + +class EditorPropertyTransform2D : public EditorProperty { + GDCLASS(EditorPropertyTransform2D, EditorProperty) + EditorSpinSlider *spin[6]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyTransform2D(); +}; + +class EditorPropertyBasis : public EditorProperty { + GDCLASS(EditorPropertyBasis, EditorProperty) + EditorSpinSlider *spin[9]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyBasis(); +}; + +class EditorPropertyTransform : public EditorProperty { + GDCLASS(EditorPropertyTransform, EditorProperty) + EditorSpinSlider *spin[12]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyTransform(); +}; + +class EditorPropertyColor : public EditorProperty { + GDCLASS(EditorPropertyColor, EditorProperty) + ColorPickerButton *picker; + void _color_changed(const Color &p_color); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(bool p_show_alpha); + EditorPropertyColor(); +}; + +class EditorPropertyNodePath : public EditorProperty { + GDCLASS(EditorPropertyNodePath, EditorProperty) + Button *assign; + Button *clear; + SceneTreeDialog *scene_tree; + NodePath base_hint; + + void _node_selected(const NodePath &p_path); + void _node_assign(); + void _node_clear(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual void update_property(); + void setup(const NodePath &p_base_hint); + EditorPropertyNodePath(); +}; + +class EditorPropertyResource : public EditorProperty { + GDCLASS(EditorPropertyResource, EditorProperty) + + enum MenuOption { + + OBJ_MENU_LOAD = 0, + OBJ_MENU_EDIT = 1, + OBJ_MENU_CLEAR = 2, + OBJ_MENU_MAKE_UNIQUE = 3, + OBJ_MENU_COPY = 4, + OBJ_MENU_PASTE = 5, + OBJ_MENU_NEW_SCRIPT = 6, + OBJ_MENU_SHOW_IN_FILE_SYSTEM = 7, + TYPE_BASE_ID = 100, + CONVERT_BASE_ID = 1000 + + }; + + Button *assign; + Button *edit; + PopupMenu *menu; + EditorFileDialog *file; + Vector<String> inheritors_array; + + String base_type; + + SceneTreeDialog *scene_tree; + + void _file_selected(const String &p_path); + void _menu_option(int p_which); + void _resource_preview(const String &p_path, const Ref<Texture> &p_preview, ObjectID p_obj); + void _resource_selected(); + void _viewport_selected(const NodePath &p_path); + + void _update_menu(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual void update_property(); + void setup(const String &p_base_type); + EditorPropertyResource(); +}; + +/////////////////////////////////////////////////// +/// \brief The EditorInspectorDefaultPlugin class +/// +class EditorInspectorDefaultPlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorDefaultPlugin, EditorInspectorPlugin) + +public: + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +#endif // EDITOR_PROPERTIES_H diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index f9fc86278a..fe0392334c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -299,7 +299,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/main_font", ""); hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font_bold", ""); - hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_bold_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/code_font", ""); hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/dim_editor_on_dialog_popup", true); @@ -371,6 +371,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/line_numbers/line_numbers_zero_padded", false); _initial_set("text_editor/line_numbers/show_breakpoint_gutter", true); _initial_set("text_editor/line_numbers/code_folding", true); + _initial_set("text_editor/line_numbers/word_wrap", false); _initial_set("text_editor/line_numbers/show_line_length_guideline", false); _initial_set("text_editor/line_numbers/line_length_guideline_column", 80); hints["text_editor/line_numbers/line_length_guideline_column"] = PropertyInfo(Variant::INT, "text_editor/line_numbers/line_length_guideline_column", PROPERTY_HINT_RANGE, "20, 160, 1"); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp new file mode 100644 index 0000000000..9764364273 --- /dev/null +++ b/editor/editor_spin_slider.cpp @@ -0,0 +1,315 @@ +#include "editor_spin_slider.h" +#include "editor_scale.h" +#include "os/input.h" +String EditorSpinSlider::get_text_value() const { + int zeros = Math::step_decimals(get_step()); + return String::num(get_value(), zeros); +} +void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + + if (mb->is_pressed()) { + + if (updown_offset != -1 && mb->get_position().x > updown_offset) { + //there is an updown, so use it. + if (mb->get_position().y < get_size().height / 2) { + set_value(get_value() + get_step()); + } else { + set_value(get_value() - get_step()); + } + return; + } else { + + grabbing_spinner_attempt = true; + grabbing_spinner = false; + grabbing_spinner_mouse_pos = Input::get_singleton()->get_mouse_position(); + } + } else { + + if (grabbing_spinner_attempt) { + + if (grabbing_spinner) { + + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse_position(grabbing_spinner_mouse_pos); + update(); + } else { + Rect2 gr = get_global_rect(); + value_input->set_text(get_text_value()); + value_input->set_position(gr.position); + value_input->set_size(gr.size); + value_input->call_deferred("show_modal"); + value_input->call_deferred("grab_focus"); + value_input->call_deferred("select_all"); + } + + grabbing_spinner = false; + grabbing_spinner_attempt = false; + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + + if (grabbing_spinner_attempt) { + + if (!grabbing_spinner) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); + grabbing_spinner = true; + } + + double v = get_value(); + + double diff_x = mm->get_relative().x; + diff_x = Math::pow(ABS(diff_x), 1.8) * SGN(diff_x); + diff_x *= 0.1; + + v += diff_x * get_step(); + + set_value(v); + + } else if (updown_offset != -1) { + bool new_hover = (mm->get_position().x > updown_offset); + if (new_hover != hover_updown) { + hover_updown = new_hover; + update(); + } + } + } + + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->is_pressed() && k->is_action("ui_accept")) { + Rect2 gr = get_global_rect(); + value_input->set_text(get_text_value()); + value_input->set_position(gr.position); + value_input->set_size(gr.size); + value_input->call_deferred("show_modal"); + value_input->call_deferred("grab_focus"); + value_input->call_deferred("select_all"); + } +} + +void EditorSpinSlider::_value_input_closed() { + set_value(value_input->get_text().to_double()); +} + +void EditorSpinSlider::_value_input_entered(const String &p_text) { + set_value(p_text.to_double()); + value_input->hide(); +} + +void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + + if (mb->is_pressed()) { + + grabbing_grabber = true; + grabbing_ratio = get_as_ratio(); + grabbing_from = grabber->get_transform().xform(mb->get_position()).x; + } else { + grabbing_grabber = false; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && grabbing_grabber) { + + float grabbing_ofs = (grabber->get_transform().xform(mm->get_position()).x - grabbing_from) / float(grabber_range); + set_as_ratio(grabbing_ratio + grabbing_ofs); + update(); + } +} + +void EditorSpinSlider::_notification(int p_what) { + + if (p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT || p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT) { + if (grabbing_spinner) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + grabbing_spinner = false; + grabbing_spinner_attempt = false; + } + } + + if (p_what == NOTIFICATION_DRAW) { + + updown_offset = -1; + + Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); + draw_style_box(sb, Rect2(Vector2(), get_size())); + Ref<Font> font = get_font("font", "LineEdit"); + + int avail_width = get_size().width - sb->get_minimum_size().width - sb->get_minimum_size().width; + avail_width -= font->get_string_size(label).width; + Ref<Texture> updown = get_icon("updown", "SpinBox"); + + if (get_step() == 1) { + avail_width -= updown->get_width(); + } + + if (has_focus()) { + Ref<StyleBox> focus = get_stylebox("focus", "LineEdit"); + draw_style_box(focus, Rect2(Vector2(), get_size())); + } + + String numstr = get_text_value(); + + int vofs = (get_size().height - font->get_height()) / 2 + font->get_ascent(); + + Color fc = get_color("font_color", "LineEdit"); + + int label_ofs = sb->get_offset().x + avail_width; + draw_string(font, Vector2(label_ofs, vofs), label, fc * Color(1, 1, 1, 0.5)); + draw_string(font, Vector2(sb->get_offset().x, vofs), numstr, fc, avail_width); + + if (get_step() == 1) { + Ref<Texture> updown = get_icon("updown", "SpinBox"); + int updown_vofs = (get_size().height - updown->get_height()) / 2; + updown_offset = get_size().width - sb->get_margin(MARGIN_RIGHT) - updown->get_width(); + Color c(1, 1, 1); + if (hover_updown) { + c *= Color(1.2, 1.2, 1.2); + } + draw_texture(updown, Vector2(updown_offset, updown_vofs), c); + if (grabber->is_visible()) { + grabber->hide(); + } + } else if (!hide_slider) { + int grabber_w = 4 * EDSCALE; + int width = get_size().width - sb->get_minimum_size().width - grabber_w; + int ofs = sb->get_offset().x; + int svofs = (get_size().height + vofs) / 2 - 1; + Color c = fc; + c.a = 0.2; + + draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); + int gofs = get_as_ratio() * width; + c.a = 0.9; + Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE); + draw_rect(grabber_rect, c); + + bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner; + if (grabber->is_visible() != display_grabber) { + if (display_grabber) { + grabber->show(); + } else { + grabber->hide(); + } + } + + if (display_grabber) { + Ref<Texture> grabber_tex; + if (mouse_over_grabber) { + grabber_tex = get_icon("grabber_highlight", "HSlider"); + } else { + grabber_tex = get_icon("grabber", "HSlider"); + } + + if (grabber->get_texture() != grabber_tex) { + grabber->set_texture(grabber_tex); + } + + grabber->set_size(Size2(0, 0)); + grabber->set_position(get_global_position() + grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5); + grabber_range = width; + } + } + } + + if (p_what == NOTIFICATION_MOUSE_ENTER) { + + mouse_over_spin = true; + update(); + } + if (p_what == NOTIFICATION_MOUSE_EXIT) { + + mouse_over_spin = false; + update(); + } +} + +Size2 EditorSpinSlider::get_minimum_size() const { + + Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); + Ref<Font> font = get_font("font", "LineEdit"); + + Size2 ms = sb->get_minimum_size(); + ms.height += font->get_height(); + + return ms; +} + +void EditorSpinSlider::set_hide_slider(bool p_hide) { + hide_slider = p_hide; + update(); +} + +bool EditorSpinSlider::is_hiding_slider() const { + return hide_slider; +} + +void EditorSpinSlider::set_label(const String &p_label) { + label = p_label; + update(); +} + +String EditorSpinSlider::get_label() const { + return label; +} + +void EditorSpinSlider::_grabber_mouse_entered() { + mouse_over_grabber = true; + update(); +} + +void EditorSpinSlider::_grabber_mouse_exited() { + mouse_over_grabber = false; + update(); +} + +void EditorSpinSlider::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label); + ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label); + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input); + ClassDB::bind_method(D_METHOD("_grabber_mouse_entered"), &EditorSpinSlider::_grabber_mouse_entered); + ClassDB::bind_method(D_METHOD("_grabber_mouse_exited"), &EditorSpinSlider::_grabber_mouse_exited); + ClassDB::bind_method(D_METHOD("_grabber_gui_input"), &EditorSpinSlider::_grabber_gui_input); + ClassDB::bind_method(D_METHOD("_value_input_closed"), &EditorSpinSlider::_value_input_closed); + ClassDB::bind_method(D_METHOD("_value_input_entered"), &EditorSpinSlider::_value_input_entered); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); +} + +EditorSpinSlider::EditorSpinSlider() { + + grabbing_spinner_attempt = false; + grabbing_spinner = false; + + set_focus_mode(FOCUS_ALL); + updown_offset = -1; + hover_updown = false; + grabber = memnew(TextureRect); + add_child(grabber); + grabber->hide(); + grabber->set_as_toplevel(true); + grabber->set_mouse_filter(MOUSE_FILTER_STOP); + grabber->connect("mouse_entered", this, "_grabber_mouse_entered"); + grabber->connect("mouse_exited", this, "_grabber_mouse_exited"); + grabber->connect("gui_input", this, "_grabber_gui_input"); + mouse_over_spin = false; + mouse_over_grabber = false; + grabbing_grabber = false; + grabber_range = 1; + value_input = memnew(LineEdit); + add_child(value_input); + value_input->set_as_toplevel(true); + value_input->hide(); + value_input->connect("modal_closed", this, "_value_input_closed"); + value_input->connect("text_entered", this, "_value_input_entered"); + hide_slider = false; +} diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h new file mode 100644 index 0000000000..ac8d9e15d6 --- /dev/null +++ b/editor/editor_spin_slider.h @@ -0,0 +1,57 @@ +#ifndef EDITOR_SPIN_SLIDER_H +#define EDITOR_SPIN_SLIDER_H + +#include "scene/gui/line_edit.h" +#include "scene/gui/range.h" +#include "scene/gui/texture_rect.h" + +class EditorSpinSlider : public Range { + GDCLASS(EditorSpinSlider, Range) + + String label; + int updown_offset; + bool hover_updown; + bool mouse_hover; + + TextureRect *grabber; + int grabber_range; + + bool mouse_over_spin; + bool mouse_over_grabber; + + bool grabbing_grabber; + int grabbing_from; + float grabbing_ratio; + + bool grabbing_spinner_attempt; + bool grabbing_spinner; + Vector2 grabbing_spinner_mouse_pos; + + LineEdit *value_input; + + void _grabber_gui_input(const Ref<InputEvent> &p_event); + void _value_input_closed(); + void _value_input_entered(const String &); + + bool hide_slider; + +protected: + void _notification(int p_what); + void _gui_input(const Ref<InputEvent> &p_event); + static void _bind_methods(); + void _grabber_mouse_entered(); + void _grabber_mouse_exited(); + +public: + String get_text_value() const; + void set_label(const String &p_label); + String get_label() const; + + void set_hide_slider(bool p_hide); + bool is_hiding_slider() const; + + virtual Size2 get_minimum_size() const; + EditorSpinSlider(); +}; + +#endif // EDITOR_SPIN_SLIDER_H diff --git a/editor/icons/icon_GUI_slider_grabber.svg b/editor/icons/icon_GUI_slider_grabber.svg index b1dcf980a5..b8e6f0a654 100644 --- a/editor/icons/icon_GUI_slider_grabber.svg +++ b/editor/icons/icon_GUI_slider_grabber.svg @@ -1,5 +1,82 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 15.999999" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<circle cx="8" cy="1044.4" r="3" fill="#fff" fill-opacity=".78431" stroke-linejoin="round" stroke-opacity=".39216" stroke-width="3"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 15.999999" + id="svg8" + sodipodi:docname="icon_GUI_slider_grabber.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1211" + inkscape:window-height="644" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="-5.7627119" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g6" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <path + transform="translate(0 1036.4)" + d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" + fill="#e0e0e0" + id="path2" + style="fill:#e0e0e0;fill-opacity:0.28925619" /> + <circle + cx="8" + cy="1044.4" + r="3" + fill="#fff" + fill-opacity=".58824" + stroke-linecap="round" + stroke-linejoin="round" + stroke-opacity=".32549" + stroke-width="3" + id="circle4" /> + </g> + <g + transform="translate(-0.06779632,-1036.4)" + id="g18"> + <circle + style="fill:#ffffff;fill-opacity:0.78430996;stroke-width:3;stroke-linejoin:round;stroke-opacity:0.39216003" + cx="8" + cy="1044.4" + r="3" + id="circle16" /> + </g> </svg> diff --git a/editor/icons/icon_GUI_slider_grabber_hl.svg b/editor/icons/icon_GUI_slider_grabber_hl.svg index 73252751ce..a04ac44cf6 100644 --- a/editor/icons/icon_GUI_slider_grabber_hl.svg +++ b/editor/icons/icon_GUI_slider_grabber_hl.svg @@ -1,6 +1,80 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 15.999999" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" fill="#e0e0e0"/> -<circle cx="8" cy="1044.4" r="3" fill="#fff" fill-opacity=".58824" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".32549" stroke-width="3"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 15.999999" + id="svg8" + sodipodi:docname="icon_GUI_slider_grabber_hl.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="944" + inkscape:window-height="480" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <path + transform="translate(0 1036.4)" + d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" + fill="#e0e0e0" + id="path2" /> + <circle + cx="8" + cy="1044.4" + r="3" + fill="#fff" + fill-opacity=".58824" + stroke-linecap="round" + stroke-linejoin="round" + stroke-opacity=".32549" + stroke-width="3" + id="circle4" /> + </g> + <g + transform="translate(-0.06779632,-1036.4)" + id="g18"> + <circle + style="fill:#ffffff;fill-opacity:0.78430996;stroke-width:3;stroke-linejoin:round;stroke-opacity:0.39216003" + cx="8" + cy="1044.4" + r="3" + id="circle16" /> + </g> </svg> diff --git a/editor/icons/icon_play_overlay.svg b/editor/icons/icon_play_overlay.svg new file mode 100644 index 0000000000..eff33f1b6b --- /dev/null +++ b/editor/icons/icon_play_overlay.svg @@ -0,0 +1,4 @@ +<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"> + <rect x="0" y="0" width="64" height="64" rx="5" ry="5" fill="#044B94" fill-opacity="0.6"/> + <path d="M16 16 L48 32 L16 48" fill="#f2f2f2"/> +</svg>
\ No newline at end of file diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp index 73e78ddf2a..173be01586 100644 --- a/editor/multi_node_edit.cpp +++ b/editor/multi_node_edit.cpp @@ -80,8 +80,8 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, ur->add_undo_property(n, name, n->get(name)); } - ur->add_do_method(EditorNode::get_singleton()->get_property_editor(), "refresh"); - ur->add_undo_method(EditorNode::get_singleton()->get_property_editor(), "refresh"); + ur->add_do_method(EditorNode::get_singleton()->get_inspector(), "refresh"); + ur->add_undo_method(EditorNode::get_singleton()->get_inspector(), "refresh"); ur->commit_action(); return true; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index b387972558..23c5e36a92 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -85,7 +85,7 @@ void AnimationPlayerEditor::_notification(int p_what) { } frame->set_value(player->get_current_animation_position()); key_editor->set_anim_pos(player->get_current_animation_position()); - EditorNode::get_singleton()->get_property_editor()->refresh(); + EditorNode::get_singleton()->get_inspector()->refresh(); } else if (last_active) { //need the last frame after it stopped @@ -1073,7 +1073,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) updating = false; _seek_value_changed(p_pos, !p_drag); - EditorNode::get_singleton()->get_property_editor()->refresh(); + EditorNode::get_singleton()->get_inspector()->refresh(); //seekit } diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 05833704d1..0ff316a286 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -65,7 +65,7 @@ void EditorAssetLibraryItem::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - icon->set_normal_texture(get_icon("GodotAssetDefault", "EditorIcons")); + icon->set_normal_texture(get_icon("DefaultProjectIcon", "EditorIcons")); category->add_color_override("font_color", Color(0.5, 0.5, 0.5)); author->add_color_override("font_color", Color(0.5, 0.5, 0.5)); } @@ -110,6 +110,7 @@ EditorAssetLibraryItem::EditorAssetLibraryItem() { add_child(hb); icon = memnew(TextureButton); + icon->set_custom_minimum_size(Size2(64, 64)); icon->set_default_cursor_shape(CURSOR_POINTING_HAND); icon->connect("pressed", this, "_asset_clicked"); @@ -170,7 +171,23 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const for (int i = 0; i < preview_images.size(); i++) { if (preview_images[i].id == p_index) { - preview_images[i].button->set_icon(p_image); + if (preview_images[i].is_video) { + Ref<Image> overlay = get_icon("PlayOverlay", "EditorIcons")->get_data(); + Ref<Image> thumbnail = p_image->get_data(); + Point2 overlay_pos = Point2((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); + + thumbnail->lock(); + thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); + thumbnail->unlock(); + + Ref<ImageTexture> tex; + tex.instance(); + tex->create_from_image(thumbnail); + + preview_images[i].button->set_icon(tex); + } else { + preview_images[i].button->set_icon(p_image); + } break; } } @@ -383,7 +400,7 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse icon->set_texture(p_preview); asset_id = p_asset_id; if (!p_preview.is_valid()) - icon->set_texture(get_icon("GodotAssetDefault", "EditorIcons")); + icon->set_texture(get_icon("DefaultProjectIcon", "EditorIcons")); host = p_download_url; sha256 = p_sha256_hash; asset_installer->connect("confirmed", this, "_close"); @@ -694,7 +711,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PoolByt switch (image_queue[p_queue_id].image_type) { case IMAGE_QUEUE_ICON: - image->resize(80 * EDSCALE, 80 * EDSCALE, Image::INTERPOLATE_CUBIC); + image->resize(64 * EDSCALE, 64 * EDSCALE, Image::INTERPOLATE_CUBIC); break; case IMAGE_QUEUE_THUMBNAIL: { @@ -724,7 +741,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PoolByt } if (!image_set && final) { - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_icon("ErrorSign", "EditorIcons")); + obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_icon("DefaultProjectIcon", "EditorIcons")); } } } @@ -767,7 +784,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons WARN_PRINTS("Error getting PNG file from URL: " + image_queue[p_queue_id].image_url); Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); if (obj) { - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_icon("ErrorSign", "EditorIcons")); + obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_icon("DefaultProjectIcon", "EditorIcons")); } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 93aeca6632..f27796db5e 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -520,8 +520,17 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel node = node->get_parent(); } + // Check if the canvas item is already in the list (for groups or scenes) + bool duplicate = false; + for (int j = 0; j < i; j++) { + if (r_items[j].item == canvas_item) { + duplicate = true; + break; + } + } + //Remove the item if invalid - if (!canvas_item || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (canvas_item->has_meta("_edit_lock_") && canvas_item->get_meta("_edit_lock_"))) { + if (!canvas_item || duplicate || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (canvas_item->has_meta("_edit_lock_") && canvas_item->get_meta("_edit_lock_"))) { r_items.remove(i); i--; } else { diff --git a/editor/plugins/cube_grid_theme_editor_plugin.cpp b/editor/plugins/cube_grid_theme_editor_plugin.cpp index 81f45b9f55..68d5ea5247 100644 --- a/editor/plugins/cube_grid_theme_editor_plugin.cpp +++ b/editor/plugins/cube_grid_theme_editor_plugin.cpp @@ -198,7 +198,7 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { } break; case MENU_OPTION_REMOVE_ITEM: { - String p = editor->get_property_editor()->get_selected_path(); + String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 8542296bde..d76c515c1f 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -467,15 +467,6 @@ Ref<Texture> EditorScriptPreviewPlugin::generate(const RES &p_from) { Color text_color = EditorSettings::get_singleton()->get("text_editor/highlighting/text_color"); Color symbol_color = EditorSettings::get_singleton()->get("text_editor/highlighting/symbol_color"); - if (EditorSettings::get_singleton()->get("text_editor/theme/color_theme") == "Adaptive") { - Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); - - bg_color = tm->get_color("text_editor/highlighting/background_color", "Editor"); - keyword_color = tm->get_color("text_editor/highlighting/keyword_color", "Editor"); - text_color = tm->get_color("text_editor/highlighting/text_color", "Editor"); - symbol_color = tm->get_color("text_editor/highlighting/symbol_color", "Editor"); - } - img->lock(); if (bg_color.a == 0) diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 8b44f672b0..f75fb0d109 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -388,7 +388,7 @@ ItemListEditor::ItemListEditor() { vbc->add_child(property_editor); property_editor->set_v_size_flags(SIZE_EXPAND_FILL); - tree = property_editor->get_scene_tree(); + tree = property_editor->get_property_tree(); } ItemListEditor::~ItemListEditor() { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index a4265b4e32..82fd727620 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -785,7 +785,7 @@ void ProjectSettingsEditor::popup_project_settings() { void ProjectSettingsEditor::_item_selected() { - TreeItem *ti = globals_editor->get_property_editor()->get_scene_tree()->get_selected(); + TreeItem *ti = globals_editor->get_property_editor()->get_property_tree()->get_selected(); if (!ti) return; if (!ti->get_parent()) @@ -1727,7 +1727,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { //globals_editor->hide_top_label(); globals_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); globals_editor->register_search_box(search_box); - globals_editor->get_property_editor()->get_scene_tree()->connect("cell_selected", this, "_item_selected"); + globals_editor->get_property_editor()->get_property_tree()->connect("cell_selected", this, "_item_selected"); globals_editor->get_property_editor()->connect("property_toggled", this, "_item_checked", varray(), CONNECT_DEFERRED); globals_editor->get_property_editor()->connect("property_edited", this, "_settings_prop_edited"); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index e0063925b1..e912ebe03a 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -4212,7 +4212,7 @@ void PropertyEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("property_edited", PropertyInfo(Variant::STRING, "property"))); } -Tree *PropertyEditor::get_scene_tree() { +Tree *PropertyEditor::get_property_tree() { return tree; } @@ -4695,7 +4695,7 @@ SectionedPropertyEditor::SectionedPropertyEditor() { editor->set_v_size_flags(SIZE_EXPAND_FILL); right_vb->add_child(editor, true); - editor->get_scene_tree()->set_column_titles_visible(false); + editor->get_property_tree()->set_column_titles_visible(false); editor->hide_top_label(); diff --git a/editor/property_editor.h b/editor/property_editor.h index 017a190adb..56743822d2 100644 --- a/editor/property_editor.h +++ b/editor/property_editor.h @@ -275,7 +275,7 @@ public: String get_selected_path() const; - Tree *get_scene_tree(); + Tree *get_property_tree(); Label *get_top_label(); void hide_top_label(); void update_tree(); @@ -309,6 +309,7 @@ public: void collapse_all_folding(); void expand_all_folding(); + PropertyEditor(); ~PropertyEditor(); }; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 8b99a3d503..f5cee4cd65 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -732,7 +732,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { if (node) { node->set_scene_inherited_state(Ref<SceneState>()); scene_tree->update_tree(); - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); } } } break; diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 64d278c0c5..dd79ae63d6 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -70,8 +70,18 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } else if (p_id == BUTTON_VISIBILITY) { undo_redo->create_action(TTR("Toggle Visible")); - undo_redo->add_do_method(this, "toggle_visible", n); - undo_redo->add_undo_method(this, "toggle_visible", n); + _toggle_visible(n); + List<Node *> selection = editor_selection->get_selected_node_list(); + if (selection.size() > 1) { + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node *nv = E->get(); + ERR_FAIL_COND(!nv); + if (nv == n) { + continue; + } + _toggle_visible(nv); + } + } undo_redo->commit_action(); } else if (p_id == BUTTON_LOCK) { @@ -118,33 +128,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } } void SceneTreeEditor::_toggle_visible(Node *p_node) { - if (p_node->is_class("Spatial")) { - bool v = bool(p_node->call("is_visible")); - p_node->call("set_visible", !v); - } else if (p_node->is_class("CanvasItem")) { + if (p_node->has_method("is_visible") && p_node->has_method("set_visible")) { bool v = bool(p_node->call("is_visible")); - if (v) { - p_node->call("hide"); - } else { - p_node->call("show"); - } + undo_redo->add_do_method(p_node, "set_visible", !v); + undo_redo->add_undo_method(p_node, "set_visible", v); } } -void SceneTreeEditor::toggle_visible(Node *p_node) { - _toggle_visible(p_node); - List<Node *> selection = editor_selection->get_selected_node_list(); - if (selection.size() > 1) { - for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { - Node *nv = E->get(); - ERR_FAIL_COND(!nv); - if (nv == p_node) { - continue; - } - _toggle_visible(nv); - } - } -} bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (!p_node) @@ -968,8 +958,6 @@ void SceneTreeEditor::_bind_methods() { ClassDB::bind_method("_cell_collapsed", &SceneTreeEditor::_cell_collapsed); ClassDB::bind_method("_rmb_select", &SceneTreeEditor::_rmb_select); ClassDB::bind_method("_warning_changed", &SceneTreeEditor::_warning_changed); - ClassDB::bind_method("_toggle_visible", &SceneTreeEditor::_toggle_visible); - ClassDB::bind_method("toggle_visible", &SceneTreeEditor::toggle_visible); ClassDB::bind_method("_node_script_changed", &SceneTreeEditor::_node_script_changed); ClassDB::bind_method("_node_visibility_changed", &SceneTreeEditor::_node_visibility_changed); diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index b63eb2a1f0..896fd6c431 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -70,8 +70,6 @@ class SceneTreeEditor : public Control { void _compute_hash(Node *p_node, uint64_t &hash); - void toggle_visible(Node *p_node); - bool _add_nodes(Node *p_node, TreeItem *p_parent); void _test_update_tree(); void _update_tree(); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index a83de1627d..9ce4305683 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -1851,7 +1851,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { ppeer = Ref<PacketPeerStream>(memnew(PacketPeerStream)); ppeer->set_input_buffer_max_size(1024 * 1024 * 8); //8mb should be enough editor = p_editor; - editor->get_property_editor()->connect("object_id_selected", this, "_scene_tree_property_select_object"); + editor->get_inspector()->connect("object_id_selected", this, "_scene_tree_property_select_object"); tabs = memnew(TabContainer); tabs->set_tab_align(TabContainer::ALIGN_LEFT); @@ -1936,7 +1936,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { inspector = memnew(PropertyEditor); inspector->set_h_size_flags(SIZE_EXPAND_FILL); inspector->hide_top_label(); - inspector->get_scene_tree()->set_column_title(0, TTR("Variable")); + inspector->get_property_tree()->set_column_title(0, TTR("Variable")); inspector->set_enable_capitalize_paths(false); inspector->set_read_only(true); inspector->connect("object_id_selected", this, "_scene_tree_property_select_object"); diff --git a/main/main.cpp b/main/main.cpp index 9c8474cb5b..c287bc81cb 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -82,6 +82,8 @@ #include "version.h" #include "version_hash.gen.h" +#include "main/timer_sync.h" + static ProjectSettings *globals = NULL; static Engine *engine = NULL; static InputMap *input_map = NULL; @@ -262,8 +264,8 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print("Standalone tools:\n"); OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" --export <target> Export the project using the given export target.\n"); - OS::get_singleton()->print(" --export-debug Use together with --export, enables debug mode for the template.\n"); + OS::get_singleton()->print(" --export <target> Export the project using the given export target. Export only main pack if path ends with .pck or .zip'.\n"); + OS::get_singleton()->print(" --export-debug <target> Like --export, but use debug template.\n"); OS::get_singleton()->print(" --doctool <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n"); OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects).\n"); @@ -337,7 +339,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph String video_driver = ""; String audio_driver = ""; - String game_path = "."; + String project_path = "."; bool upwards = false; String debug_mode; String debug_host; @@ -553,7 +555,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (OS::get_singleton()->set_cwd(p) == OK) { //nothing } else { - game_path = I->next()->get(); //use game_path instead + project_path = I->next()->get(); //use project_path instead } N = I->next()->next(); } else { @@ -576,7 +578,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (OS::get_singleton()->set_cwd(path) == OK) { // path already specified, don't override } else { - game_path = path; + project_path = path; } #ifdef TOOLS_ENABLED editor = true; @@ -672,35 +674,20 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } else if (I->get() == "--disable-crash-handler") { OS::get_singleton()->disable_crash_handler(); } else { - - //test for game path - bool gpfound = false; - - if (!I->get().begins_with("-") && game_path == "") { - DirAccess *da = DirAccess::open(I->get()); - if (da != NULL) { - game_path = I->get(); - gpfound = true; - memdelete(da); - } - } - - if (!gpfound) { - main_args.push_back(I->get()); - } + main_args.push_back(I->get()); } I = N; } - if (globals->setup(game_path, main_pack, upwards) == OK) { + if (globals->setup(project_path, main_pack, upwards) == OK) { found_project = true; } else { #ifdef TOOLS_ENABLED editor = false; #else - OS::get_singleton()->print("Error: Could not load game path '%s'.\n", game_path.ascii().get_data()); + OS::get_singleton()->print("Error: Could not load game path '%s'.\n", project_path.ascii().get_data()); goto error; #endif @@ -991,7 +978,7 @@ error: video_driver = ""; audio_driver = ""; - game_path = ""; + project_path = ""; args.clear(); main_args.clear(); @@ -1236,227 +1223,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) { } // everything the main loop needs to know about frame timings -struct _FrameTime { - float animation_step; // time to advance animations for (argument to process()) - int physics_steps; // number of times to iterate the physics engine - - void clamp_animation(float min_animation_step, float max_animation_step) { - if (animation_step < min_animation_step) { - animation_step = min_animation_step; - } else if (animation_step > max_animation_step) { - animation_step = max_animation_step; - } - } -}; - -class _TimerSync { - // wall clock time measured on the main thread - uint64_t last_cpu_ticks_usec; - uint64_t current_cpu_ticks_usec; - - // logical game time since last physics timestep - float time_accum; - - // current difference between wall clock time and reported sum of animation_steps - float time_deficit; - - // number of frames back for keeping accumulated physics steps roughly constant. - // value of 12 chosen because that is what is required to make 144 Hz monitors - // behave well with 60 Hz physics updates. The only worse commonly available refresh - // would be 85, requiring CONTROL_STEPS = 17. - static const int CONTROL_STEPS = 12; - - // sum of physics steps done over the last (i+1) frames - int accumulated_physics_steps[CONTROL_STEPS]; - - // typical value for accumulated_physics_steps[i] is either this or this plus one - int typical_physics_steps[CONTROL_STEPS]; - -protected: - // returns the fraction of p_frame_slice required for the timer to overshoot - // before advance_core considers changing the physics_steps return from - // the typical values as defined by typical_physics_steps - float get_physics_jitter_fix() { - return Engine::get_singleton()->get_physics_jitter_fix(); - } - - // gets our best bet for the average number of physics steps per render frame - // return value: number of frames back this data is consistent - int get_average_physics_steps(float &p_min, float &p_max) { - p_min = typical_physics_steps[0]; - p_max = p_min + 1; - - for (int i = 1; i < CONTROL_STEPS; ++i) { - const float typical_lower = typical_physics_steps[i]; - const float current_min = typical_lower / (i + 1); - if (current_min > p_max) - return i; // bail out of further restrictions would void the interval - else if (current_min > p_min) - p_min = current_min; - const float current_max = (typical_lower + 1) / (i + 1); - if (current_max < p_min) - return i; - else if (current_max < p_max) - p_max = current_max; - } - - return CONTROL_STEPS; - } - - // advance physics clock by p_animation_step, return appropriate number of steps to simulate - _FrameTime advance_core(float p_frame_slice, int p_iterations_per_second, float p_animation_step) { - _FrameTime ret; - - ret.animation_step = p_animation_step; - // simple determination of number of physics iteration - time_accum += ret.animation_step; - ret.physics_steps = floor(time_accum * p_iterations_per_second); - - int min_typical_steps = typical_physics_steps[0]; - int max_typical_steps = min_typical_steps + 1; - - // given the past recorded steps and typcial steps to match, calculate bounds for this - // step to be typical - bool update_typical = false; - - for (int i = 0; i < CONTROL_STEPS - 1; ++i) { - int steps_left_to_match_typical = typical_physics_steps[i + 1] - accumulated_physics_steps[i]; - if (steps_left_to_match_typical > max_typical_steps || - steps_left_to_match_typical + 1 < min_typical_steps) { - update_typical = true; - break; - } - - if (steps_left_to_match_typical > min_typical_steps) - min_typical_steps = steps_left_to_match_typical; - if (steps_left_to_match_typical + 1 < max_typical_steps) - max_typical_steps = steps_left_to_match_typical + 1; - } - - // try to keep it consistent with previous iterations - if (ret.physics_steps < min_typical_steps) { - const int max_possible_steps = floor((time_accum)*p_iterations_per_second + get_physics_jitter_fix()); - if (max_possible_steps < min_typical_steps) { - ret.physics_steps = max_possible_steps; - update_typical = true; - } else { - ret.physics_steps = min_typical_steps; - } - } else if (ret.physics_steps > max_typical_steps) { - const int min_possible_steps = floor((time_accum)*p_iterations_per_second - get_physics_jitter_fix()); - if (min_possible_steps > max_typical_steps) { - ret.physics_steps = min_possible_steps; - update_typical = true; - } else { - ret.physics_steps = max_typical_steps; - } - } - - time_accum -= ret.physics_steps * p_frame_slice; - - // keep track of accumulated step counts - for (int i = CONTROL_STEPS - 2; i >= 0; --i) { - accumulated_physics_steps[i + 1] = accumulated_physics_steps[i] + ret.physics_steps; - } - accumulated_physics_steps[0] = ret.physics_steps; - - if (update_typical) { - for (int i = CONTROL_STEPS - 1; i >= 0; --i) { - if (typical_physics_steps[i] > accumulated_physics_steps[i]) { - typical_physics_steps[i] = accumulated_physics_steps[i]; - } else if (typical_physics_steps[i] < accumulated_physics_steps[i] - 1) { - typical_physics_steps[i] = accumulated_physics_steps[i] - 1; - } - } - } - - return ret; - } - - // calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero - _FrameTime advance_checked(float p_frame_slice, int p_iterations_per_second, float p_animation_step) { - if (fixed_fps != -1) - p_animation_step = 1.0 / fixed_fps; - - // compensate for last deficit - p_animation_step += time_deficit; - - _FrameTime ret = advance_core(p_frame_slice, p_iterations_per_second, p_animation_step); - - // we will do some clamping on ret.animation_step and need to sync those changes to time_accum, - // that's easiest if we just remember their fixed difference now - const double animation_minus_accum = ret.animation_step - time_accum; - - // first, least important clamping: keep ret.animation_step consistent with typical_physics_steps. - // this smoothes out the animation steps and culls small but quick variations. - { - float min_average_physics_steps, max_average_physics_steps; - int consistent_steps = get_average_physics_steps(min_average_physics_steps, max_average_physics_steps); - if (consistent_steps > 3) { - ret.clamp_animation(min_average_physics_steps * p_frame_slice, max_average_physics_steps * p_frame_slice); - } - } - - // second clamping: keep abs(time_deficit) < jitter_fix * frame_slise - float max_clock_deviation = get_physics_jitter_fix() * p_frame_slice; - ret.clamp_animation(p_animation_step - max_clock_deviation, p_animation_step + max_clock_deviation); - - // last clamping: make sure time_accum is between 0 and p_frame_slice for consistency between physics and animation - ret.clamp_animation(animation_minus_accum, animation_minus_accum + p_frame_slice); - - // restore time_accum - time_accum = ret.animation_step - animation_minus_accum; - - // track deficit - time_deficit = p_animation_step - ret.animation_step; - - return ret; - } - - // determine wall clock step since last iteration - float get_cpu_animation_step() { - uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec; - last_cpu_ticks_usec = current_cpu_ticks_usec; - - return cpu_ticks_elapsed / 1000000.0; - } - -public: - explicit _TimerSync() : - last_cpu_ticks_usec(0), - current_cpu_ticks_usec(0), - time_accum(0), - time_deficit(0) { - for (int i = CONTROL_STEPS - 1; i >= 0; --i) { - typical_physics_steps[i] = i; - accumulated_physics_steps[i] = i; - } - } - - // start the clock - void init(uint64_t p_cpu_ticks_usec) { - current_cpu_ticks_usec = last_cpu_ticks_usec = p_cpu_ticks_usec; - } - - // set measured wall clock time - void set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec) { - current_cpu_ticks_usec = p_cpu_ticks_usec; - } - - // advance one frame, return timesteps to take - _FrameTime advance(float p_frame_slice, int p_iterations_per_second) { - float cpu_animation_step = get_cpu_animation_step(); - - return advance_checked(p_frame_slice, p_iterations_per_second, cpu_animation_step); - } - - void before_start_render() { - VisualServer::get_singleton()->sync(); - } -}; - -static _TimerSync _timer_sync; +static MainTimerSync main_timer_sync; bool Main::start() { @@ -1472,7 +1240,7 @@ bool Main::start() { String _export_preset; bool export_debug = false; - _timer_sync.init(OS::get_singleton()->get_ticks_usec()); + main_timer_sync.init(OS::get_singleton()->get_ticks_usec()); List<String> args = OS::get_singleton()->get_cmdline_args(); for (int i = 0; i < args.size(); i++) { @@ -1676,6 +1444,114 @@ bool Main::start() { } #endif + if (!project_manager) { // game or editor + if (game_path != "" || script != "") { + //autoload + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + //first pass, add the constants so they exist before any script is loaded + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) + continue; + String name = s.get_slicec('/', 1); + String path = ProjectSettings::get_singleton()->get(s); + bool global_var = false; + if (path.begins_with("*")) { + global_var = true; + } + + if (global_var) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { +#ifdef TOOLS_ENABLED + if (editor) { + ScriptServer::get_language(i)->add_named_global_constant(name, Variant()); + } else { + ScriptServer::get_language(i)->add_global_constant(name, Variant()); + } +#else + ScriptServer::get_language(i)->add_global_constant(name, Variant()); +#endif + } + } + } + + //second pass, load into global constants + List<Node *> to_add; +#ifdef TOOLS_ENABLED + ResourceLoader::set_timestamp_on_load(editor); // Avoid problems when editing +#endif + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) + continue; + String name = s.get_slicec('/', 1); + String path = ProjectSettings::get_singleton()->get(s); + bool global_var = false; + if (path.begins_with("*")) { + global_var = true; + path = path.substr(1, path.length() - 1); + } + + RES res = ResourceLoader::load(path); + ERR_EXPLAIN("Can't autoload: " + path); + ERR_CONTINUE(res.is_null()); + Node *n = NULL; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> s = res; + StringName ibt = s->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_EXPLAIN("Script does not inherit a Node: " + path); + ERR_CONTINUE(!valid_type); + + Object *obj = ClassDB::instance(ibt); + + ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); + ERR_CONTINUE(obj == NULL); + + n = Object::cast_to<Node>(obj); + n->set_script(s.get_ref_ptr()); + } + + ERR_EXPLAIN("Path in autoload not a node or script: " + path); + ERR_CONTINUE(!n); + n->set_name(name); + + //defer so references are all valid on _ready() + to_add.push_back(n); + + if (global_var) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { +#ifdef TOOLS_ENABLED + if (editor) { + ScriptServer::get_language(i)->add_named_global_constant(name, n); + } else { + ScriptServer::get_language(i)->add_global_constant(name, n); + } +#else + ScriptServer::get_language(i)->add_global_constant(name, n); +#endif + } + } + } + +#ifdef TOOLS_ENABLED + ResourceLoader::set_timestamp_on_load(false); +#endif + + for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) { + + sml->get_root()->add_child(E->get()); + } + } + } + #ifdef TOOLS_ENABLED EditorNode *editor_node = NULL; @@ -1695,9 +1571,6 @@ bool Main::start() { } #endif - { - } - if (!editor && !project_manager) { //standard helpers that can be changed from main config @@ -1810,89 +1683,6 @@ bool Main::start() { } if (!project_manager && !editor) { // game - if (game_path != "" || script != "") { - //autoload - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - //first pass, add the constants so they exist before any script is loaded - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slicec('/', 1); - String path = ProjectSettings::get_singleton()->get(s); - bool global_var = false; - if (path.begins_with("*")) { - global_var = true; - } - - if (global_var) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(name, Variant()); - } - } - } - - //second pass, load into global constants - List<Node *> to_add; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slicec('/', 1); - String path = ProjectSettings::get_singleton()->get(s); - bool global_var = false; - if (path.begins_with("*")) { - global_var = true; - path = path.substr(1, path.length() - 1); - } - - RES res = ResourceLoader::load(path); - ERR_EXPLAIN("Can't autoload: " + path); - ERR_CONTINUE(res.is_null()); - Node *n = NULL; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instance(); - } else if (res->is_class("Script")) { - Ref<Script> s = res; - StringName ibt = s->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_EXPLAIN("Script does not inherit a Node: " + path); - ERR_CONTINUE(!valid_type); - - Object *obj = ClassDB::instance(ibt); - - ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); - ERR_CONTINUE(obj == NULL); - - n = Object::cast_to<Node>(obj); - n->set_script(s.get_ref_ptr()); - } - - ERR_EXPLAIN("Path in autoload not a node or script: " + path); - ERR_CONTINUE(!n); - n->set_name(name); - - //defer so references are all valid on _ready() - to_add.push_back(n); - - if (global_var) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(name, n); - } - } - } - - for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) { - - sml->get_root()->add_child(E->get()); - } - } - if (game_path != "") { Node *scene = NULL; Ref<PackedScene> scenedata = ResourceLoader::load(local_game_path); @@ -1951,15 +1741,16 @@ bool Main::iteration() { uint64_t ticks = OS::get_singleton()->get_ticks_usec(); Engine::get_singleton()->_frame_ticks = ticks; - _timer_sync.set_cpu_ticks_usec(ticks); + main_timer_sync.set_cpu_ticks_usec(ticks); + main_timer_sync.set_fixed_fps(fixed_fps); uint64_t ticks_elapsed = ticks - last_ticks; int physics_fps = Engine::get_singleton()->get_iterations_per_second(); float frame_slice = 1.0 / physics_fps; - _FrameTime advance = _timer_sync.advance(frame_slice, physics_fps); - double step = advance.animation_step; + MainFrameTime advance = main_timer_sync.advance(frame_slice, physics_fps); + double step = advance.idle_step; Engine::get_singleton()->_frame_step = step; @@ -2023,7 +1814,7 @@ bool Main::iteration() { OS::get_singleton()->get_main_loop()->idle(step * time_scale); message_queue->flush(); - _timer_sync.before_start_render(); //sync if still drawing from previous frames. + VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames. if (OS::get_singleton()->can_draw() && !disable_render_loop) { diff --git a/main/timer_sync.cpp b/main/timer_sync.cpp new file mode 100644 index 0000000000..c33cbafee8 --- /dev/null +++ b/main/timer_sync.cpp @@ -0,0 +1,193 @@ +#include "timer_sync.h" + +void MainFrameTime::clamp_idle(float min_idle_step, float max_idle_step) { + if (idle_step < min_idle_step) { + idle_step = min_idle_step; + } else if (idle_step > max_idle_step) { + idle_step = max_idle_step; + } +} + +///////////////////////////////// + +// returns the fraction of p_frame_slice required for the timer to overshoot +// before advance_core considers changing the physics_steps return from +// the typical values as defined by typical_physics_steps +float MainTimerSync::get_physics_jitter_fix() { + return Engine::get_singleton()->get_physics_jitter_fix(); +} + +// gets our best bet for the average number of physics steps per render frame +// return value: number of frames back this data is consistent +int MainTimerSync::get_average_physics_steps(float &p_min, float &p_max) { + p_min = typical_physics_steps[0]; + p_max = p_min + 1; + + for (int i = 1; i < CONTROL_STEPS; ++i) { + const float typical_lower = typical_physics_steps[i]; + const float current_min = typical_lower / (i + 1); + if (current_min > p_max) + return i; // bail out of further restrictions would void the interval + else if (current_min > p_min) + p_min = current_min; + const float current_max = (typical_lower + 1) / (i + 1); + if (current_max < p_min) + return i; + else if (current_max < p_max) + p_max = current_max; + } + + return CONTROL_STEPS; +} + +// advance physics clock by p_idle_step, return appropriate number of steps to simulate +MainFrameTime MainTimerSync::advance_core(float p_frame_slice, int p_iterations_per_second, float p_idle_step) { + MainFrameTime ret; + + ret.idle_step = p_idle_step; + + // simple determination of number of physics iteration + time_accum += ret.idle_step; + ret.physics_steps = floor(time_accum * p_iterations_per_second); + + int min_typical_steps = typical_physics_steps[0]; + int max_typical_steps = min_typical_steps + 1; + + // given the past recorded steps and typcial steps to match, calculate bounds for this + // step to be typical + bool update_typical = false; + + for (int i = 0; i < CONTROL_STEPS - 1; ++i) { + int steps_left_to_match_typical = typical_physics_steps[i + 1] - accumulated_physics_steps[i]; + if (steps_left_to_match_typical > max_typical_steps || + steps_left_to_match_typical + 1 < min_typical_steps) { + update_typical = true; + break; + } + + if (steps_left_to_match_typical > min_typical_steps) + min_typical_steps = steps_left_to_match_typical; + if (steps_left_to_match_typical + 1 < max_typical_steps) + max_typical_steps = steps_left_to_match_typical + 1; + } + + // try to keep it consistent with previous iterations + if (ret.physics_steps < min_typical_steps) { + const int max_possible_steps = floor((time_accum)*p_iterations_per_second + get_physics_jitter_fix()); + if (max_possible_steps < min_typical_steps) { + ret.physics_steps = max_possible_steps; + update_typical = true; + } else { + ret.physics_steps = min_typical_steps; + } + } else if (ret.physics_steps > max_typical_steps) { + const int min_possible_steps = floor((time_accum)*p_iterations_per_second - get_physics_jitter_fix()); + if (min_possible_steps > max_typical_steps) { + ret.physics_steps = min_possible_steps; + update_typical = true; + } else { + ret.physics_steps = max_typical_steps; + } + } + + time_accum -= ret.physics_steps * p_frame_slice; + + // keep track of accumulated step counts + for (int i = CONTROL_STEPS - 2; i >= 0; --i) { + accumulated_physics_steps[i + 1] = accumulated_physics_steps[i] + ret.physics_steps; + } + accumulated_physics_steps[0] = ret.physics_steps; + + if (update_typical) { + for (int i = CONTROL_STEPS - 1; i >= 0; --i) { + if (typical_physics_steps[i] > accumulated_physics_steps[i]) { + typical_physics_steps[i] = accumulated_physics_steps[i]; + } else if (typical_physics_steps[i] < accumulated_physics_steps[i] - 1) { + typical_physics_steps[i] = accumulated_physics_steps[i] - 1; + } + } + } + + return ret; +} + +// calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero +MainFrameTime MainTimerSync::advance_checked(float p_frame_slice, int p_iterations_per_second, float p_idle_step) { + if (fixed_fps != -1) + p_idle_step = 1.0 / fixed_fps; + + // compensate for last deficit + p_idle_step += time_deficit; + + MainFrameTime ret = advance_core(p_frame_slice, p_iterations_per_second, p_idle_step); + + // we will do some clamping on ret.idle_step and need to sync those changes to time_accum, + // that's easiest if we just remember their fixed difference now + const double idle_minus_accum = ret.idle_step - time_accum; + + // first, least important clamping: keep ret.idle_step consistent with typical_physics_steps. + // this smoothes out the idle steps and culls small but quick variations. + { + float min_average_physics_steps, max_average_physics_steps; + int consistent_steps = get_average_physics_steps(min_average_physics_steps, max_average_physics_steps); + if (consistent_steps > 3) { + ret.clamp_idle(min_average_physics_steps * p_frame_slice, max_average_physics_steps * p_frame_slice); + } + } + + // second clamping: keep abs(time_deficit) < jitter_fix * frame_slise + float max_clock_deviation = get_physics_jitter_fix() * p_frame_slice; + ret.clamp_idle(p_idle_step - max_clock_deviation, p_idle_step + max_clock_deviation); + + // last clamping: make sure time_accum is between 0 and p_frame_slice for consistency between physics and idle + ret.clamp_idle(idle_minus_accum, idle_minus_accum + p_frame_slice); + + // restore time_accum + time_accum = ret.idle_step - idle_minus_accum; + + // track deficit + time_deficit = p_idle_step - ret.idle_step; + + return ret; +} + +// determine wall clock step since last iteration +float MainTimerSync::get_cpu_idle_step() { + uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec; + last_cpu_ticks_usec = current_cpu_ticks_usec; + + return cpu_ticks_elapsed / 1000000.0; +} + +MainTimerSync::MainTimerSync() : + last_cpu_ticks_usec(0), + current_cpu_ticks_usec(0), + time_accum(0), + time_deficit(0), + fixed_fps(0) { + for (int i = CONTROL_STEPS - 1; i >= 0; --i) { + typical_physics_steps[i] = i; + accumulated_physics_steps[i] = i; + } +} + +// start the clock +void MainTimerSync::init(uint64_t p_cpu_ticks_usec) { + current_cpu_ticks_usec = last_cpu_ticks_usec = p_cpu_ticks_usec; +} + +// set measured wall clock time +void MainTimerSync::set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec) { + current_cpu_ticks_usec = p_cpu_ticks_usec; +} + +void MainTimerSync::set_fixed_fps(int p_fixed_fps) { + fixed_fps = p_fixed_fps; +} + +// advance one frame, return timesteps to take +MainFrameTime MainTimerSync::advance(float p_frame_slice, int p_iterations_per_second) { + float cpu_idle_step = get_cpu_idle_step(); + + return advance_checked(p_frame_slice, p_iterations_per_second, cpu_idle_step); +} diff --git a/main/timer_sync.h b/main/timer_sync.h new file mode 100644 index 0000000000..6ef4254270 --- /dev/null +++ b/main/timer_sync.h @@ -0,0 +1,71 @@ +#ifndef TIMER_SYNC_H +#define TIMER_SYNC_H + +#include "core/engine.h" + +struct MainFrameTime { + float idle_step; // time to advance idles for (argument to process()) + int physics_steps; // number of times to iterate the physics engine + + void clamp_idle(float min_idle_step, float max_idle_step); +}; + +class MainTimerSync { + // wall clock time measured on the main thread + uint64_t last_cpu_ticks_usec; + uint64_t current_cpu_ticks_usec; + + // logical game time since last physics timestep + float time_accum; + + // current difference between wall clock time and reported sum of idle_steps + float time_deficit; + + // number of frames back for keeping accumulated physics steps roughly constant. + // value of 12 chosen because that is what is required to make 144 Hz monitors + // behave well with 60 Hz physics updates. The only worse commonly available refresh + // would be 85, requiring CONTROL_STEPS = 17. + static const int CONTROL_STEPS = 12; + + // sum of physics steps done over the last (i+1) frames + int accumulated_physics_steps[CONTROL_STEPS]; + + // typical value for accumulated_physics_steps[i] is either this or this plus one + int typical_physics_steps[CONTROL_STEPS]; + + int fixed_fps; + +protected: + // returns the fraction of p_frame_slice required for the timer to overshoot + // before advance_core considers changing the physics_steps return from + // the typical values as defined by typical_physics_steps + float get_physics_jitter_fix(); + + // gets our best bet for the average number of physics steps per render frame + // return value: number of frames back this data is consistent + int get_average_physics_steps(float &p_min, float &p_max); + + // advance physics clock by p_idle_step, return appropriate number of steps to simulate + MainFrameTime advance_core(float p_frame_slice, int p_iterations_per_second, float p_idle_step); + + // calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero + MainFrameTime advance_checked(float p_frame_slice, int p_iterations_per_second, float p_idle_step); + + // determine wall clock step since last iteration + float get_cpu_idle_step(); + +public: + MainTimerSync(); + + // start the clock + void init(uint64_t p_cpu_ticks_usec); + // set measured wall clock time + void set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec); + //set fixed fps + void set_fixed_fps(int p_fixed_fps); + + // advance one frame, return timesteps to take + MainFrameTime advance(float p_frame_slice, int p_iterations_per_second); +}; + +#endif // TIMER_SYNC_H diff --git a/misc/dist/linux/godot.6 b/misc/dist/linux/godot.6 index 26ebe01af7..64495d817e 100644 --- a/misc/dist/linux/godot.6 +++ b/misc/dist/linux/godot.6 @@ -128,10 +128,10 @@ Print the frames per second to the stdout. Run a script. .TP \fB\-\-export\fR <target> -Export the project using the given export target. +Export the project using the given export target. Export only main pack if path ends with .pck or .zip. .TP \fB\-\-export\-debug\fR -Use together with \fB\-\-export\fR, enables debug mode for the template. +Like \-\-export, but use debug template. .TP \fB\-\-doctool\fR <path> Dump the engine API reference to the given <path> in XML format, merging if existing files are found. diff --git a/modules/csg/config.py b/modules/csg/config.py index 5f133eba90..5e1d916790 100644 --- a/modules/csg/config.py +++ b/modules/csg/config.py @@ -3,3 +3,19 @@ def can_build(platform): def configure(env): pass + +def get_doc_classes(): + return [ + "CSGBox", + "CSGCombiner", + "CSGCylinder", + "CSGMesh", + "CSGPolygon", + "CSGPrimitive", + "CSGShape", + "CSGSphere", + "CSGTorus", + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/csg/doc_classes/CSGBox.xml b/modules/csg/doc_classes/CSGBox.xml new file mode 100644 index 0000000000..80455fda80 --- /dev/null +++ b/modules/csg/doc_classes/CSGBox.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGBox" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="depth" type="float" setter="set_depth" getter="get_depth"> + </member> + <member name="height" type="float" setter="set_height" getter="get_height"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="width" type="float" setter="set_width" getter="get_width"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGCombiner.xml b/modules/csg/doc_classes/CSGCombiner.xml new file mode 100644 index 0000000000..b2265d7703 --- /dev/null +++ b/modules/csg/doc_classes/CSGCombiner.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGCombiner" inherits="CSGShape" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGCylinder.xml b/modules/csg/doc_classes/CSGCylinder.xml new file mode 100644 index 0000000000..0cab26ad3d --- /dev/null +++ b/modules/csg/doc_classes/CSGCylinder.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGCylinder" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="cone" type="bool" setter="set_cone" getter="is_cone"> + </member> + <member name="height" type="float" setter="set_height" getter="get_height"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius"> + </member> + <member name="sides" type="int" setter="set_sides" getter="get_sides"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGMesh.xml b/modules/csg/doc_classes/CSGMesh.xml new file mode 100644 index 0000000000..e5c3e5ccf3 --- /dev/null +++ b/modules/csg/doc_classes/CSGMesh.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGMesh" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGPolygon.xml b/modules/csg/doc_classes/CSGPolygon.xml new file mode 100644 index 0000000000..379c512d6a --- /dev/null +++ b/modules/csg/doc_classes/CSGPolygon.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGPolygon" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="depth" type="float" setter="set_depth" getter="get_depth"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon.Mode"> + </member> + <member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval"> + </member> + <member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node"> + </member> + <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon.PathRotation"> + </member> + <member name="polygon" type="PoolVector2Array" setter="set_polygon" getter="get_polygon"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + <member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees"> + </member> + <member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides"> + </member> + </members> + <constants> + <constant name="MODE_DEPTH" value="0" enum="Mode"> + </constant> + <constant name="MODE_SPIN" value="1" enum="Mode"> + </constant> + <constant name="MODE_PATH" value="2" enum="Mode"> + </constant> + <constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation"> + </constant> + <constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation"> + </constant> + <constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation"> + </constant> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGPrimitive.xml b/modules/csg/doc_classes/CSGPrimitive.xml new file mode 100644 index 0000000000..bf41c40f22 --- /dev/null +++ b/modules/csg/doc_classes/CSGPrimitive.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGPrimitive" inherits="CSGShape" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGShape.xml b/modules/csg/doc_classes/CSGShape.xml new file mode 100644 index 0000000000..cf236a4207 --- /dev/null +++ b/modules/csg/doc_classes/CSGShape.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGShape" inherits="VisualInstance" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="is_root_shape" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> + </methods> + <members> + <member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape.Operation"> + </member> + <member name="snap" type="float" setter="set_snap" getter="get_snap"> + </member> + <member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision"> + </member> + </members> + <constants> + <constant name="OPERATION_UNION" value="0" enum="Operation"> + </constant> + <constant name="OPERATION_INTERSECTION" value="1" enum="Operation"> + </constant> + <constant name="OPERATION_SUBTRACTION" value="2" enum="Operation"> + </constant> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGSphere.xml b/modules/csg/doc_classes/CSGSphere.xml new file mode 100644 index 0000000000..520368506e --- /dev/null +++ b/modules/csg/doc_classes/CSGSphere.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGSphere" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments"> + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius"> + </member> + <member name="rings" type="int" setter="set_rings" getter="get_rings"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGTorus.xml b/modules/csg/doc_classes/CSGTorus.xml new file mode 100644 index 0000000000..58bbef2600 --- /dev/null +++ b/modules/csg/doc_classes/CSGTorus.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGTorus" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius"> + </member> + <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides"> + </member> + <member name="sides" type="int" setter="set_sides" getter="get_sides"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 4b2a9df8c4..d5fd4bff09 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -65,6 +65,20 @@ Disconnect the given peer. If "now" is set to true, the connection will be closed immediately without flushing queued messages. </description> </method> + <method name="get_last_packet_channel" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the channel of the last packet fetched via [method PacketPeer.get_packet] + </description> + </method> + <method name="get_packet_channel" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the channel of the next packet that will be retrieved via [method PacketPeer.get_packet_peer] + </description> + </method> <method name="get_peer_address" qualifiers="const"> <return type="String"> </return> @@ -94,9 +108,18 @@ </method> </methods> <members> + <member name="always_ordered" type="bool" setter="set_always_ordered" getter="is_always_ordered"> + Always use [code]TRANSFER_MODE_ORDERED[/code] in place of [code]TRANSFER_MODE_UNRELIABLE[/code]. This is the only way to use ordering with the RPC system. + </member> + <member name="channel_count" type="int" setter="set_channel_count" getter="get_channel_count"> + The number of channels to be used by ENet. Default: [code]3[/code]. Channels are used to separate different kinds of data. In realiable or ordered mode, for example, the packet delivery order is ensured on a per channel basis. + </member> <member name="compression_mode" type="int" setter="set_compression_mode" getter="get_compression_mode" enum="NetworkedMultiplayerENet.CompressionMode"> The compression method used for network packets. Default is no compression. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all. </member> + <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel"> + Set the default channel to be used to transfer data. By default this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data. + </member> </members> <constants> <constant name="COMPRESS_NONE" value="0" enum="CompressionMode"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index e948df2742..88768829d7 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -55,6 +55,22 @@ int NetworkedMultiplayerENet::get_packet_peer() const { return incoming_packets.front()->get().from; } +int NetworkedMultiplayerENet::get_packet_channel() const { + + ERR_FAIL_COND_V(!active, -1); + ERR_FAIL_COND_V(incoming_packets.size() == 0, -1); + + return incoming_packets.front()->get().channel; +} + +int NetworkedMultiplayerENet::get_last_packet_channel() const { + + ERR_FAIL_COND_V(!active, -1); + ERR_FAIL_COND_V(!current_packet.packet, -1); + + return current_packet.channel; +} + Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int p_in_bandwidth, int p_out_bandwidth) { ERR_FAIL_COND_V(active, ERR_ALREADY_IN_USE); @@ -83,7 +99,7 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int host = enet_host_create(&address /* the address to bind the server host to */, p_max_clients /* allow up to 32 clients and/or outgoing connections */, - SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, + channel_count /* allow up to channel_count to be used */, p_in_bandwidth /* limit incoming bandwith if > 0 */, p_out_bandwidth /* limit outgoing bandwith if > 0 */); @@ -127,13 +143,13 @@ Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_por host = enet_host_create(&c_client /* create a client host */, 1 /* only allow 1 outgoing connection */, - SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, + channel_count /* allow up to channel_count to be used */, p_in_bandwidth /* limit incoming bandwith if > 0 */, p_out_bandwidth /* limit outgoing bandwith if > 0 */); } else { host = enet_host_create(NULL /* create a client host */, 1 /* only allow 1 outgoing connection */, - SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, + channel_count /* allow up to channel_count to be used */, p_in_bandwidth /* limit incoming bandwith if > 0 */, p_out_bandwidth /* limit outgoing bandwith if > 0 */); } @@ -167,7 +183,7 @@ Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_por unique_id = _gen_unique_id(); // Initiate connection, allocating enough channels - ENetPeer *peer = enet_host_connect(host, &address, SYSCH_MAX, unique_id); + ENetPeer *peer = enet_host_connect(host, &address, channel_count, unique_id); if (peer == NULL) { enet_host_destroy(host); @@ -316,7 +332,7 @@ void NetworkedMultiplayerENet::poll() { } enet_packet_destroy(event.packet); - } else if (event.channelID < SYSCH_MAX) { + } else if (event.channelID < channel_count) { Packet packet; packet.packet = event.packet; @@ -330,6 +346,7 @@ void NetworkedMultiplayerENet::poll() { uint32_t flags = decode_uint32(&event.packet->data[8]); packet.from = source; + packet.channel = event.channelID; if (server) { // Someone is cheating and trying to fake the source! @@ -496,7 +513,10 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer switch (transfer_mode) { case TRANSFER_MODE_UNRELIABLE: { - packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; + if (always_ordered) + packet_flags = 0; + else + packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; channel = SYSCH_UNRELIABLE; } break; case TRANSFER_MODE_UNRELIABLE_ORDERED: { @@ -509,6 +529,9 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer } break; } + if (transfer_channel > SYSCH_CONFIG) + channel = transfer_channel; + Map<int, ENetPeer *>::Element *E = NULL; if (target_peer != 0) { @@ -572,6 +595,7 @@ void NetworkedMultiplayerENet::_pop_current_packet() { enet_packet_destroy(current_packet.packet); current_packet.packet = NULL; current_packet.from = 0; + current_packet.channel = -1; } } @@ -759,6 +783,40 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { #endif } +void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { + + ERR_FAIL_COND(p_channel < -1 || p_channel >= channel_count); + + if (p_channel == SYSCH_CONFIG) { + ERR_EXPLAIN("Channel " + itos(SYSCH_CONFIG) + " is reserved"); + ERR_FAIL(); + } + transfer_channel = p_channel; +} + +int NetworkedMultiplayerENet::get_transfer_channel() const { + return transfer_channel; +} + +void NetworkedMultiplayerENet::set_channel_count(int p_channel) { + + ERR_FAIL_COND(active); + ERR_FAIL_COND(p_channel < SYSCH_MAX); + channel_count = p_channel; +} + +int NetworkedMultiplayerENet::get_channel_count() const { + return channel_count; +} + +void NetworkedMultiplayerENet::set_always_ordered(bool p_ordered) { + always_ordered = p_ordered; +} + +bool NetworkedMultiplayerENet::is_always_ordered() const { + return always_ordered; +} + void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "in_bandwidth", "out_bandwidth"), &NetworkedMultiplayerENet::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0)); @@ -771,7 +829,19 @@ void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &NetworkedMultiplayerENet::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &NetworkedMultiplayerENet::get_peer_port); + ClassDB::bind_method(D_METHOD("get_packet_channel"), &NetworkedMultiplayerENet::get_packet_channel); + ClassDB::bind_method(D_METHOD("get_last_packet_channel"), &NetworkedMultiplayerENet::get_last_packet_channel); + ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &NetworkedMultiplayerENet::set_transfer_channel); + ClassDB::bind_method(D_METHOD("get_transfer_channel"), &NetworkedMultiplayerENet::get_transfer_channel); + ClassDB::bind_method(D_METHOD("set_channel_count", "channels"), &NetworkedMultiplayerENet::set_channel_count); + ClassDB::bind_method(D_METHOD("get_channel_count"), &NetworkedMultiplayerENet::get_channel_count); + ClassDB::bind_method(D_METHOD("set_always_ordered", "ordered"), &NetworkedMultiplayerENet::set_always_ordered); + ClassDB::bind_method(D_METHOD("is_always_ordered"), &NetworkedMultiplayerENet::is_always_ordered); + ADD_PROPERTY(PropertyInfo(Variant::INT, "compression_mode", PROPERTY_HINT_ENUM, "None,Range Coder,FastLZ,ZLib,ZStd"), "set_compression_mode", "get_compression_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel"), "set_transfer_channel", "get_transfer_channel"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "channel_count"), "set_channel_count", "get_channel_count"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "always_ordered"), "set_always_ordered", "is_always_ordered"); BIND_ENUM_CONSTANT(COMPRESS_NONE); BIND_ENUM_CONSTANT(COMPRESS_RANGE_CODER); @@ -789,6 +859,9 @@ NetworkedMultiplayerENet::NetworkedMultiplayerENet() { target_peer = 0; current_packet.packet = NULL; transfer_mode = TRANSFER_MODE_RELIABLE; + channel_count = SYSCH_MAX; + transfer_channel = -1; + always_ordered = false; connection_status = CONNECTION_DISCONNECTED; compression_mode = COMPRESS_NONE; enet_compressor.context = this; diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index d481f5d496..705807d429 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -68,6 +68,9 @@ private: int target_peer; TransferMode transfer_mode; + int transfer_channel; + int channel_count; + bool always_ordered; ENetEvent event; ENetPeer *peer; @@ -83,6 +86,7 @@ private: ENetPacket *packet; int from; + int channel; }; CompressionMode compression_mode; @@ -145,6 +149,15 @@ public: void set_compression_mode(CompressionMode p_mode); CompressionMode get_compression_mode() const; + int get_packet_channel() const; + int get_last_packet_channel() const; + void set_transfer_channel(int p_channel); + int get_transfer_channel() const; + void set_channel_count(int p_channel); + int get_channel_count() const; + void set_always_ordered(bool p_ordered); + bool is_always_ordered() const; + NetworkedMultiplayerENet(); ~NetworkedMultiplayerENet(); diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 8a7c2a773a..301f218361 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -17,18 +17,15 @@ if env['builtin_freetype']: "src/base/ftbitmap.c", "src/base/ftcid.c", "src/base/ftdebug.c", - "src/base/ftfntfmt.c", "src/base/ftfstype.c", "src/base/ftgasp.c", "src/base/ftglyph.c", "src/base/ftgxval.c", "src/base/ftinit.c", - "src/base/ftlcdfil.c", "src/base/ftmm.c", "src/base/ftotval.c", "src/base/ftpatent.c", "src/base/ftpfr.c", - "src/base/ftpic.c", "src/base/ftstroke.c", "src/base/ftsynth.c", "src/base/ftsystem.c", diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index 8654ef3d82..6d2f8ce8ad 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -23,7 +23,8 @@ def _build_gdnative_api_struct_header(api): '\textern const godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct;' ] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] gdnative_api_init_macro.append( '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) @@ -31,9 +32,10 @@ def _build_gdnative_api_struct_header(api): gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] gdnative_api_init_macro.append( - '\t\t\tcase GDNATIVE_EXT_%s:' % api['extensions'][name]['type']) + '\t\t\tcase GDNATIVE_EXT_%s:' % ext['type']) gdnative_api_init_macro.append( '\t\t\t\t_gdnative_wrapper_{0}_api_struct = (godot_gdnative_ext_{0}_api_struct *)' ' _gdnative_wrapper_api_struct->extensions[i];'.format(name)) @@ -61,8 +63,8 @@ def _build_gdnative_api_struct_header(api): '\tGDNATIVE_' + api['core']['type'] + ',' ] - for name in api['extensions']: - out += ['\tGDNATIVE_EXT_' + api['extensions'][name]['type'] + ','] + for ext in api['extensions']: + out += ['\tGDNATIVE_EXT_' + ext['type'] + ','] out += ['};', ''] @@ -88,8 +90,9 @@ def _build_gdnative_api_struct_header(api): return ret_val - for name in api['extensions']: - out += generate_extension_struct(name, api['extensions'][name], False) + for ext in api['extensions']: + name = ext['name'] + out += generate_extension_struct(name, ext, False) out += [ 'typedef struct godot_gdnative_core_api_struct {', @@ -151,12 +154,14 @@ def _build_gdnative_api_struct_source(api): return ret_val - for name in api['extensions']: - out += get_extension_struct_definition(name, api['extensions'][name], False) + for ext in api['extensions']: + name = ext['name'] + out += get_extension_struct_definition(name, ext, False) out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] out += ['\t(godot_gdnative_api_struct *)&api_extension_' + name + '_struct,'] out += ['};\n'] @@ -214,7 +219,8 @@ def _build_gdnative_wrapper_code(api): 'godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct = 0;', ] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] out.append('godot_gdnative_ext_' + name + '_api_struct *_gdnative_wrapper_' + name + '_api_struct = 0;') out += [''] @@ -232,8 +238,9 @@ def _build_gdnative_wrapper_code(api): out.append('}') out.append('') - for name in api['extensions']: - for funcdef in api['extensions'][name]['api']: + for ext in api['extensions']: + name = ext['name'] + for funcdef in ext['api']: args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) @@ -267,7 +274,7 @@ def build_gdnative_wrapper_code(target, source, env): if ARGUMENTS.get('gdnative_wrapper', False): - #build wrapper code +#build wrapper code gensource, = gdn_env.Command('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', build_gdnative_wrapper_code) gd_wrapper_env = env.Clone() diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index f41c3859bd..9fcf61af8a 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5756,8 +5756,9 @@ } ] }, - "extensions": { - "nativescript": { + "extensions": [ + { + "name": "nativescript", "type": "NATIVESCRIPT", "version": { "major": 1, @@ -5942,7 +5943,8 @@ } ] }, - "pluginscript": { + { + "name": "pluginscript", "type": "PLUGINSCRIPT", "version": { "major": 1, @@ -5959,7 +5961,8 @@ } ] }, - "arvr": { + { + "name": "arvr", "type": "ARVR", "version": { "major": 1, @@ -6055,5 +6058,5 @@ } ] } - } + ] } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4e3ee4d22c..14bdce50ec 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1333,6 +1333,15 @@ void GDScriptLanguage::add_global_constant(const StringName &p_variable, const V _add_global(p_variable, p_value); } +void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const Variant &p_value) { + named_globals[p_name] = p_value; +} + +void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { + ERR_FAIL_COND(!named_globals.has(p_name)); + named_globals.erase(p_name); +} + void GDScriptLanguage::init() { //populate global constants diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 9566e3b32e..6885fbb7fe 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -262,6 +262,7 @@ class GDScriptLanguage : public ScriptLanguage { Variant *_global_array; Vector<Variant> global_array; Map<StringName, int> globals; + Map<StringName, Variant> named_globals; struct CallLevel { @@ -369,7 +370,8 @@ public: _FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); } _FORCE_INLINE_ Variant *get_global_array() { return _global_array; } - _FORCE_INLINE_ const Map<StringName, int> &get_global_map() { return globals; } + _FORCE_INLINE_ const Map<StringName, int> &get_global_map() const { return globals; } + _FORCE_INLINE_ const Map<StringName, Variant> &get_named_globals_map() const { return named_globals; } _FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; } @@ -403,6 +405,8 @@ public: virtual String _get_indentation() const; virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value); + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value); + virtual void remove_named_global_constant(const StringName &p_name); /* DEBUGGER FUNCTIONS */ diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 048948dada..9947512444 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -278,6 +278,18 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } +#ifdef TOOLS_ENABLED + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { + + int idx = codegen.named_globals.find(identifier); + if (idx == -1) { + idx = codegen.named_globals.size(); + codegen.named_globals.push_back(identifier); + } + return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + } +#endif + //not found, error _set_error("Identifier not found: " + String(identifier), p_expression); @@ -1511,6 +1523,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->_global_names_count = 0; } +#ifdef TOOLS_ENABLED + // Named globals + if (codegen.named_globals.size()) { + gdfunc->named_globals.resize(codegen.named_globals.size()); + gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); + for (int i = 0; i < codegen.named_globals.size(); i++) { + gdfunc->named_globals[i] = codegen.named_globals[i]; + } + gdfunc->_named_globals_count = gdfunc->named_globals.size(); + } +#endif + if (codegen.opcodes.size()) { gdfunc->code = codegen.opcodes; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 62aafdbe01..237b0de9e7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -94,6 +94,9 @@ class GDScriptCompiler { HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; Map<StringName, int> name_map; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif int get_name_map_pos(const StringName &p_identifier) { int ret; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 1c5b8187ca..dac7da3a28 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -108,6 +108,21 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta #endif return &GDScriptLanguage::get_singleton()->get_global_array()[address]; } break; +#ifdef TOOLS_ENABLED + case ADDR_TYPE_NAMED_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _named_globals_count, NULL); +#endif + StringName id = _named_globals_ptr[address]; + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { + return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; + } else { + r_error = "Autoload singleton '" + String(id) + "' has been removed."; + return NULL; + } + } break; +#endif case ADDR_TYPE_NIL: { return &nil; } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index dff4bdfaf2..ea009dcd96 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -92,7 +92,8 @@ public: ADDR_TYPE_STACK = 5, ADDR_TYPE_STACK_VARIABLE = 6, ADDR_TYPE_GLOBAL = 7, - ADDR_TYPE_NIL = 8 + ADDR_TYPE_NAMED_GLOBAL = 8, + ADDR_TYPE_NIL = 9 }; enum RPCMode { @@ -121,6 +122,10 @@ private: int _constant_count; const StringName *_global_names_ptr; int _global_names_count; +#ifdef TOOLS_ENABLED + const StringName *_named_globals_ptr; + int _named_globals_count; +#endif const int *_default_arg_ptr; int _default_arg_count; const int *_code_ptr; @@ -137,6 +142,9 @@ private: StringName name; Vector<Variant> constants; Vector<StringName> global_names; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif Vector<int> default_arguments; Vector<int> code; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index bbe245951e..161e62c81f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -782,7 +782,7 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } if (Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); NodeDock::singleton->update_lists(); } } diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index eb10c5e99f..dfaa873b13 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -268,7 +268,7 @@ protected: if (String(p_name) == "export") { script->set_variable_export(var, p_value); - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); return true; } diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index b09c232b3c..c681e2b34f 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -263,7 +263,7 @@ if env["platform"] == 'uwp': webm_cpu_x86 = True else: import platform - is_x11_or_server_arm = ((env["platform"] == 'x11' or env["platform"] == 'server') and platform.machine().startswith('arm')) + is_x11_or_server_arm = ((env["platform"] == 'x11' or env["platform"] == 'server') and (platform.machine().startswith('arm') or platform.machine().startswith('aarch'))) is_ios_x86 = (env["platform"] == 'iphone' and env["ios_sim"]) is_android_x86 = (env["platform"] == 'android' and env["android_arch"] == 'x86') if is_android_x86: @@ -337,7 +337,6 @@ if webm_cpu_arm: if webm_simd_optimizations == False: print("WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!") - env_libvpx.add_source_files(env.modules_sources, libvpx_sources) if webm_cpu_x86: is_clang_or_gcc = ('gcc' in env["CC"]) or ('clang' in env["CC"]) or ("OSXCROSS_ROOT" in os.environ) @@ -379,5 +378,5 @@ elif webm_cpu_arm: env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_armasm_ms) elif env["platform"] == 'iphone': env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas_apple) - elif not env["android_arch"] == 'arm64v8': + elif (is_x11_or_server_arm and cpu_bits == '32') or (env["platform"] == 'android' and not env["android_arch"] == 'arm64v8'): env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas) diff --git a/modules/webp/SCsub b/modules/webp/SCsub index ea7af1bf9e..21ae0ce7c2 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -78,10 +78,12 @@ if env['builtin_libwebp']: "dsp/upsampling_msa.c", "dsp/upsampling_neon.c", "dsp/upsampling_sse2.c", + "dsp/upsampling_sse41.c", "dsp/yuv.c", "dsp/yuv_mips32.c", "dsp/yuv_mips_dsp_r2.c", "dsp/yuv_sse2.c", + "dsp/yuv_sse41.c", "enc/alpha_enc.c", "enc/analysis_enc.c", "enc/backward_references_cost_enc.c", diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 7ed1328b20..dd5ce4ab10 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -709,21 +709,18 @@ static int frame_count = 0; iphone_finish(); }; -- (void)applicationDidEnterBackground:(UIApplication *)application { - ///@TODO maybe add pause motionManager? and where would we unpause it? +// When application goes to background (e.g. user switches to another app or presses Home), +// then applicationWillResignActive -> applicationDidEnterBackground are called. +// When user opens the inactive app again, +// applicationWillEnterForeground -> applicationDidBecomeActive are called. - on_focus_out(view_controller, &is_focus_out); -} - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); - [view_controller.view startAnimation]; -} +// There are cases when applicationWillResignActive -> applicationDidBecomeActive +// sequence is called without the app going to background. For example, that happens +// if you open the app list without switching to another app or open/close the +// notification panel by swiping from the upper part of the screen. - (void)applicationWillResignActive:(UIApplication *)application { - // OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); - [view_controller.view - stopAnimation]; // FIXME: pause seems to be recommended elsewhere + on_focus_out(view_controller, &is_focus_out); } - (void)applicationDidBecomeActive:(UIApplication *)application { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index dbe9a11471..bde0b4e898 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -541,7 +541,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } - (void)cursorUpdate:(NSEvent *)event { - //setModeCursor(window, window->cursorMode); + OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; + OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; + OS_OSX::singleton->set_cursor_shape(p_shape); } static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { @@ -656,11 +658,12 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { return; if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - if (OS_OSX::singleton->input) { + if (OS_OSX::singleton->input) OS_OSX::singleton->input->set_mouse_in_window(true); - OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; - OS_OSX::singleton->set_cursor_shape(OS::CURSOR_ARROW); - } + + OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; + OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; + OS_OSX::singleton->set_cursor_shape(p_shape); } - (void)magnifyWithEvent:(NSEvent *)event { @@ -847,16 +850,16 @@ struct _KeyCodeMap { static const _KeyCodeMap _keycodes[55] = { { '`', KEY_QUOTELEFT }, { '~', KEY_ASCIITILDE }, - { '0', KEY_KP_0 }, - { '1', KEY_KP_1 }, - { '2', KEY_KP_2 }, - { '3', KEY_KP_3 }, - { '4', KEY_KP_4 }, - { '5', KEY_KP_5 }, - { '6', KEY_KP_6 }, - { '7', KEY_KP_7 }, - { '8', KEY_KP_8 }, - { '9', KEY_KP_9 }, + { '0', KEY_0 }, + { '1', KEY_1 }, + { '2', KEY_2 }, + { '3', KEY_3 }, + { '4', KEY_4 }, + { '5', KEY_5 }, + { '6', KEY_6 }, + { '7', KEY_7 }, + { '8', KEY_8 }, + { '9', KEY_9 }, { '-', KEY_MINUS }, { '_', KEY_UNDERSCORE }, { '=', KEY_EQUAL }, @@ -2424,12 +2427,21 @@ void OS_OSX::run() { //int frames=0; //uint64_t frame=0; - while (!force_quit) { + bool quit = false; - process_events(); // get rid of pending events - joypad_osx->process_joypads(); - if (Main::iteration() == true) - break; + while (!force_quit && !quit) { + + @try { + + process_events(); // get rid of pending events + joypad_osx->process_joypads(); + + if (Main::iteration() == true) { + quit = true; + } + } @catch (NSException *exception) { + ERR_PRINTS("NSException: " + String([exception reason].UTF8String)); + } }; main_loop->finish(); diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index a8be4fbc35..3b1be780d4 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -30,6 +30,7 @@ #include "os_server.h" #include "drivers/dummy/audio_driver_dummy.h" #include "drivers/dummy/rasterizer_dummy.h" +#include "drivers/dummy/texture_loader_dummy.h" #include "print_string.h" #include "servers/visual/visual_server_raster.h" #include <stdio.h> @@ -83,6 +84,9 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int _ensure_user_data_dir(); + resource_loader_dummy = memnew(ResourceFormatDummyTexture); + ResourceLoader::add_resource_format_loader(resource_loader_dummy); + return OK; } @@ -99,6 +103,8 @@ void OS_Server::finalize() { memdelete(power_manager); + memdelete(resource_loader_dummy); + args.clear(); } diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 2cc6f0c47e..f1a880ecc2 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -32,6 +32,7 @@ #include "../x11/crash_handler_x11.h" #include "../x11/power_x11.h" +#include "drivers/dummy/texture_loader_dummy.h" #include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/unix/os_unix.h" #include "main/input_default.h" @@ -65,6 +66,8 @@ class OS_Server : public OS_Unix { CrashHandler crash_handler; + ResourceFormatDummyTexture *resource_loader_dummy; + protected: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index 60a7961293..54194ff543 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -361,7 +361,7 @@ void AnimatedSprite::_notification(int p_what) { if (timeout <= 0) { - timeout = 1.0 / speed; + timeout = _get_frame_duration(); int fc = frames->get_frame_count(animation); if (frame >= fc - 1) { @@ -483,7 +483,13 @@ int AnimatedSprite::get_frame() const { void AnimatedSprite::set_speed_scale(float p_speed_scale) { + float elapsed = _get_frame_duration() - timeout; + speed_scale = MAX(p_speed_scale, 0.0f); + + // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed + _reset_timeout(); + timeout -= elapsed; } float AnimatedSprite::get_speed_scale() const { @@ -574,21 +580,22 @@ bool AnimatedSprite::is_playing() const { return playing; } -void AnimatedSprite::_reset_timeout() { - - if (!playing) - return; - +float AnimatedSprite::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { float speed = frames->get_animation_speed(animation) * speed_scale; if (speed > 0) { - timeout = 1.0 / speed; - } else { - timeout = 0; + return 1.0 / speed; } - } else { - timeout = 0; } + return 0.0; +} + +void AnimatedSprite::_reset_timeout() { + + if (!playing) + return; + + timeout = _get_frame_duration(); } void AnimatedSprite::set_animation(const StringName &p_animation) { diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index 7b91a1faef..be5b1ef6d6 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -141,6 +141,7 @@ class AnimatedSprite : public Node2D { void _res_changed(); + float _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index f998f23d3b..3878559b60 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -462,7 +462,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "1,65536,1"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 584c2f2c85..2ac6c76032 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -120,7 +120,6 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc if (mirroring.x) { double den = mirroring.x * p_scale; - double before = new_ofs.x; new_ofs.x -= den * ceil(new_ofs.x / den); } diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 240bd631a1..4d854ff578 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -375,7 +375,7 @@ void DirectionalLight::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_normal_bias", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_bias_split_scale", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_BIAS_SPLIT_SCALE); ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_depth_range", PROPERTY_HINT_ENUM, "Stable,Optimized"), "set_shadow_depth_range", "get_shadow_depth_range"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_EXP_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); BIND_ENUM_CONSTANT(SHADOW_ORTHOGONAL); BIND_ENUM_CONSTANT(SHADOW_PARALLEL_2_SPLITS); @@ -428,8 +428,8 @@ void OmniLight::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shadow_detail"), &OmniLight::get_shadow_detail); ADD_GROUP("Omni", "omni_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_detail", PROPERTY_HINT_ENUM, "Vertical,Horizontal"), "set_shadow_detail", "get_shadow_detail"); @@ -450,8 +450,8 @@ OmniLight::OmniLight() : void SpotLight::_bind_methods() { ADD_GROUP("Spot", "spot_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_param", "get_param", PARAM_SPOT_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_SPOT_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index 5512fd4f10..0577a23ea6 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -1825,6 +1825,7 @@ void PhysicalBone::_notification(int p_what) { parent_skeleton = find_skeleton_parent(get_parent()); update_bone_id(); reset_to_rest_position(); + _reset_physics_simulation_state(); break; case NOTIFICATION_EXIT_TREE: if (parent_skeleton) { @@ -1886,10 +1887,8 @@ void PhysicalBone::_bind_methods() { ClassDB::bind_method(D_METHOD("set_body_offset", "offset"), &PhysicalBone::set_body_offset); ClassDB::bind_method(D_METHOD("get_body_offset"), &PhysicalBone::get_body_offset); - ClassDB::bind_method(D_METHOD("set_static_body", "simulate"), &PhysicalBone::set_static_body); ClassDB::bind_method(D_METHOD("is_static_body"), &PhysicalBone::is_static_body); - ClassDB::bind_method(D_METHOD("set_simulate_physics", "simulate"), &PhysicalBone::set_simulate_physics); ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone::get_simulate_physics); ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone::is_simulating_physics); @@ -1913,9 +1912,7 @@ void PhysicalBone::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "joint_type", PROPERTY_HINT_ENUM, "None,PinJoint,ConeJoint,HingeJoint,SliderJoint,6DOFJoint"), "set_joint_type", "get_joint_type"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "joint_offset"), "set_joint_offset", "get_joint_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simulate_physics"), "set_simulate_physics", "get_simulate_physics"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "body_offset"), "set_body_offset", "get_body_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "static_body"), "set_static_body", "is_static_body"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "weight", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_weight", "get_weight"); @@ -2255,8 +2252,8 @@ PhysicalBone::PhysicalBone() : joint_data(NULL), static_body(false), simulate_physics(false), - _internal_static_body(!static_body), - _internal_simulate_physics(simulate_physics), + _internal_static_body(false), + _internal_simulate_physics(false), bone_id(-1), parent_skeleton(NULL), bone_name(""), diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index a7eb54c85d..76d90dc6ff 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -330,7 +330,7 @@ void Skeleton::add_bone(const String &p_name) { _make_dirty(); update_gizmo(); } -int Skeleton::find_bone(String p_name) const { +int Skeleton::find_bone(const String &p_name) const { for (int i = 0; i < bones.size(); i++) { @@ -347,6 +347,19 @@ String Skeleton::get_bone_name(int p_bone) const { return bones[p_bone].name; } +bool Skeleton::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { + + int parent_of_bone = get_bone_parent(p_bone); + + if (-1 == parent_of_bone) + return false; + + if (parent_of_bone == p_parent_bone_id) + return true; + + return is_bone_parent_of(parent_of_bone, p_parent_bone_id); +} + int Skeleton::get_bone_count() const { return bones.size(); @@ -534,18 +547,6 @@ void Skeleton::localize_rests() { } } -void _notify_physical_bones_simulation(bool start, Node *p_node) { - - for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { - _notify_physical_bones_simulation(start, p_node->get_child(i)); - } - - PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node); - if (pb) { - pb->set_simulate_physics(start); - } -} - void Skeleton::bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone) { ERR_FAIL_INDEX(p_bone, bones.size()); ERR_FAIL_COND(bones[p_bone].physical_bone); @@ -603,8 +604,67 @@ void Skeleton::_rebuild_physical_bones_cache() { } } -void Skeleton::physical_bones_simulation(bool start) { - _notify_physical_bones_simulation(start, this); +void _pb_stop_simulation(Node *p_node) { + + for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { + _pb_stop_simulation(p_node->get_child(i)); + } + + PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node); + if (pb) { + pb->set_simulate_physics(false); + pb->set_static_body(false); + } +} + +void Skeleton::physical_bones_stop_simulation() { + _pb_stop_simulation(this); +} + +void _pb_start_simulation(const Skeleton *p_skeleton, Node *p_node, const Vector<int> &p_sim_bones) { + + for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { + _pb_start_simulation(p_skeleton, p_node->get_child(i), p_sim_bones); + } + + PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node); + if (pb) { + bool sim = false; + for (int i = p_sim_bones.size() - 1; 0 <= i; --i) { + if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { + sim = true; + break; + } + } + + pb->set_simulate_physics(true); + if (sim) { + pb->set_static_body(false); + } else { + pb->set_static_body(true); + } + } +} + +void Skeleton::physical_bones_start_simulation_on(const Array &p_bones) { + + Vector<int> sim_bones; + if (p_bones.size() <= 0) { + sim_bones.push_back(0); // if no bones is specified, activate ragdoll on full body + } else { + sim_bones.resize(p_bones.size()); + int c = 0; + for (int i = sim_bones.size() - 1; 0 <= i; --i) { + if (Variant::STRING == p_bones.get(i).get_type()) { + int bone_id = find_bone(p_bones.get(i)); + if (bone_id != -1) + sim_bones[c++] = bone_id; + } + } + sim_bones.resize(c); + } + + _pb_start_simulation(this, this, sim_bones); } void _physical_bones_add_remove_collision_exception(bool p_add, Node *p_node, RID p_exception) { @@ -667,7 +727,8 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_transform", "bone_idx"), &Skeleton::get_bone_transform); - ClassDB::bind_method(D_METHOD("physical_bones_simulation", "start"), &Skeleton::physical_bones_simulation); + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton::physical_bones_stop_simulation); + ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton::physical_bones_start_simulation_on, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton::physical_bones_remove_collision_exception); @@ -683,6 +744,5 @@ Skeleton::Skeleton() { } Skeleton::~Skeleton() { - VisualServer::get_singleton()->free(skeleton); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index f0e71c8b4f..dad11960a5 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -120,9 +120,11 @@ public: // skeleton creation api void add_bone(const String &p_name); - int find_bone(String p_name) const; + int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; + bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const; + void set_bone_parent(int p_bone, int p_parent); int get_bone_parent(int p_bone) const; @@ -176,7 +178,8 @@ private: void _rebuild_physical_bones_cache(); public: - void physical_bones_simulation(bool start); + void physical_bones_stop_simulation(); + void physical_bones_start_simulation_on(const Array &p_bones); void physical_bones_add_collision_exception(RID p_exception); void physical_bones_remove_collision_exception(RID p_exception); diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index b72665aa2b..a239e7e871 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -672,13 +672,8 @@ void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { m_forwardImpulse.resize(numWheel); m_sideImpulse.resize(numWheel); - int numWheelsOnGround = 0; - //collapse all those loops into one! for (int i = 0; i < wheels.size(); i++) { - VehicleWheel &wheelInfo = *wheels[i]; - if (wheelInfo.m_raycastInfo.m_isInContact) - numWheelsOnGround++; m_sideImpulse[i] = real_t(0.); m_forwardImpulse[i] = real_t(0.); } diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 13700e0bd3..670df5cc7f 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -2316,13 +2316,10 @@ Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) { PoolVector<Vector3> vertices; PoolVector<Color> colors; - - int vtx_idx = 0; #define ADD_VTX(m_idx) \ ; \ vertices.push_back(face_points[m_idx]); \ - colors.push_back(Color(1, 1, 1, 1)); \ - vtx_idx++; + colors.push_back(Color(1, 1, 1, 1)); for (int i = 0; i < 6; i++) { diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index c0d1e62e07..a0e0137863 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -507,7 +507,6 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) { float delta = p_delta * speed_scale * cd.speed_scale; - bool backwards = delta < 0; float next_pos = cd.pos + delta; float len = cd.from->animation->get_length(); @@ -525,6 +524,8 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f if (&cd == &playback.current) { + bool backwards = delta < 0; + if (!backwards && cd.pos <= len && next_pos == len /*&& playback.blend.empty()*/) { //playback finished end_reached = true; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6f34f3e49f..1e0db6e8cd 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -656,8 +656,9 @@ ColorPicker::ColorPicker() : void ColorPickerButton::_color_changed(const Color &p_color) { + color = p_color; update(); - emit_signal("color_changed", p_color); + emit_signal("color_changed", color); } void ColorPickerButton::_modal_closed() { @@ -667,6 +668,7 @@ void ColorPickerButton::_modal_closed() { void ColorPickerButton::pressed() { + _update_picker(); popup->set_position(get_global_position() - picker->get_combined_minimum_size()); popup->popup(); picker->set_focus_on_line_edit(); @@ -679,7 +681,7 @@ void ColorPickerButton::_notification(int p_what) { Ref<StyleBox> normal = get_stylebox("normal"); Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); draw_texture_rect(Control::get_icon("bg", "ColorPickerButton"), r, true); - draw_rect(r, picker->get_pick_color()); + draw_rect(r, color); } if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST) { @@ -689,27 +691,34 @@ void ColorPickerButton::_notification(int p_what) { void ColorPickerButton::set_pick_color(const Color &p_color) { - picker->set_pick_color(p_color); + color = p_color; + if (picker) { + picker->set_pick_color(p_color); + } + update(); - emit_signal("color_changed", p_color); } Color ColorPickerButton::get_pick_color() const { - return picker->get_pick_color(); + return color; } void ColorPickerButton::set_edit_alpha(bool p_show) { - picker->set_edit_alpha(p_show); + edit_alpha = p_show; + if (picker) { + picker->set_edit_alpha(p_show); + } } bool ColorPickerButton::is_editing_alpha() const { - return picker->is_editing_alpha(); + return edit_alpha; } -ColorPicker *ColorPickerButton::get_picker() const { +ColorPicker *ColorPickerButton::get_picker() { + _update_picker(); return picker; } @@ -718,6 +727,19 @@ PopupPanel *ColorPickerButton::get_popup() const { return popup; } +void ColorPickerButton::_update_picker() { + if (!picker) { + popup = memnew(PopupPanel); + picker = memnew(ColorPicker); + popup->add_child(picker); + add_child(popup); + picker->connect("color_changed", this, "_color_changed"); + popup->connect("modal_closed", this, "_modal_closed"); + picker->set_pick_color(color); + picker->set_edit_alpha(edit_alpha); + } +} + void ColorPickerButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPickerButton::set_pick_color); @@ -737,12 +759,7 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { - popup = memnew(PopupPanel); - picker = memnew(ColorPicker); - popup->add_child(picker); - - picker->connect("color_changed", this, "_color_changed"); - popup->connect("modal_closed", this, "_modal_closed"); - - add_child(popup); + picker = NULL; + popup = NULL; + edit_alpha = true; } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 7d1a554ada..6b63e5fe60 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -118,12 +118,16 @@ class ColorPickerButton : public Button { PopupPanel *popup; ColorPicker *picker; + Color color; + bool edit_alpha; void _color_changed(const Color &p_color); void _modal_closed(); virtual void pressed(); + void _update_picker(); + protected: void _notification(int); static void _bind_methods(); @@ -135,7 +139,7 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; - ColorPicker *get_picker() const; + ColorPicker *get_picker(); PopupPanel *get_popup() const; ColorPickerButton(); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 7cb0ad5707..177582c87c 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -34,9 +34,9 @@ void Container::_child_minsize_changed() { - Size2 ms = get_combined_minimum_size(); - if (ms.width > get_size().width || ms.height > get_size().height) - minimum_size_changed(); + //Size2 ms = get_combined_minimum_size(); + //if (ms.width > get_size().width || ms.height > get_size().height) { + minimum_size_changed(); queue_sort(); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index b7c1d35fd7..3097ecaf16 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -155,12 +155,21 @@ Size2 Control::get_custom_minimum_size() const { return data.custom_minimum_size; } -Size2 Control::get_combined_minimum_size() const { +void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); minsize.x = MAX(minsize.x, data.custom_minimum_size.x); minsize.y = MAX(minsize.y, data.custom_minimum_size.y); - return minsize; + data.minimum_size_cache = minsize; + data.minimum_size_valid = true; +} + +Size2 Control::get_combined_minimum_size() const { + + if (!data.minimum_size_valid) { + const_cast<Control *>(this)->_update_minimum_size_cache(); + } + return data.minimum_size_cache; } Size2 Control::_edit_get_minimum_size() const { @@ -259,14 +268,17 @@ void Control::_update_minimum_size() { if (!is_inside_tree()) return; - data.pending_min_size_update = false; Size2 minsize = get_combined_minimum_size(); if (minsize.x > data.size_cache.x || minsize.y > data.size_cache.y) { _size_changed(); } - emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + data.updating_last_minimum_size = false; + + if (minsize != data.last_minimum_size) { + emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + } } bool Control::_get(const StringName &p_name, Variant &r_ret) const { @@ -437,8 +449,12 @@ void Control::_notification(int p_notification) { case NOTIFICATION_ENTER_TREE: { - _size_changed(); - + } break; + case NOTIFICATION_POST_ENTER_TREE: { + if (is_visible_in_tree()) { + data.minimum_size_valid = false; + _size_changed(); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -620,13 +636,12 @@ void Control::_notification(int p_notification) { if (is_inside_tree()) { _modal_stack_remove(); - minimum_size_changed(); } //remove key focus //remove modalness } else { - + data.minimum_size_valid = false; _size_changed(); } @@ -2464,17 +2479,25 @@ void Control::minimum_size_changed() { if (!is_inside_tree() || data.block_minimum_size_adjust) return; - if (data.pending_min_size_update) + Control *invalidate = this; + + //invalidate cache upwards + while (invalidate && invalidate->data.minimum_size_valid) { + invalidate->data.minimum_size_valid = false; + if (invalidate->is_set_as_toplevel()) + break; // do not go further up + invalidate = invalidate->data.parent; + } + + if (!is_visible_in_tree()) + return; + + if (data.updating_last_minimum_size) return; - data.pending_min_size_update = true; - MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); + data.updating_last_minimum_size = true; - if (!is_toplevel_control()) { - Control *pc = get_parent_control(); - if (pc) - pc->minimum_size_changed(); - } + MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); } int Control::get_v_size_flags() const { @@ -2985,7 +3008,6 @@ Control::Control() { data.h_size_flags = SIZE_FILL; data.v_size_flags = SIZE_FILL; data.expand = 1; - data.pending_min_size_update = false; data.rotation = 0; data.parent_canvas_item = NULL; data.scale = Vector2(1, 1); @@ -2995,6 +3017,8 @@ Control::Control() { data.disable_visibility_clip = false; data.h_grow = GROW_DIRECTION_END; data.v_grow = GROW_DIRECTION_END; + data.minimum_size_valid = false; + data.updating_last_minimum_size = false; data.clip_contents = false; for (int i = 0; i < 4; i++) { diff --git a/scene/gui/control.h b/scene/gui/control.h index b5453e60f5..9124256624 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -148,6 +148,11 @@ private: Point2 pos_cache; Size2 size_cache; + Size2 minimum_size_cache; + bool minimum_size_valid; + + Size2 last_minimum_size; + bool updating_last_minimum_size; float margin[4]; float anchor[4]; @@ -164,7 +169,6 @@ private: int h_size_flags; int v_size_flags; float expand; - bool pending_min_size_update; Point2 custom_minimum_size; bool pass_on_modal_close_click; @@ -244,6 +248,8 @@ private: void _modal_stack_remove(); void _modal_set_prev_focus_owner(ObjectID p_prev); + void _update_minimum_size_cache(); + protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index cd6c6bb65c..caafda2c65 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -136,9 +136,9 @@ void Range::set_as_ratio(double p_value) { double v; - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); v = Math::pow(2, exp_min + (exp_max - exp_min) * p_value); } else { @@ -155,9 +155,9 @@ void Range::set_as_ratio(double p_value) { } double Range::get_as_ratio() const { - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); double v = Math::log(get_value()) / Math::log((double)2); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d4ef735107..af368af46a 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -190,7 +190,6 @@ private: struct ItemNewline : public Item { - int line; // FIXME: Overriding base's line ? ItemNewline() { type = ITEM_NEWLINE; } }; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index a1dcf3b002..2dd5c64378 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -37,6 +37,7 @@ bool ScrollContainer::clips_input() const { Size2 ScrollContainer::get_minimum_size() const { + Ref<StyleBox> sb = get_stylebox("bg"); Size2 min_size; for (int i = 0; i < get_child_count(); i++) { @@ -64,8 +65,9 @@ Size2 ScrollContainer::get_minimum_size() const { if (v_scroll->is_visible_in_tree()) { min_size.x += v_scroll->get_minimum_size().x; } + min_size += sb->get_minimum_size(); return min_size; -}; +} void ScrollContainer::_cancel_drag() { set_physics_process_internal(false); @@ -233,6 +235,12 @@ void ScrollContainer::_notification(int p_what) { child_max_size = Size2(0, 0); Size2 size = get_size(); + Point2 ofs; + + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); + ofs += sb->get_offset(); + if (h_scroll->is_visible_in_tree()) size.y -= h_scroll->get_minimum_size().y; @@ -268,6 +276,7 @@ void ScrollContainer::_notification(int p_what) { else r.size.height = minsize.height; } + r.position += ofs; fit_child_in_rect(c, r); } update(); @@ -275,6 +284,9 @@ void ScrollContainer::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { + Ref<StyleBox> sb = get_stylebox("bg"); + draw_style_box(sb, Rect2(Vector2(), get_size())); + update_scrollbars(); } @@ -353,6 +365,8 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index bf7033e8ba..c38c411333 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -33,13 +33,6 @@ #include "label.h" #include "margin_container.h" -struct _MinSizeCache { - - int min_size; - bool will_stretch; - int final_size; -}; - Control *SplitContainer::_getch(int p_idx) const { int idx = 0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 4ff74dbb3f..c6ff8489c0 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -117,7 +117,6 @@ void TextEdit::Text::set_indent_size(int p_indent_size) { void TextEdit::Text::_update_line_cache(int p_line) const { int w = 0; - int tab_w = font->get_char_size(' ').width * indent_size; int len = text[p_line].data.length(); const CharType *str = text[p_line].data.c_str(); @@ -125,22 +124,13 @@ void TextEdit::Text::_update_line_cache(int p_line) const { //update width for (int i = 0; i < len; i++) { - if (str[i] == '\t') { - - int left = w % tab_w; - if (left == 0) - w += tab_w; - else - w += tab_w - w % tab_w; // is right... - - } else { - - w += font->get_char_size(str[i], str[i + 1]).width; - } + w += get_char_width(str[i], str[i + 1], w); } text[p_line].width_cache = w; + text[p_line].wrap_amount_cache = -1; + //update regions text[p_line].region_info.clear(); @@ -242,10 +232,32 @@ int TextEdit::Text::get_line_width(int p_line) const { return text[p_line].width_cache; } -void TextEdit::Text::clear_caches() { +void TextEdit::Text::set_line_wrap_amount(int p_line, int p_wrap_amount) const { + + ERR_FAIL_INDEX(p_line, text.size()); + + text[p_line].wrap_amount_cache = p_wrap_amount; +} + +int TextEdit::Text::get_line_wrap_amount(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), -1); + + return text[p_line].wrap_amount_cache; +} + +void TextEdit::Text::clear_width_cache() { - for (int i = 0; i < text.size(); i++) + for (int i = 0; i < text.size(); i++) { text[i].width_cache = -1; + } +} + +void TextEdit::Text::clear_wrap_cache() { + + for (int i = 0; i < text.size(); i++) { + text[i].wrap_amount_cache = -1; + } } void TextEdit::Text::clear() { @@ -270,6 +282,7 @@ void TextEdit::Text::set(int p_line, const String &p_text) { ERR_FAIL_INDEX(p_line, text.size()); text[p_line].width_cache = -1; + text[p_line].wrap_amount_cache = -1; text[p_line].data = p_text; } @@ -280,6 +293,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { line.breakpoint = false; line.hidden = false; line.width_cache = -1; + line.wrap_amount_cache = -1; line.data = p_text; text.insert(p_at, line); } @@ -288,6 +302,25 @@ void TextEdit::Text::remove(int p_at) { text.remove(p_at); } +int TextEdit::Text::get_char_width(CharType c, CharType next_c, int px) const { + + int tab_w = font->get_char_size(' ').width * indent_size; + int w = 0; + + if (c == '\t') { + + int left = px % tab_w; + if (left == 0) + w = tab_w; + else + w = tab_w - px % tab_w; // is right... + } else { + + w = font->get_char_size(c, next_c).width; + } + return w; +} + void TextEdit::_update_scrollbars() { Size2 size = get_size(); @@ -302,9 +335,12 @@ void TextEdit::_update_scrollbars() { int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1; int visible_rows = get_visible_rows(); - int num_rows = MAX(visible_rows, num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs))); - int total_rows = (is_hiding_enabled() ? get_total_unhidden_rows() : text.size()); + int first_vis_line = get_first_visible_line(); + int wi; + int num_rows = MAX(visible_rows, num_lines_from_rows(first_vis_line, cursor.wrap_ofs, visible_rows, wi)); + + int total_rows = get_total_visible_rows(); if (scroll_past_end_of_file_enabled) { total_rows += visible_rows - 1; } @@ -350,28 +386,24 @@ void TextEdit::_update_scrollbars() { if (use_vscroll) { v_scroll->show(); - v_scroll->set_max(total_rows); - v_scroll->set_page(visible_rows); + v_scroll->set_max(total_rows + get_visible_rows_offset()); + v_scroll->set_page(visible_rows + get_visible_rows_offset()); if (smooth_scroll_enabled) { v_scroll->set_step(0.25); } else { v_scroll->set_step(1); } - - update_line_scroll_pos(); - if (fabs(v_scroll->get_value() - get_line_scroll_pos()) >= 1) { - cursor.line_ofs += v_scroll->get_value() - get_line_scroll_pos(); - } + set_v_scroll(get_v_scroll()); } else { cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; v_scroll->set_value(0); v_scroll->hide(); } - if (use_hscroll) { + if (use_hscroll && !is_wrap_enabled()) { h_scroll->show(); h_scroll->set_max(total_width); @@ -422,8 +454,8 @@ void TextEdit::_update_selection_mode_pointer() { select(selection.selecting_line, selection.selecting_column, row, col); - cursor_set_line(row); - cursor_set_column(col); + cursor_set_line(row, false); + cursor_set_column(col, false); update(); click_select_held->start(); @@ -476,7 +508,7 @@ void TextEdit::_update_selection_mode_word() { cursor_set_column(selection.to_column); } } - cursor_set_line(row); + cursor_set_line(row, false); update(); click_select_held->start(); @@ -491,15 +523,15 @@ void TextEdit::_update_selection_mode_line() { col = 0; if (row < selection.selecting_line) { // cursor is above us - cursor_set_line(row - 1); + cursor_set_line(row - 1, false); selection.selecting_column = text[selection.selecting_line].length(); } else { // cursor is below us - cursor_set_line(row + 1); + cursor_set_line(row + 1, false); selection.selecting_column = 0; col = text[row].length(); } - cursor_set_column(0); + cursor_set_column(0, false); select(selection.selecting_line, selection.selecting_column, row, col); update(); @@ -517,13 +549,13 @@ void TextEdit::_notification(int p_what) { MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); if (text_changed_dirty) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); - + update_wrap_at(); } break; case NOTIFICATION_RESIZED: { cache.size = get_size(); - adjust_viewport_to_cursor(); - + _update_scrollbars(); + update_wrap_at(); } break; case NOTIFICATION_THEME_CHANGED: { @@ -540,17 +572,17 @@ void TextEdit::_notification(int p_what) { update(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (scrolling && v_scroll->get_value() != target_v_scroll) { - double target_y = target_v_scroll - v_scroll->get_value(); + if (scrolling && get_v_scroll() != target_v_scroll) { + double target_y = target_v_scroll - get_v_scroll(); double dist = sqrt(target_y * target_y); double vel = ((target_y / dist) * v_scroll_speed) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); scrolling = false; set_physics_process_internal(false); } else { - v_scroll->set_value(v_scroll->get_value() + vel); + set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; @@ -777,12 +809,14 @@ void TextEdit::_notification(int p_what) { String highlighted_text = get_selection_text(); String line_num_padding = line_numbers_zero_padded ? "0" : " "; - update_line_scroll_pos(); - int line = cursor.line_ofs - 1; - // another row may be visible during smooth scrolling - int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + int cursor_wrap_index = get_cursor_wrap_index(); + FontDrawer drawer(cache.font, Color(1, 1, 1)); + + int line = get_first_visible_line() - 1; + int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(line + 1); for (int i = 0; i < draw_amount; i++) { line++; @@ -800,269 +834,360 @@ void TextEdit::_notification(int p_what) { if (line < 0 || line >= (int)text.size()) continue; - const String &str = text[line]; + const String &fullstr = text[line]; - int char_margin = xmargin_beg - cursor.x_ofs; - int char_ofs = 0; - - int ofs_readonly = 0; - int ofs_x = 0; + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(line); + } + // ensure we at least use the font color + Color current_color = cache.font_color; if (readonly) { - ofs_readonly = cache.style_readonly->get_offset().y / 2; - ofs_x = cache.style_readonly->get_offset().x / 2; + current_color.a *= readonly_alpha; } - int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; - if (smooth_scroll_enabled) - ofs_y -= ((v_scroll->get_value() - get_line_scroll_pos()) * get_row_height()); bool underlined = false; - // check if line contains highlighted word - int highlighted_text_col = -1; - int search_text_col = -1; - int highlighted_word_col = -1; + int line_wrap_amount = times_line_wraps(line); + int last_wrap_column = 0; + Vector<String> wrap_rows = get_wrap_rows_text(line); - if (!search_text.empty()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { + if (line_wrap_index != 0) { + i++; + if (i >= draw_amount) + break; + } - if (highlighted_text.length() != 0 && highlighted_text != search_text) - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + const String &str = wrap_rows[line_wrap_index]; + int indent_px = line_wrap_index != 0 ? get_indent_level(line) * cache.font->get_char_size(' ').width : 0; - if (select_identifiers_enabled && highlighted_word.length() != 0) { - if (_is_char(highlighted_word[0])) { - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + if (line_wrap_index > 0) + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); + + int char_margin = xmargin_beg - cursor.x_ofs; + char_margin += indent_px; + int char_ofs = 0; + + int ofs_readonly = 0; + int ofs_x = 0; + if (readonly) { + ofs_readonly = cache.style_readonly->get_offset().y / 2; + ofs_x = cache.style_readonly->get_offset().x / 2; } - } - if (text.is_marked(line)) { + int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; + ofs_y -= cursor.wrap_ofs * get_row_height(); + if (smooth_scroll_enabled) + ofs_y += (-get_v_scroll_offset()) * get_row_height(); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); - } + // check if line contains highlighted word + int highlighted_text_col = -1; + int search_text_col = -1; + int highlighted_word_col = -1; + + if (!search_text.empty()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); - if (str.length() == 0) { - // draw line background if empty as we won't loop at at all - if (line == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); + if (highlighted_text.length() != 0 && highlighted_text != search_text) + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + + if (select_identifiers_enabled && highlighted_word.length() != 0) { + if (_is_char(highlighted_word[0])) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + } } - // give visual indication of empty selected line - if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - int char_w = cache.font->get_char_size(' ').width; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color); + if (text.is_marked(line)) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); } - } else { - // if it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (line == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color); + + if (str.length() == 0) { + // draw line background if empty as we won't loop at at all + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); + } + + // give visual indication of empty selected line + if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { + int char_w = cache.font->get_char_size(' ').width; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color); + } + } else { + // if it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg, get_row_height()), cache.current_line_color); + } } - } - if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { + if (line_wrap_index == 0) { + // only do these if we are on the first wrapped part of a line + + if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { #ifdef TOOLS_ENABLED - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); #else - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); #endif - } + } - // draw breakpoint marker - if (text.is_breakpoint(line)) { - if (draw_breakpoint_gutter) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_height = get_row_height() - (vertical_gap * 2); - int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); - // no transparency on marker - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); - } - } + // draw breakpoint marker + if (text.is_breakpoint(line)) { + if (draw_breakpoint_gutter) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_height = get_row_height() - (vertical_gap * 2); + int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); + // no transparency on marker + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); + } + } - // draw fold markers - if (draw_fold_gutter) { - int horizontal_gap = (cache.fold_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; - if (is_folded(line)) { - int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; - int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; - cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } else if (can_fold(line)) { - int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; - int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; - cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } - } + // draw fold markers + if (draw_fold_gutter) { + int horizontal_gap = (cache.fold_gutter_width * 30) / 100; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; + if (is_folded(line)) { + int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; + int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; + cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } else if (can_fold(line)) { + int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; + int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; + cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } + } + + // draw line numbers + if (cache.line_number_w) { + String fc = String::num(line + 1); + while (fc.length() < line_number_char_count) { + fc = line_num_padding + fc; + } - if (cache.line_number_w) { - String fc = String::num(line + 1); - while (fc.length() < line_number_char_count) { - fc = line_num_padding + fc; + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); + } } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); - } + //loop through characters in one line + for (int j = 0; j < str.length(); j++) { - //loop through characters in one line - Map<int, HighlighterInfo> color_map; - if (syntax_coloring) { - color_map = _get_line_syntax_highlighting(line); - } + if (syntax_coloring) { + if (color_map.has(last_wrap_column + j)) { + current_color = color_map[last_wrap_column + j].color; + if (readonly) { + current_color.a *= readonly_alpha; + } + } + color = current_color; + } - // ensure we at least use the font color - Color current_color = cache.font_color; - if (readonly) { - current_color.a *= readonly_alpha; - } - for (int j = 0; j < str.length(); j++) { + int char_w; + + //handle tabulator + char_w = text.get_char_width(str[j], str[j + 1], char_ofs); + + if ((char_ofs + char_margin) < xmargin_beg) { + char_ofs += char_w; - if (syntax_coloring) { - if (color_map.has(j)) { - current_color = color_map[j].color; - if (readonly) { - current_color.a *= readonly_alpha; + // line highlighting handle horizontal clipping + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + + if (j == str.length() - 1) { + // end of line when last char is skipped + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } else if ((char_ofs + char_margin) > xmargin_beg) { + // char next to margin is skipped + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); + } } + continue; } - color = current_color; - } - int char_w; - //handle tabulator + if ((char_ofs + char_margin + char_w) >= xmargin_end) { + if (syntax_coloring) + continue; + else + break; + } - if (str[j] == '\t') { - int left = char_ofs % tab_w; - if (left == 0) - char_w = tab_w; - else - char_w = tab_w - char_ofs % tab_w; // is right... + bool in_search_result = false; - } else { - char_w = cache.font->get_char_size(str[j], str[j + 1]).width; - } + if (search_text_col != -1) { + // if we are at the end check for new search result on same line + if (j >= search_text_col + search_text.length()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); - if ((char_ofs + char_margin) < xmargin_beg) { - char_ofs += char_w; + in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); - // line highlighting handle horizontal clipping - if (line == cursor.line && highlight_current_line) { + if (in_search_result) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); + } + } - if (j == str.length() - 1) { - // end of line when last char is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); + //current line highlighting + bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || last_wrap_column + j >= selection.from_column) && (line < selection.to_line || last_wrap_column + j < selection.to_column)); - } else if ((char_ofs + char_margin) > xmargin_beg) { - // char next to margin is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - xmargin_beg, get_row_height()), cache.current_line_color); + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + // draw the wrap indent offset highlight + if (line_wrap_index != 0 && j == 0) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin - indent_px, ofs_y, (char_ofs + char_margin), get_row_height()), cache.current_line_color); + } + // if its the last char draw to end of the line + if (j == str.length() - 1) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } + // actual text + if (!in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); } } - continue; - } - - if ((char_ofs + char_margin + char_w) >= xmargin_end) { - if (syntax_coloring) - continue; - else - break; - } - bool in_search_result = false; + if (in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); + } - if (search_text_col != -1) { - // if we are at the end check for new search result on same line - if (j >= search_text_col + search_text.length()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); + if (in_search_result) { + Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; - in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); - if (in_search_result) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); + if (j == search_text_col) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); + if (j == search_text_col + search_text.length() - 1) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); } - } - //current line highlighting - bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || j >= selection.from_column) && (line < selection.to_line || j < selection.to_column)); + if (highlight_all_occurrences) { + if (highlighted_text_col != -1) { - if (line == cursor.line && highlight_current_line) { - // if its the last char draw to end of the line - if (j == str.length() - 1) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + // if we are at the end check for new word on same line + if (j > highlighted_text_col + highlighted_text.length()) { + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); + } + + bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); + + // if this is the original highlighted text we don't want to highlight it again + if (cursor.line == line && cursor_wrap_index == line_wrap_index && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { + in_highlighted_word = false; + } + + if (in_highlighted_word) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + } + } } - // actual text - if (!in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); + + if (highlighted_word_col != -1) { + if (j + last_wrap_column > highlighted_word_col + highlighted_word.length()) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j + last_wrap_column); + } + underlined = (j + last_wrap_column >= highlighted_word_col && j + last_wrap_column < highlighted_word_col + highlighted_word.length()); } - } - if (in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); - } + if (brace_matching_enabled) { + if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { - if (in_search_result) { - Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; + if (brace_open_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); + if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { - if (j == search_text_col) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); - if (j == search_text_col + search_text.length() - 1) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); - } + if (brace_close_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } + } - if (highlight_all_occurrences) { - if (highlighted_text_col != -1) { + if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index) { - // if we are at the end check for new word on same line - if (j > highlighted_text_col + highlighted_text.length()) { - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); + cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); + + if (insert_mode) { + cursor_pos.y += (get_row_height() - 3); } - bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); + int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; - /* if this is the original highlighted text we don't want to highlight it again */ - if (cursor.line == line && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { - in_highlighted_word = false; - } + CharType cchar = ime_text[ofs]; + CharType next = ime_text[ofs + 1]; + int im_char_width = cache.font->get_char_size(cchar, next).width; + + if ((char_ofs + char_margin + im_char_width) >= xmargin_end) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); + } + + drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - if (in_highlighted_word) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + char_ofs += im_char_width; + ofs++; + } + } + if (ime_text.length() == 0) { + if (draw_caret) { + if (insert_mode) { + int caret_h = (block_caret) ? 4 : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + } else { + caret_w = (block_caret) ? caret_w : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + } + } } } - } - if (highlighted_word_col != -1) { - if (j > highlighted_word_col + highlighted_word.length()) { - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); + if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && block_caret && draw_caret && !insert_mode) { + color = cache.caret_background_color; + } else if (!syntax_coloring && block_caret) { + color = cache.font_color; + color.a *= readonly_alpha; } - underlined = (j >= highlighted_word_col && j < highlighted_word_col + highlighted_word.length()); - } - if (brace_matching_enabled) { - if ((brace_open_match_line == line && brace_open_match_column == j) || - (cursor.column == j && cursor.line == line && (brace_open_matching || brace_open_mismatch))) { - - if (brace_open_mismatch) - color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (str[j] >= 32) { + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (underlined) { + draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); + } + } else if (draw_tabs && str[j] == '\t') { + int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; + cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); } - if ( - (brace_close_match_line == line && brace_close_match_column == j) || - (cursor.column == j + 1 && cursor.line == line && (brace_close_matching || brace_close_mismatch))) { + char_ofs += char_w; - if (brace_close_mismatch) - color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (line_wrap_index == line_wrap_amount && j == str.length() - 1 && is_folded(line)) { + int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; + int xofs = cache.folded_eol_icon->get_width() / 2; + Color eol_color = cache.code_folding_color; + eol_color.a = 1; + cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); } } - if (cursor.column == j && cursor.line == line) { + if (cursor.column == last_wrap_column + str.length() && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) { cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); if (insert_mode) { cursor_pos.y += (get_row_height() - 3); } - - int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; if (ime_text.length() > 0) { int ofs = 0; while (true) { @@ -1092,92 +1217,17 @@ void TextEdit::_notification(int p_what) { if (ime_text.length() == 0) { if (draw_caret) { if (insert_mode) { + int char_w = cache.font->get_char_size(' ').width; int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); } else { - caret_w = (block_caret) ? caret_w : 1; + int char_w = cache.font->get_char_size(' ').width; + int caret_w = (block_caret) ? char_w : 1; VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); } } } } - - if (cursor.column == j && cursor.line == line && block_caret && draw_caret && !insert_mode) { - color = cache.caret_background_color; - } else if (!syntax_coloring && block_caret) { - color = cache.font_color; - color.a *= readonly_alpha; - } - - if (str[j] >= 32) { - int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); - if (underlined) { - draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - } - - else if (draw_tabs && str[j] == '\t') { - int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; - cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - - char_ofs += char_w; - - if (j == str.length() - 1 && is_folded(line)) { - int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; - int xofs = cache.folded_eol_icon->get_width() / 2; - Color eol_color = cache.code_folding_color; - eol_color.a = 1; - cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); - } - } - - if (cursor.column == str.length() && cursor.line == line && (char_ofs + char_margin) >= xmargin_beg) { - - cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); - - if (insert_mode) { - cursor_pos.y += (get_row_height() - 3); - } - if (ime_text.length() > 0) { - int ofs = 0; - while (true) { - if (ofs >= ime_text.length()) - break; - - CharType cchar = ime_text[ofs]; - CharType next = ime_text[ofs + 1]; - int im_char_width = cache.font->get_char_size(cchar, next).width; - - if ((char_ofs + char_margin + im_char_width) >= xmargin_end) - break; - - bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; - if (selected) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); - } else { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); - } - - drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - - char_ofs += im_char_width; - ofs++; - } - } - if (ime_text.length() == 0) { - if (draw_caret) { - if (insert_mode) { - int char_w = cache.font->get_char_size(' ').width; - int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); - } else { - int char_w = cache.font->get_char_size(' ').width; - int caret_w = (block_caret) ? char_w : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); - } - } - } } } @@ -1604,16 +1654,17 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co float rows = p_mouse.y; rows -= cache.style_normal->get_margin(MARGIN_TOP); - rows += (CLAMP(v_scroll->get_value() - get_line_scroll_pos(true), 0, 1) * get_row_height()); rows /= get_row_height(); - int first_vis_line = CLAMP(cursor.line_ofs, 0, text.size() - 1); + rows += get_v_scroll_offset(); + int first_vis_line = get_first_visible_line(); int row = first_vis_line + Math::floor(rows); + int wrap_index = 0; + + if (is_wrap_enabled() || is_hiding_enabled()) { - if (is_hiding_enabled()) { - // row will be offset by the hidden rows - int f_ofs = num_lines_from(first_vis_line, rows + 1) - 1; + int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + 1, wrap_index) - 1; row = first_vis_line + f_ofs; - row = CLAMP(row, 0, text.size() - num_lines_from(text.size() - 1, -1)); + row = CLAMP(row, 0, get_last_visible_line() + 1); } if (row < 0) @@ -1627,9 +1678,19 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); - col += cursor.x_ofs; - col = get_char_pos_for(col, get_line(row)); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); + colx += cursor.x_ofs; + col = get_char_pos_for_line(colx, row, wrap_index); + if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { + // move back one if we are at the end of the row + Vector<String> rows = get_wrap_rows_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (col >= row_end_col) + col -= 1; + } } r_row = row; @@ -1704,7 +1765,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (mb->get_command() && highlighted_word != String()) { @@ -1836,7 +1896,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (is_right_click_moving_caret()) { @@ -2453,13 +2512,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == 0 && cursor.line > 0) { cursor_set_line(cursor.line - 1); cursor_set_column(text[cursor.line].length()); } else { + bool prev_char = false; + while (cc > 0) { bool ischar = _is_text_char(text[cursor.line][cc - 1]); @@ -2514,13 +2574,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == text[cursor.line].length() && cursor.line < text.size() - 1) { cursor_set_line(cursor.line + 1); cursor_set_column(0); } else { + bool prev_char = false; + while (cc < text[cursor.line].length()) { bool ischar = _is_text_char(text[cursor.line][cc]); @@ -2572,11 +2633,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - if (k->get_command()) + if (k->get_command()) { cursor_set_line(0); - else + } else #endif - cursor_set_line(cursor_get_line() - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index > 0) { + cursor_set_line(cursor.line, true, false, cur_wrap_index - 1); + } else if (cursor.line == 0) { + cursor_set_column(0); + } else { + int new_line = cursor.line - num_lines_from(cursor.line - 1, -1); + if (line_wraps(new_line)) { + cursor_set_line(new_line, true, false, times_line_wraps(new_line)); + } else { + cursor_set_line(new_line, true, false); + } + } + } if (k->get_shift()) _post_shift_selection(); @@ -2604,22 +2679,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - { #else if (k->get_command() && k->get_alt()) { _scroll_lines_down(); break; } - if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - else { + if (k->get_command()) { + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + } else #endif - if (!is_last_visible_line(cursor.line)) { - cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index < times_line_wraps(cursor.line)) { + cursor_set_line(cursor.line, true, false, cur_wrap_index + 1); + } else if (cursor.line == get_last_unhidden_line()) { + cursor_set_column(text[cursor.line].length()); } else { - cursor_set_line(text.size() - 1); - cursor_set_column(get_line(cursor.line).length(), true); + int new_line = cursor.line + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1); + cursor_set_line(new_line, true, false, 0); } } @@ -2628,7 +2706,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_code_hint(); } break; - case KEY_DELETE: { if (readonly) @@ -2735,19 +2812,31 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(0); cursor_set_column(0); } else { - // compute whitespace symbols seq length - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[cursor.line].length()) { - CharType c = text[cursor.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') - break; - current_line_whitespace_len++; + + // move cursor column to start of wrapped row and then to start of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_wrap_index(); + int row_start_col = 0; + for (int i = 0; i < wi; i++) { + row_start_col += rows[i].length(); } + if (cursor.column == row_start_col || wi == 0) { + // compute whitespace symbols seq length + int current_line_whitespace_len = 0; + while (current_line_whitespace_len < text[cursor.line].length()) { + CharType c = text[cursor.line][current_line_whitespace_len]; + if (c != '\t' && c != ' ') + break; + current_line_whitespace_len++; + } - if (cursor_get_column() == current_line_whitespace_len) - cursor_set_column(0); - else - cursor_set_column(current_line_whitespace_len); + if (cursor_get_column() == current_line_whitespace_len) + cursor_set_column(0); + else + cursor_set_column(current_line_whitespace_len); + } else { + cursor_set_column(row_start_col); + } } if (k->get_shift()) @@ -2772,7 +2861,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(text.size() - 1, true, false); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); if (k->get_shift()) _post_shift_selection(); @@ -2787,8 +2876,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _pre_shift_selection(); if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - cursor_set_column(text[cursor.line].length()); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + + // move cursor column to end of wrapped row and then to end of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_wrap_index(); + int row_end_col = -1; + for (int i = 0; i < wi + 1; i++) { + row_end_col += rows[i].length(); + } + if (wi == rows.size() - 1 || cursor.column == row_end_col) { + cursor_set_column(text[cursor.line].length()); + } else { + cursor_set_column(row_end_col); + } if (k->get_shift()) _post_shift_selection(); @@ -2812,7 +2913,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() - num_lines_from(cursor.line, -get_visible_rows()), true, false); + int wi; + int n_line = cursor.line - num_lines_from_rows(cursor.line, get_cursor_wrap_index(), -get_visible_rows(), wi) + 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2826,14 +2929,16 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pageup + // numlock disabled. fallthrough to key_pagedown } case KEY_PAGEDOWN: { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() + num_lines_from(cursor.line, get_visible_rows()), true, false); + int wi; + int n_line = cursor.line + num_lines_from_rows(cursor.line, get_cursor_wrap_index(), get_visible_rows(), wi) - 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2851,7 +2956,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } select_all(); #else - if (k->get_alt()) { + if (k->get_alt() || (!k->get_shift() && !k->get_command() && !k->get_control())) { scancode_handled = false; break; } @@ -3078,7 +3183,7 @@ void TextEdit::_scroll_up(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); } else { - target_v_scroll = (v_scroll->get_value() - p_delta); + target_v_scroll = (get_v_scroll() - p_delta); } if (smooth_scroll_enabled) { @@ -3092,7 +3197,7 @@ void TextEdit::_scroll_up(real_t p_delta) { set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3104,20 +3209,15 @@ void TextEdit::_scroll_down(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); } else { - target_v_scroll = (v_scroll->get_value() + p_delta); + target_v_scroll = (get_v_scroll() + p_delta); } if (smooth_scroll_enabled) { - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); if (target_v_scroll > max_v_scroll) { target_v_scroll = max_v_scroll; + v_scroll->set_value(target_v_scroll); } - if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { v_scroll->set_value(target_v_scroll); } else { @@ -3125,7 +3225,7 @@ void TextEdit::_scroll_down(real_t p_delta) { set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3156,35 +3256,37 @@ void TextEdit::_scroll_lines_up() { scrolling = false; // adjust the vertical scroll - if (get_v_scroll() >= 0) { - set_v_scroll(get_v_scroll() - 1); - } + set_v_scroll(get_v_scroll() - 1); + + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); - // adjust the cursor - int num_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), get_visible_rows()); - if (cursor.line >= cursor.line_ofs + num_lines && !selection.active) { - cursor_set_line(cursor.line_ofs + num_lines, false, false); + if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + cursor_set_line(last_vis_line, false, false, last_vis_wrap); + } } } void TextEdit::_scroll_lines_down() { scrolling = false; - // calculate the maximum vertical scroll position - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - // adjust the vertical scroll - if (get_v_scroll() < max_v_scroll) { - set_v_scroll(get_v_scroll() + 1); - } + set_v_scroll(get_v_scroll() + 1); - // adjust the cursor - if (cursor.line <= cursor.line_ofs - 1 && !selection.active) { - cursor_set_line(cursor.line_ofs, false, false); + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + cursor_set_line(first_vis_line, false, false, first_vis_wrap); + } } } @@ -3238,6 +3340,8 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } + text.set_line_wrap_amount(p_line, -1); + r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -3292,6 +3396,8 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li text.set(p_from_line, pre_text + post_text); + text.set_line_wrap_amount(p_from_line, -1); + if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -3450,61 +3556,63 @@ int TextEdit::get_visible_rows() const { int total = cache.size.height; total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; total /= get_row_height(); return total; } -int TextEdit::get_total_unhidden_rows() const { - if (!is_hiding_enabled()) +int TextEdit::get_total_visible_rows() const { + + // returns the total amount of rows we need in the editor. + // This skips hidden lines and counts each wrapping of a line. + if (!is_hiding_enabled() && !is_wrap_enabled()) return text.size(); - int total_unhidden = 0; + int total_rows = 0; for (int i = 0; i < text.size(); i++) { - if (!text.is_hidden(i)) - total_unhidden++; + if (!text.is_hidden(i)) { + total_rows++; + total_rows += times_line_wraps(i); + } } - return total_unhidden; + return total_rows; } -double TextEdit::get_line_scroll_pos(bool p_recalculate) const { +void TextEdit::update_wrap_at() { - if (!is_hiding_enabled()) - return cursor.line_ofs; - if (!p_recalculate) - return line_scroll_pos; + wrap_at = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - wrap_right_offset; + update_cursor_wrap_offset(); + text.clear_wrap_cache(); - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; + for (int i = 0; i < text.size(); i++) { + // update all values that wrap + if (!line_wraps(i)) + continue; + Vector<String> rows = get_wrap_rows_text(i); + text.set_line_wrap_amount(i, rows.size() - 1); } - return new_line_scroll_pos; } -void TextEdit::update_line_scroll_pos() { +void TextEdit::adjust_viewport_to_cursor() { - if (!is_hiding_enabled()) { - line_scroll_pos = cursor.line_ofs; - return; - } + // make sure cursor is visible on the screen + scrolling = false; - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; - } - line_scroll_pos = new_line_scroll_pos; -} + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); -void TextEdit::adjust_viewport_to_cursor() { - scrolling = false; + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); - if (cursor.line_ofs > cursor.line) { - cursor.line_ofs = cursor.line; + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + // cursor is above screen + set_line_as_first_visible(cur_line, cur_wrap); + } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + // cursor is below screen + set_line_as_last_visible(cur_line, cur_wrap); } int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; @@ -3512,91 +3620,174 @@ void TextEdit::adjust_viewport_to_cursor() { visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree() && !scroll_past_end_of_file_enabled) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - int num_rows = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs)); - - // make sure the cursor is on the screen - // above the caret - if (cursor.line > (cursor.line_ofs + MAX(num_rows, visible_rows))) { - cursor.line_ofs = cursor.line - num_lines_from(cursor.line, -visible_rows) + 1; - } - // below the caret - if (cursor.line_ofs == cursor.line) { - cursor.line_ofs = cursor.line - 2; - } - int line_ofs_max = text.size() - 1; - if (!scroll_past_end_of_file_enabled) { - line_ofs_max -= num_lines_from(text.size() - 1, -visible_rows) - 1; - line_ofs_max += (h_scroll->is_visible_in_tree() ? 1 : 0); - line_ofs_max += (cursor.line == text.size() - 1 ? 1 : 0); - } - line_ofs_max = MAX(line_ofs_max, 0); - cursor.line_ofs = CLAMP(cursor.line_ofs, 0, line_ofs_max); - - // adjust x offset - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); + if (!is_wrap_enabled()) { + // adjust x offset + int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; - - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; + h_scroll->set_value(cursor.x_ofs); + update(); } void TextEdit::center_viewport_to_cursor() { - scrolling = false; - if (cursor.line_ofs > cursor.line) - cursor.line_ofs = cursor.line; + // move viewport so the cursor is in the center of the screen + scrolling = false; if (is_line_hidden(cursor.line)) unfold_line(cursor.line); + set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree()) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - if (text.size() >= visible_rows) { - int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : MAX(num_lines_from(text.size() - 1, -visible_rows), 0)); - cursor.line_ofs = CLAMP(cursor.line - num_lines_from(MAX(cursor.line - visible_rows / 2, 0), -visible_rows / 2), 0, max_ofs); + if (is_wrap_enabled()) { + // center x offset + int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line); + + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; + + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; } - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); + h_scroll->set_value(cursor.x_ofs); - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; + update(); +} - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; +void TextEdit::update_cursor_wrap_offset() { + int first_vis_line = get_first_visible_line(); + if (line_wraps(first_vis_line)) { + cursor.wrap_ofs = MIN(cursor.wrap_ofs, times_line_wraps(first_vis_line)); + } else { + cursor.wrap_ofs = 0; + } + set_line_as_first_visible(cursor.line_ofs, cursor.wrap_ofs); +} - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); +bool TextEdit::line_wraps(int line) const { + + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!is_wrap_enabled()) + return false; + return text.get_line_width(line) > wrap_at; +} + +int TextEdit::times_line_wraps(int line) const { + + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!line_wraps(line)) + return 0; + + int wrap_amount = text.get_line_wrap_amount(line); + if (wrap_amount == -1) { + // update the value + Vector<String> rows = get_wrap_rows_text(line); + wrap_amount = rows.size() - 1; + text.set_line_wrap_amount(line, wrap_amount); } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; - update(); + + return wrap_amount; +} + +Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>()); + + Vector<String> lines; + if (!line_wraps(p_line)) { + lines.push_back(text[p_line]); + return lines; + } + + int px = 0; + int col = 0; + String line_text = text[p_line]; + String wrap_substring = ""; + + int word_px = 0; + String word_str = ""; + int cur_wrap_index = 0; + + int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + + while (col < line_text.length()) { + char c = line_text[col]; + int w = text.get_char_width(c, line_text[col + 1], px + word_px); + + int indent_ofs = (cur_wrap_index != 0 ? tab_offset_px : 0); + + word_str += c; + word_px += w; + if (c == ' ') { + // end of a word; add this word to the substring + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + + if ((indent_ofs + px + word_px) > wrap_at) { + // do not want to add this word + if (indent_ofs + word_px > wrap_at) { + // not enough space; add it anyway + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + lines.push_back(wrap_substring); + // reset for next wrap + cur_wrap_index++; + wrap_substring = ""; + px = 0; + } + col++; + } + // line ends before hit wrap_at; add this word to the substring + wrap_substring += word_str; + px += word_px; + lines.push_back(wrap_substring); + return lines; +} + +int TextEdit::get_cursor_wrap_index() const { + + return get_line_wrap_index_at_col(cursor.line, cursor.column); +} + +int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + if (!line_wraps(p_line)) + return 0; + + // loop through wraps in the line text until we get to the column + int wrap_index = 0; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_column) + break; + } + return wrap_index; } void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { @@ -3608,7 +3799,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { if (cursor.column > get_line(cursor.line).length()) cursor.column = get_line(cursor.line).length(); - cursor.last_fit_x = get_column_x_offset(cursor.column, get_line(cursor.line)); + cursor.last_fit_x = get_column_x_offset_for_line(cursor.column, cursor.line); if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3620,7 +3811,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { } } -void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden) { +void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { if (setting_row) return; @@ -3629,8 +3820,8 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ if (p_row < 0) p_row = 0; - if (p_row >= (int)text.size()) - p_row = (int)text.size() - 1; + if (p_row >= text.size()) + p_row = text.size() - 1; if (!p_can_be_hidden) { if (is_line_hidden(CLAMP(p_row, 0, text.size() - 1))) { @@ -3648,7 +3839,18 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ } } cursor.line = p_row; - cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line)); + + int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index); + if (is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { + Vector<String> rows = get_wrap_rows_text(p_row); + int row_end_col = 0; + for (int i = 0; i < p_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (n_col >= row_end_col) + n_col -= 1; + } + cursor.column = n_col; if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3725,9 +3927,25 @@ void TextEdit::_scroll_moved(double p_to_val) { if (h_scroll->is_visible_in_tree()) cursor.x_ofs = h_scroll->get_value(); if (v_scroll->is_visible_in_tree()) { - double val = v_scroll->get_value(); - cursor.line_ofs = num_lines_from(0, (int)floor(val)); - line_scroll_pos = (int)floor(val); + + // set line ofs and wrap ofs + int v_scroll_i = floor(get_v_scroll()); + int sc = 0; + int n_line; + for (n_line = 0; n_line < text.size(); n_line++) { + if (!is_line_hidden(n_line)) { + sc++; + sc += times_line_wraps(n_line); + if (sc > v_scroll_i) + break; + } + } + int line_wrap_amount = times_line_wraps(n_line); + int wi = line_wrap_amount - (sc - v_scroll_i - 1); + wi = CLAMP(wi, 0, line_wrap_amount); + + cursor.line_ofs = n_line; + cursor.wrap_ofs = wi; } update(); } @@ -3737,29 +3955,73 @@ int TextEdit::get_row_height() const { return cache.font->get_height() + cache.line_spacing; } -int TextEdit::get_char_pos_for(int p_px, String p_str) const { +int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { - int px = 0; - int c = 0; + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - int tab_w = cache.font->get_char_size(' ').width * indent_size; + if (line_wraps(p_line)) { - while (c < p_str.length()) { + int line_wrap_amount = times_line_wraps(p_line); + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (p_wrap_index > line_wrap_amount) + p_wrap_index = line_wrap_amount; + if (p_wrap_index > 0) + p_px -= wrap_offset_px; + else + p_wrap_index = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int c_pos = get_char_pos_for(p_px, rows[p_wrap_index]); + for (int i = 0; i < p_wrap_index; i++) { + String s = rows[i]; + c_pos += s.length(); + } - int w = 0; + return c_pos; + } else { - if (p_str[c] == '\t') { + return get_char_pos_for(p_px, text[p_line]); + } +} - int left = px % tab_w; - if (left == 0) - w = tab_w; - else - w = tab_w - px % tab_w; // is right... +int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { - } else { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - w = cache.font->get_char_size(p_str[c], p_str[c + 1]).width; + if (line_wraps(p_line)) { + + int n_char = p_char; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int wrap_index = 0; + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_char) + break; + n_char -= s.length(); } + int px = get_column_x_offset(n_char, rows[wrap_index]); + + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (wrap_index != 0) + px += wrap_offset_px; + + return px; + } else { + + return get_column_x_offset(p_char, text[p_line]); + } +} + +int TextEdit::get_char_pos_for(int p_px, String p_str) const { + + int px = 0; + int c = 0; + + while (c < p_str.length()) { + + int w = text.get_char_width(p_str[c], p_str[c + 1], px); if (p_px < (px + w / 2)) break; @@ -3770,28 +4032,16 @@ int TextEdit::get_char_pos_for(int p_px, String p_str) const { return c; } -int TextEdit::get_column_x_offset(int p_char, String p_str) { +int TextEdit::get_column_x_offset(int p_char, String p_str) const { int px = 0; - int tab_w = cache.font->get_char_size(' ').width * indent_size; - for (int i = 0; i < p_char; i++) { if (i >= p_str.length()) break; - if (p_str[i] == '\t') { - - int left = px % tab_w; - if (left == 0) - px += tab_w; - else - px += tab_w - px % tab_w; // is right... - - } else { - px += cache.font->get_char_size(p_str[i], p_str[i + 1]).width; - } + px += text.get_char_width(p_str[i], p_str[i + 1], px); } return px; @@ -3867,7 +4117,7 @@ void TextEdit::set_text(String p_text) { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; cursor_set_line(0); cursor_set_column(0); @@ -3953,7 +4203,7 @@ void TextEdit::_clear() { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; } @@ -3975,14 +4225,14 @@ bool TextEdit::is_readonly() const { return readonly; } -void TextEdit::set_wrap(bool p_wrap) { +void TextEdit::set_wrap_enabled(bool p_wrap_enabled) { - wrap = p_wrap; + wrap_enabled = p_wrap_enabled; } -bool TextEdit::is_wrapping() const { +bool TextEdit::is_wrap_enabled() const { - return wrap; + return wrap_enabled; } void TextEdit::set_max_chars(int p_max_chars) { @@ -4131,7 +4381,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); color_region_cache.clear(); - text.clear_caches(); + text.clear_width_cache(); } void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color) { @@ -4151,7 +4401,7 @@ Color TextEdit::get_keyword_color(String p_keyword) const { void TextEdit::add_color_region(const String &p_begin_key, const String &p_end_key, const Color &p_color, bool p_line_only) { color_regions.push_back(ColorRegion(p_begin_key, p_end_key, p_color, p_line_only)); - text.clear_caches(); + text.clear_width_cache(); update(); } @@ -4648,52 +4898,99 @@ void TextEdit::unhide_all_lines() { update(); } -int TextEdit::num_lines_from(int p_line_from, int unhidden_amount) const { +int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // returns the number of hidden and unhidden lines from p_line_from to p_line_from + amount of visible lines - ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(unhidden_amount)); + // returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines) + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); if (!is_hiding_enabled()) - return ABS(unhidden_amount); + return ABS(visible_amount); + int num_visible = 0; int num_total = 0; - if (unhidden_amount >= 0) { + if (visible_amount >= 0) { for (int i = p_line_from; i < text.size(); i++) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } else { - unhidden_amount = ABS(unhidden_amount); + visible_amount = ABS(visible_amount); for (int i = p_line_from; i >= 0; i--) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } return num_total; } -bool TextEdit::is_last_visible_line(int p_line) const { +int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); + // returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows) + // wrap index is set to the wrap index of the last line + wrap_index = 0; + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); - if (p_line == text.size() - 1) - return true; + if (!is_hiding_enabled() && !is_wrap_enabled()) + return ABS(visible_amount); + + int num_visible = 0; + int num_total = 0; + if (visible_amount == 0) { + num_total = 0; + wrap_index = 0; + } else if (visible_amount > 0) { + int i; + num_visible -= p_wrap_index_from; + for (i = p_line_from; i < text.size(); i++) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - (num_visible - visible_amount); + } else { + visible_amount = ABS(visible_amount); + int i; + num_visible -= times_line_wraps(p_line_from) - p_wrap_index_from; + for (i = p_line_from; i >= 0; i--) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = (num_visible - visible_amount); + } + wrap_index = MAX(wrap_index, 0); + return num_total; +} + +int TextEdit::get_last_unhidden_line() const { + // returns the last line in the text that is not hidden if (!is_hiding_enabled()) - return false; + return text.size() - 1; - for (int i = p_line + 1; i < text.size(); i++) { - if (!is_line_hidden(i)) - return false; + int last_line; + for (last_line = text.size() - 1; last_line > 0; last_line--) { + if (!is_line_hidden(last_line)) { + break; + } } - - return true; + return last_line; } int TextEdit::get_indent_level(int p_line) const { @@ -4713,7 +5010,7 @@ int TextEdit::get_indent_level(int p_line) const { break; } } - return tab_count + whitespace_count / indent_size; + return tab_count * indent_size + whitespace_count; } bool TextEdit::is_line_comment(int p_line) const { @@ -5061,6 +5358,7 @@ bool TextEdit::is_drawing_tabs() const { void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } + bool TextEdit::is_overriding_selected_font_color() const { return override_selected_font_color; } @@ -5081,58 +5379,143 @@ bool TextEdit::is_insert_text_operation() { uint32_t TextEdit::get_version() const { return current_op.version; } + uint32_t TextEdit::get_saved_version() const { return saved_version; } + void TextEdit::tag_saved_version() { saved_version = get_version(); } -int TextEdit::get_v_scroll() const { +double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { - return v_scroll->get_value(); -} -void TextEdit::set_v_scroll(int p_scroll) { + if (!is_wrap_enabled() && !is_hiding_enabled()) + return p_line; - if (p_scroll < 0) { - p_scroll = 0; - } - if (!scroll_past_end_of_file_enabled) { - if (p_scroll + get_visible_rows() > get_total_unhidden_rows()) { - int num_rows = num_lines_from(CLAMP(p_scroll, 0, text.size() - 1), MIN(get_visible_rows(), text.size() - 1 - p_scroll)); - p_scroll = text.size() - num_rows; + // count the number of visible lines up to this line + double new_line_scroll_pos = 0; + int to = CLAMP(p_line, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) { + new_line_scroll_pos++; + new_line_scroll_pos += times_line_wraps(i); } } + new_line_scroll_pos += p_wrap_index; + return new_line_scroll_pos; +} + +void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { + + set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index)); +} + +void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { + + int visible_rows = get_visible_rows(); + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -visible_rows / 2, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi)); +} + +void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { + + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -get_visible_rows() - 1, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi) + get_visible_rows_offset()); +} + +int TextEdit::get_first_visible_line() const { + + return CLAMP(cursor.line_ofs, 0, text.size() - 1); +} + +int TextEdit::get_last_visible_line() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); + return last_vis_line; +} + +int TextEdit::get_last_visible_line_wrap_index() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + return wi; +} + +double TextEdit::get_visible_rows_offset() const { + + double total = cache.size.height; + total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; + total /= (double)get_row_height(); + total = total - floor(total); + total = -CLAMP(total, 0.001, 1) + 1; + return total; +} + +double TextEdit::get_v_scroll_offset() const { + + double val = get_v_scroll() - floor(get_v_scroll()); + return CLAMP(val, 0, 1); +} + +double TextEdit::get_v_scroll() const { + + return v_scroll->get_value(); +} + +void TextEdit::set_v_scroll(double p_scroll) { + v_scroll->set_value(p_scroll); - cursor.line_ofs = num_lines_from(0, p_scroll); - line_scroll_pos = p_scroll; + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); + if (p_scroll >= max_v_scroll - 1.0) + _scroll_moved(v_scroll->get_value()); } int TextEdit::get_h_scroll() const { return h_scroll->get_value(); } + void TextEdit::set_h_scroll(int p_scroll) { + if (p_scroll < 0) { + p_scroll = 0; + } h_scroll->set_value(p_scroll); } void TextEdit::set_smooth_scroll_enabled(bool p_enable) { + v_scroll->set_smooth_scroll_enabled(p_enable); smooth_scroll_enabled = p_enable; } bool TextEdit::is_smooth_scroll_enabled() const { + return smooth_scroll_enabled; } void TextEdit::set_v_scroll_speed(float p_speed) { + v_scroll_speed = p_speed; } float TextEdit::get_v_scroll_speed() const { + return v_scroll_speed; } @@ -5612,7 +5995,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); @@ -5629,8 +6012,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); - ClassDB::bind_method(D_METHOD("set_wrap", "enable"), &TextEdit::set_wrap); - ClassDB::bind_method(D_METHOD("is_wrapping"), &TextEdit::is_wrapping); + ClassDB::bind_method(D_METHOD("set_wrap_enabled", "enable"), &TextEdit::set_wrap_enabled); + ClassDB::bind_method(D_METHOD("is_wrap_enabled"), &TextEdit::is_wrap_enabled); // ClassDB::bind_method(D_METHOD("set_max_chars", "amount"), &TextEdit::set_max_chars); // ClassDB::bind_method(D_METHOD("get_max_char"), &TextEdit::get_max_chars); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); @@ -5708,7 +6091,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_lines"), "set_wrap", "is_wrapping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "max_chars"), "set_max_chars", "get_max_chars"); ADD_GROUP("Caret", "caret_"); @@ -5743,7 +6126,8 @@ TextEdit::TextEdit() { draw_caret = true; max_chars = 0; clear(); - wrap = false; + wrap_enabled = false; + wrap_right_offset = 10; set_focus_mode(FOCUS_ALL); syntax_highlighter = NULL; _update_caches(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 60c6ab4929..5c82d1ac20 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -76,6 +76,7 @@ public: bool marked : 1; bool breakpoint : 1; bool hidden : 1; + int wrap_amount_cache : 24; Map<int, ColorRegionInfo> region_info; String data; }; @@ -94,6 +95,9 @@ public: void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; } int get_line_width(int p_line) const; int get_max_width(bool p_exclude_hidden = false) const; + int get_char_width(CharType c, CharType next_c, int px) const; + void set_line_wrap_amount(int p_line, int p_wrap_amount) const; + int get_line_wrap_amount(int p_line) const; const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const; void set(int p_line, const String &p_text); void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; } @@ -106,7 +110,8 @@ public: void remove(int p_at); int size() const { return text.size(); } void clear(); - void clear_caches(); + void clear_width_cache(); + void clear_wrap_cache(); _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } Text() { indent_size = 4; } }; @@ -115,7 +120,7 @@ private: struct Cursor { int last_fit_x; int line, column; ///< cursor - int x_ofs, line_ofs; + int x_ofs, line_ofs, wrap_ofs; } cursor; struct Selection { @@ -263,8 +268,11 @@ private: bool block_caret; bool right_click_moves_caret; + bool wrap_enabled; + int wrap_at; + int wrap_right_offset; + bool setting_row; - bool wrap; bool draw_tabs; bool override_selected_font_color; bool cursor_changed_dirty; @@ -321,19 +329,34 @@ private: int search_result_line; int search_result_col; - double line_scroll_pos; - bool context_menu_enabled; int get_visible_rows() const; - int get_total_unhidden_rows() const; - double get_line_scroll_pos(bool p_recalculate = false) const; - void update_line_scroll_pos(); - + int get_total_visible_rows() const; + + void update_cursor_wrap_offset(); + void update_wrap_at(); + bool line_wraps(int line) const; + int times_line_wraps(int line) const; + Vector<String> get_wrap_rows_text(int p_line) const; + int get_cursor_wrap_index() const; + int get_line_wrap_index_at_col(int p_line, int p_column) const; int get_char_count(); + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; + void set_line_as_first_visible(int p_line, int p_wrap_index = 0); + void set_line_as_center_visible(int p_line, int p_wrap_index = 0); + void set_line_as_last_visible(int p_line, int p_wrap_index = 0); + int get_first_visible_line() const; + int get_last_visible_line() const; + int get_last_visible_line_wrap_index() const; + double get_visible_rows_offset() const; + double get_v_scroll_offset() const; + + int get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; + int get_column_x_offset_for_line(int p_char, int p_line) const; int get_char_pos_for(int p_px, String p_str) const; - int get_column_x_offset(int p_char, String p_str); + int get_column_x_offset(int p_char, String p_str) const; void adjust_viewport_to_cursor(); double get_scroll_line_diff() const; @@ -455,8 +478,10 @@ public: bool is_line_hidden(int p_line) const; void fold_all_lines(); void unhide_all_lines(); - int num_lines_from(int p_line_from, int unhidden_amount) const; - bool is_last_visible_line(int p_line) const; + int num_lines_from(int p_line_from, int visible_amount) const; + int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const; + int get_last_unhidden_line() const; + bool can_fold(int p_line) const; bool is_folded(int p_line) const; void fold_line(int p_line); @@ -493,7 +518,7 @@ public: void center_viewport_to_cursor(); void cursor_set_column(int p_col, bool p_adjust_viewport = true); - void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true); + void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); int cursor_get_column() const; int cursor_get_line() const; @@ -516,8 +541,8 @@ public: void set_max_chars(int p_max_chars); int get_max_chars() const; - void set_wrap(bool p_wrap); - bool is_wrapping() const; + void set_wrap_enabled(bool p_wrap_enabled); + bool is_wrap_enabled() const; void clear(); @@ -578,8 +603,8 @@ public: Color get_member_color(String p_member) const; void clear_member_keywords(); - int get_v_scroll() const; - void set_v_scroll(int p_scroll); + double get_v_scroll() const; + void set_v_scroll(double p_scroll); int get_h_scroll() const; void set_h_scroll(int p_scroll); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index e6ea4e4b4a..3643aedb85 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -176,6 +176,9 @@ void Node::_propagate_ready() { data.children[i]->_propagate_ready(); } data.blocked--; + + notification(NOTIFICATION_POST_ENTER_TREE); + if (data.ready_first) { data.ready_first = false; notification(NOTIFICATION_READY); diff --git a/scene/main/node.h b/scene/main/node.h index 4ff1247e14..540f34cba7 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -237,6 +237,7 @@ public: NOTIFICATION_TRANSLATION_CHANGED = 24, NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, + NOTIFICATION_POST_ENTER_TREE = 27, }; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 295f131db3..5a3447bec0 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1345,7 +1345,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_label->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -ttp->get_margin(MARGIN_RIGHT)); gui.tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM, Control::ANCHOR_END, -ttp->get_margin(MARGIN_BOTTOM)); gui.tooltip_label->set_text(tooltip.strip_edges()); - Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_combined_minimum_size() + ttp->get_minimum_size()); + Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_minimum_size() + ttp->get_minimum_size()); Rect2 vr = gui.tooltip_label->get_viewport_rect(); if (r.size.x + r.position.x > vr.size.x) r.position.x = vr.size.x - r.size.x; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 5ac9344f31..3ea856541e 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -544,6 +544,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("updown", "SpinBox", make_icon(spinbox_updown_png)); + //scroll container + Ref<StyleBoxEmpty> empty; + empty.instance(); + theme->set_stylebox("bg", "ScrollContainer", empty); + // WindowDialog theme->set_stylebox("panel", "WindowDialog", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6)); diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index f41a26a680..f0870349d9 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -1080,6 +1080,11 @@ void DynamicFont::update_oversampling() { if (E->self()->data_at_size.is_valid()) { E->self()->data_at_size->update_oversampling(); + + if (E->self()->outline_data_at_size.is_valid()) { + E->self()->outline_data_at_size->update_oversampling(); + } + changed.push_back(Ref<DynamicFont>(E->self())); } E = E->next(); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index d87644381c..b0620d3363 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -222,7 +222,6 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { continue; Array a = surface_get_arrays(i); - int vcount = 0; if (i == 0) { arrays = a; @@ -230,6 +229,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { index_accum += v.size(); } else { + int vcount = 0; for (int j = 0; j < arrays.size(); j++) { if (arrays[j].get_type() == Variant::NIL || a[j].get_type() == Variant::NIL) { @@ -1194,8 +1194,6 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe for (int j = 0; j < 3; j++) { - int vertex_idx = gen_vertices[gen_indices[i + j]]; - SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { diff --git a/thirdparty/README.md b/thirdparty/README.md index 1d4e56d42e..0848be285b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -218,7 +218,7 @@ Godot-made change marked with `// -- GODOT --` comments. ## libwebp - Upstream: https://chromium.googlesource.com/webm/libwebp/ -- Version: 0.6.1 +- Version: 1.0.0 - License: BSD-3-Clause Files extracted from upstream source: diff --git a/thirdparty/libwebp/AUTHORS b/thirdparty/libwebp/AUTHORS index 70423cb4dd..83c7b9c5eb 100644 --- a/thirdparty/libwebp/AUTHORS +++ b/thirdparty/libwebp/AUTHORS @@ -2,25 +2,38 @@ Contributors: - Charles Munger (clm at google dot com) - Christian Duvivier (cduvivier at google dot com) - Djordje Pesut (djordje dot pesut at imgtec dot com) +- Hui Su (huisu at google dot com) - James Zern (jzern at google dot com) - Jan Engelhardt (jengelh at medozas dot de) +- Jehan (jehan at girinstud dot io) - Johann (johann dot koenig at duck dot com) - Jovan Zelincevic (jovan dot zelincevic at imgtec dot com) - Jyrki Alakuijala (jyrki at google dot com) -- levytamar82 (tamar dot levy at intel dot com) +- Lode Vandevenne (lode at google dot com) - Lou Quillio (louquillio at google dot com) - Mans Rullgard (mans at mansr dot com) +- Marcin Kowalczyk (qrczak at google dot com) - Martin Olsson (mnemo at minimum dot se) - Mikołaj Zalewski (mikolajz at google dot com) +- Mislav Bradac (mislavm at google dot com) +- Nico Weber (thakis at chromium dot org) - Noel Chromium (noel at chromium dot org) +- Owen Rodley (orodley at google dot com) +- Parag Salasakar (img dot mips1 at gmail dot com) - Pascal Massimino (pascal dot massimino at gmail dot com) - Paweł Hajdan, Jr (phajdan dot jr at chromium dot org) - Pierre Joye (pierre dot php at gmail dot com) - Sam Clegg (sbc at chromium dot org) +- Scott Hancher (seh at google dot com) - Scott LaVarnway (slavarnway at google dot com) - Scott Talbot (s at chikachow dot org) - Slobodan Prijic (slobodan dot prijic at imgtec dot com) - Somnath Banerjee (somnath dot banerjee at gmail dot com) +- Sriraman Tallam (tmsriram at google dot com) +- Tamar Levy (tamar dot levy at intel dot com) - Timothy Gu (timothygu99 at gmail dot com) - Urvang Joshi (urvang at google dot com) - Vikas Arora (vikasa at google dot com) +- Vincent Rabaud (vrabaud at google dot com) +- Vlad Tsyrklevich (vtsyrklevich at chromium dot org) +- Yang Zhang (yang dot zhang at arm dot com) diff --git a/thirdparty/libwebp/src/dec/frame_dec.c b/thirdparty/libwebp/src/dec/frame_dec.c index 517d0f5850..a9d5430d00 100644 --- a/thirdparty/libwebp/src/dec/frame_dec.c +++ b/thirdparty/libwebp/src/dec/frame_dec.c @@ -400,7 +400,9 @@ static void DitherRow(VP8Decoder* const dec) { #define MACROBLOCK_VPOS(mb_y) ((mb_y) * 16) // vertical position of a MB // Finalize and transmit a complete row. Return false in case of user-abort. -static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { +static int FinishRow(void* arg1, void* arg2) { + VP8Decoder* const dec = (VP8Decoder*)arg1; + VP8Io* const io = (VP8Io*)arg2; int ok = 1; const VP8ThreadContext* const ctx = &dec->thread_ctx_; const int cache_id = ctx->id_; @@ -448,10 +450,9 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { if (y_end > io->crop_bottom) { y_end = io->crop_bottom; // make sure we don't overflow on last row. } + // If dec->alpha_data_ is not NULL, we have some alpha plane present. io->a = NULL; if (dec->alpha_data_ != NULL && y_start < y_end) { - // TODO(skal): testing presence of alpha with dec->alpha_data_ is not a - // good idea. io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start); if (io->a == NULL) { return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, @@ -558,7 +559,6 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { if (io->bypass_filtering) { dec->filter_type_ = 0; } - // TODO(skal): filter type / strength / sharpness forcing // Define the area where we can skip in-loop filtering, in case of cropping. // @@ -569,8 +569,6 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { // Means: there's a dependency chain that goes all the way up to the // top-left corner of the picture (MB #0). We must filter all the previous // macroblocks. - // TODO(skal): add an 'approximate_decoding' option, that won't produce - // a 1:1 bit-exactness for complex filtering? { const int extra_pixels = kFilterExtraRows[dec->filter_type_]; if (dec->filter_type_ == 2) { @@ -651,7 +649,7 @@ static int InitThreadContext(VP8Decoder* const dec) { } worker->data1 = dec; worker->data2 = (void*)&dec->thread_ctx_.io_; - worker->hook = (WebPWorkerHook)FinishRow; + worker->hook = FinishRow; dec->num_caches_ = (dec->filter_type_ > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1; } else { diff --git a/thirdparty/libwebp/src/dec/vp8_dec.c b/thirdparty/libwebp/src/dec/vp8_dec.c index 6212efd179..c904b529f6 100644 --- a/thirdparty/libwebp/src/dec/vp8_dec.c +++ b/thirdparty/libwebp/src/dec/vp8_dec.c @@ -491,7 +491,7 @@ static int GetCoeffsAlt(VP8BitReader* const br, return 16; } -WEBP_TSAN_IGNORE_FUNCTION static void InitGetCoeffs(void) { +static WEBP_TSAN_IGNORE_FUNCTION void InitGetCoeffs(void) { if (GetCoeffs == NULL) { if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) { GetCoeffs = GetCoeffsAlt; diff --git a/thirdparty/libwebp/src/dec/vp8i_dec.h b/thirdparty/libwebp/src/dec/vp8i_dec.h index 28244d9d7a..c929933e1c 100644 --- a/thirdparty/libwebp/src/dec/vp8i_dec.h +++ b/thirdparty/libwebp/src/dec/vp8i_dec.h @@ -30,9 +30,9 @@ extern "C" { // Various defines and enums // version numbers -#define DEC_MAJ_VERSION 0 -#define DEC_MIN_VERSION 6 -#define DEC_REV_VERSION 1 +#define DEC_MAJ_VERSION 1 +#define DEC_MIN_VERSION 0 +#define DEC_REV_VERSION 0 // YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). // Constraints are: We need to store one 16x16 block of luma samples (y), diff --git a/thirdparty/libwebp/src/dec/vp8l_dec.c b/thirdparty/libwebp/src/dec/vp8l_dec.c index 42ea3b5e4c..0570f53a77 100644 --- a/thirdparty/libwebp/src/dec/vp8l_dec.c +++ b/thirdparty/libwebp/src/dec/vp8l_dec.c @@ -1643,17 +1643,17 @@ int VP8LDecodeImage(VP8LDecoder* const dec) { #if !defined(WEBP_REDUCE_SIZE) if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; - - if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { - // need the alpha-multiply functions for premultiplied output or rescaling - WebPInitAlphaProcessing(); - } #else if (io->use_scaling) { dec->status_ = VP8_STATUS_INVALID_PARAM; goto Err; } #endif + if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { + // need the alpha-multiply functions for premultiplied output or rescaling + WebPInitAlphaProcessing(); + } + if (!WebPIsRGBMode(dec->output_->colorspace)) { WebPInitConvertARGBToYUV(); if (dec->output_->u.YUVA.a != NULL) WebPInitAlphaProcessing(); diff --git a/thirdparty/libwebp/src/demux/demux.c b/thirdparty/libwebp/src/demux/demux.c index 79c24a5a7f..684215e3de 100644 --- a/thirdparty/libwebp/src/demux/demux.c +++ b/thirdparty/libwebp/src/demux/demux.c @@ -23,9 +23,9 @@ #include "src/webp/demux.h" #include "src/webp/format_constants.h" -#define DMUX_MAJ_VERSION 0 -#define DMUX_MIN_VERSION 3 -#define DMUX_REV_VERSION 3 +#define DMUX_MAJ_VERSION 1 +#define DMUX_MIN_VERSION 0 +#define DMUX_REV_VERSION 0 typedef struct { size_t start_; // start location of the data diff --git a/thirdparty/libwebp/src/dsp/alpha_processing.c b/thirdparty/libwebp/src/dsp/alpha_processing.c index 590e3bc312..819d1391f2 100644 --- a/thirdparty/libwebp/src/dsp/alpha_processing.c +++ b/thirdparty/libwebp/src/dsp/alpha_processing.c @@ -366,6 +366,16 @@ static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); } +#ifdef WORDS_BIGENDIAN +static void PackARGB_C(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, uint32_t* out) { + int i; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } +} +#endif + static void PackRGB_C(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out) { int i, offset = 0; @@ -381,6 +391,10 @@ int (*WebPDispatchAlpha)(const uint8_t*, int, int, int, uint8_t*, int); void (*WebPDispatchAlphaToGreen)(const uint8_t*, int, int, int, uint32_t*, int); int (*WebPExtractAlpha)(const uint8_t*, int, int, int, uint8_t*, int); void (*WebPExtractGreen)(const uint32_t* argb, uint8_t* alpha, int size); +#ifdef WORDS_BIGENDIAN +void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int, uint32_t*); +#endif void (*WebPPackRGB)(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out); @@ -395,16 +409,14 @@ extern void WebPInitAlphaProcessingSSE2(void); extern void WebPInitAlphaProcessingSSE41(void); extern void WebPInitAlphaProcessingNEON(void); -static volatile VP8CPUInfo alpha_processing_last_cpuinfo_used = - (VP8CPUInfo)&alpha_processing_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessing(void) { - if (alpha_processing_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitAlphaProcessing) { WebPMultARGBRow = WebPMultARGBRow_C; WebPMultRow = WebPMultRow_C; WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b_C; +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_C; +#endif WebPPackRGB = PackRGB_C; #if !WEBP_NEON_OMIT_C_CODE WebPApplyAlphaMultiply = ApplyAlphaMultiply_C; @@ -451,9 +463,10 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessing(void) { assert(WebPDispatchAlphaToGreen != NULL); assert(WebPExtractAlpha != NULL); assert(WebPExtractGreen != NULL); +#ifdef WORDS_BIGENDIAN + assert(WebPPackARGB != NULL); +#endif assert(WebPPackRGB != NULL); assert(WebPHasAlpha8b != NULL); assert(WebPHasAlpha32b != NULL); - - alpha_processing_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c b/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c index e0dc91bab9..0090e87cd1 100644 --- a/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c +++ b/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c @@ -125,6 +125,49 @@ static void MultARGBRow_MIPSdspR2(uint32_t* const ptr, int width, } } +#ifdef WORDS_BIGENDIAN +static void PackARGB_MIPSdspR2(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out) { + int temp0, temp1, temp2, temp3, offset; + const int rest = len & 1; + const uint32_t* const loop_end = out + len - rest; + const int step = 4; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} +#endif // WORDS_BIGENDIAN + static void PackRGB_MIPSdspR2(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out) { @@ -172,6 +215,9 @@ extern void WebPInitAlphaProcessingMIPSdspR2(void); WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingMIPSdspR2(void) { WebPDispatchAlpha = DispatchAlpha_MIPSdspR2; WebPMultARGBRow = MultARGBRow_MIPSdspR2; +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_MIPSdspR2; +#endif WebPPackRGB = PackRGB_MIPSdspR2; } diff --git a/thirdparty/libwebp/src/dsp/common_sse2.h b/thirdparty/libwebp/src/dsp/common_sse2.h index 995d7cf4ea..e9f1ebff44 100644 --- a/thirdparty/libwebp/src/dsp/common_sse2.h +++ b/thirdparty/libwebp/src/dsp/common_sse2.h @@ -128,9 +128,9 @@ static WEBP_INLINE void VP8Transpose_2_4x4_16b( // Pack the planar buffers // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... -static WEBP_INLINE void VP8PlanarTo24b(__m128i* const in0, __m128i* const in1, - __m128i* const in2, __m128i* const in3, - __m128i* const in4, __m128i* const in5) { +static WEBP_INLINE void VP8PlanarTo24b_SSE2( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { // The input is 6 registers of sixteen 8b but for the sake of explanation, // let's take 6 registers of four 8b values. // To pack, we will keep taking one every two 8b integer and move it @@ -159,10 +159,10 @@ static WEBP_INLINE void VP8PlanarTo24b(__m128i* const in0, __m128i* const in1, // Convert four packed four-channel buffers like argbargbargbargb... into the // split channels aaaaa ... rrrr ... gggg .... bbbbb ...... -static WEBP_INLINE void VP8L32bToPlanar(__m128i* const in0, - __m128i* const in1, - __m128i* const in2, - __m128i* const in3) { +static WEBP_INLINE void VP8L32bToPlanar_SSE2(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { // Column-wise transpose. const __m128i A0 = _mm_unpacklo_epi8(*in0, *in1); const __m128i A1 = _mm_unpackhi_epi8(*in0, *in1); diff --git a/thirdparty/libwebp/src/dsp/common_sse41.h b/thirdparty/libwebp/src/dsp/common_sse41.h new file mode 100644 index 0000000000..2f173c024a --- /dev/null +++ b/thirdparty/libwebp/src/dsp/common_sse41.h @@ -0,0 +1,132 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE41_H_ +#define WEBP_DSP_COMMON_SSE41_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE41) +#include <smmintrin.h> + +//------------------------------------------------------------------------------ +// Channel mixing. +// Shuffles the input buffer as A0 0 0 A1 0 0 A2 ... +#define WEBP_SSE41_SHUFF(OUT, IN0, IN1) \ + OUT##0 = _mm_shuffle_epi8(*IN0, shuff0); \ + OUT##1 = _mm_shuffle_epi8(*IN0, shuff1); \ + OUT##2 = _mm_shuffle_epi8(*IN0, shuff2); \ + OUT##3 = _mm_shuffle_epi8(*IN1, shuff0); \ + OUT##4 = _mm_shuffle_epi8(*IN1, shuff1); \ + OUT##5 = _mm_shuffle_epi8(*IN1, shuff2); + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + __m128i R0, R1, R2, R3, R4, R5; + __m128i G0, G1, G2, G3, G4, G5; + __m128i B0, B1, B2, B3, B4, B5; + + // Process R. + { + const __m128i shuff0 = _mm_set_epi8( + 5, -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + -1, -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1); + WEBP_SSE41_SHUFF(R, in0, in1) + } + + // Process G. + { + // Same as before, just shifted to the left by one and including the right + // padding. + const __m128i shuff0 = _mm_set_epi8( + -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1); + const __m128i shuff1 = _mm_set_epi8( + 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5); + const __m128i shuff2 = _mm_set_epi8( + -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1); + WEBP_SSE41_SHUFF(G, in2, in3) + } + + // Process B. + { + const __m128i shuff0 = _mm_set_epi8( + -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1, -1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1, 10); + WEBP_SSE41_SHUFF(B, in4, in5) + } + + // OR the different channels. + { + const __m128i RG0 = _mm_or_si128(R0, G0); + const __m128i RG1 = _mm_or_si128(R1, G1); + const __m128i RG2 = _mm_or_si128(R2, G2); + const __m128i RG3 = _mm_or_si128(R3, G3); + const __m128i RG4 = _mm_or_si128(R4, G4); + const __m128i RG5 = _mm_or_si128(R5, G5); + *in0 = _mm_or_si128(RG0, B0); + *in1 = _mm_or_si128(RG1, B1); + *in2 = _mm_or_si128(RG2, B2); + *in3 = _mm_or_si128(RG3, B3); + *in4 = _mm_or_si128(RG4, B4); + *in5 = _mm_or_si128(RG5, B5); + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE41(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // aaaarrrrggggbbbb + const __m128i shuff0 = + _mm_set_epi8(15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0); + const __m128i A0 = _mm_shuffle_epi8(*in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(*in1, shuff0); + const __m128i A2 = _mm_shuffle_epi8(*in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(*in3, shuff0); + // A0A1R0R1 + // G0G1B0B1 + // A2A3R2R3 + // G0G1B0B1 + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_unpackhi_epi32(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_unpackhi_epi32(A2, A3); + *in3 = _mm_unpacklo_epi64(B0, B2); + *in2 = _mm_unpackhi_epi64(B0, B2); + *in1 = _mm_unpacklo_epi64(B1, B3); + *in0 = _mm_unpackhi_epi64(B1, B3); +} + +#endif // WEBP_USE_SSE41 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE41_H_ diff --git a/thirdparty/libwebp/src/dsp/cost.c b/thirdparty/libwebp/src/dsp/cost.c index a732389d58..634ccc2085 100644 --- a/thirdparty/libwebp/src/dsp/cost.c +++ b/thirdparty/libwebp/src/dsp/cost.c @@ -378,12 +378,7 @@ extern void VP8EncDspCostInitMIPS32(void); extern void VP8EncDspCostInitMIPSdspR2(void); extern void VP8EncDspCostInitSSE2(void); -static volatile VP8CPUInfo cost_last_cpuinfo_used = - (VP8CPUInfo)&cost_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInit(void) { - if (cost_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) { VP8GetResidualCost = GetResidualCost_C; VP8SetResidualCoeffs = SetResidualCoeffs_C; @@ -405,8 +400,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInit(void) { } #endif } - - cost_last_cpuinfo_used = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/dec.c b/thirdparty/libwebp/src/dsp/dec.c index 7e82407567..1119842dd3 100644 --- a/thirdparty/libwebp/src/dsp/dec.c +++ b/thirdparty/libwebp/src/dsp/dec.c @@ -741,12 +741,7 @@ extern void VP8DspInitMIPS32(void); extern void VP8DspInitMIPSdspR2(void); extern void VP8DspInitMSA(void); -static volatile VP8CPUInfo dec_last_cpuinfo_used = - (VP8CPUInfo)&dec_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8DspInit(void) { - if (dec_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8DspInit) { VP8InitClipTables(); #if !WEBP_NEON_OMIT_C_CODE @@ -889,6 +884,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8DspInit(void) { assert(VP8PredChroma8[5] != NULL); assert(VP8PredChroma8[6] != NULL); assert(VP8DitherCombine8x8 != NULL); - - dec_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/dsp.h b/thirdparty/libwebp/src/dsp/dsp.h index 99eefe092f..4ab77a5130 100644 --- a/thirdparty/libwebp/src/dsp/dsp.h +++ b/thirdparty/libwebp/src/dsp/dsp.h @@ -141,6 +141,42 @@ extern "C" { #endif #endif +#if defined(WEBP_USE_THREAD) && !defined(_WIN32) +#include <pthread.h> // NOLINT + +#define WEBP_DSP_INIT(func) do { \ + static volatile VP8CPUInfo func ## _last_cpuinfo_used = \ + (VP8CPUInfo)&func ## _last_cpuinfo_used; \ + static pthread_mutex_t func ## _lock = PTHREAD_MUTEX_INITIALIZER; \ + if (pthread_mutex_lock(&func ## _lock)) break; \ + if (func ## _last_cpuinfo_used != VP8GetCPUInfo) func(); \ + func ## _last_cpuinfo_used = VP8GetCPUInfo; \ + (void)pthread_mutex_unlock(&func ## _lock); \ +} while (0) +#else // !(defined(WEBP_USE_THREAD) && !defined(_WIN32)) +#define WEBP_DSP_INIT(func) do { \ + static volatile VP8CPUInfo func ## _last_cpuinfo_used = \ + (VP8CPUInfo)&func ## _last_cpuinfo_used; \ + if (func ## _last_cpuinfo_used == VP8GetCPUInfo) break; \ + func(); \ + func ## _last_cpuinfo_used = VP8GetCPUInfo; \ +} while (0) +#endif // defined(WEBP_USE_THREAD) && !defined(_WIN32) + +// Defines an Init + helper function that control multiple initialization of +// function pointers / tables. +/* Usage: + WEBP_DSP_INIT_FUNC(InitFunc) { + ...function body + } +*/ +#define WEBP_DSP_INIT_FUNC(name) \ + static WEBP_TSAN_IGNORE_FUNCTION void name ## _body(void); \ + WEBP_TSAN_IGNORE_FUNCTION void name(void) { \ + WEBP_DSP_INIT(name ## _body); \ + } \ + static WEBP_TSAN_IGNORE_FUNCTION void name ## _body(void) + #define WEBP_UBSAN_IGNORE_UNDEF #define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW #if defined(__clang__) && defined(__has_attribute) @@ -166,6 +202,13 @@ extern "C" { #define WEBP_SWAP_16BIT_CSP 0 #endif +// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) && \ + (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) +#define WORDS_BIGENDIAN +#endif + typedef enum { kSSE2, kSSE3, @@ -189,7 +232,7 @@ WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; // avoiding a compiler warning. #define WEBP_DSP_INIT_STUB(func) \ extern void func(void); \ - WEBP_TSAN_IGNORE_FUNCTION void func(void) {} + void func(void) {} //------------------------------------------------------------------------------ // Encoding @@ -578,6 +621,13 @@ void WebPMultRow_C(uint8_t* const ptr, const uint8_t* const alpha, int width, int inverse); void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse); +#ifdef WORDS_BIGENDIAN +// ARGB packing function: a/r/g/b input is rgba or bgra order. +extern void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out); +#endif + // RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order. extern void (*WebPPackRGB)(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out); diff --git a/thirdparty/libwebp/src/dsp/enc.c b/thirdparty/libwebp/src/dsp/enc.c index 1c807f1df7..fa23b40a30 100644 --- a/thirdparty/libwebp/src/dsp/enc.c +++ b/thirdparty/libwebp/src/dsp/enc.c @@ -740,12 +740,7 @@ extern void VP8EncDspInitMIPS32(void); extern void VP8EncDspInitMIPSdspR2(void); extern void VP8EncDspInitMSA(void); -static volatile VP8CPUInfo enc_last_cpuinfo_used = - (VP8CPUInfo)&enc_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInit(void) { - if (enc_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8EncDspInit) { VP8DspInit(); // common inverse transforms InitTables(); @@ -838,6 +833,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInit(void) { assert(VP8EncQuantizeBlockWHT != NULL); assert(VP8Copy4x4 != NULL); assert(VP8Copy16x8 != NULL); - - enc_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/filters.c b/thirdparty/libwebp/src/dsp/filters.c index ca5f877da7..069a22eaef 100644 --- a/thirdparty/libwebp/src/dsp/filters.c +++ b/thirdparty/libwebp/src/dsp/filters.c @@ -238,12 +238,7 @@ extern void VP8FiltersInitMSA(void); extern void VP8FiltersInitNEON(void); extern void VP8FiltersInitSSE2(void); -static volatile VP8CPUInfo filters_last_cpuinfo_used = - (VP8CPUInfo)&filters_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInit(void) { - if (filters_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8FiltersInit) { WebPUnfilters[WEBP_FILTER_NONE] = NULL; #if !WEBP_NEON_OMIT_C_CODE WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_C; @@ -289,6 +284,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInit(void) { assert(WebPFilters[WEBP_FILTER_HORIZONTAL] != NULL); assert(WebPFilters[WEBP_FILTER_VERTICAL] != NULL); assert(WebPFilters[WEBP_FILTER_GRADIENT] != NULL); - - filters_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/lossless.c b/thirdparty/libwebp/src/dsp/lossless.c index 83f553d9ad..f9b3c182d3 100644 --- a/thirdparty/libwebp/src/dsp/lossless.c +++ b/thirdparty/libwebp/src/dsp/lossless.c @@ -577,9 +577,6 @@ extern void VP8LDspInitNEON(void); extern void VP8LDspInitMIPSdspR2(void); extern void VP8LDspInitMSA(void); -static volatile VP8CPUInfo lossless_last_cpuinfo_used = - (VP8CPUInfo)&lossless_last_cpuinfo_used; - #define COPY_PREDICTOR_ARRAY(IN, OUT) do { \ (OUT)[0] = IN##0_C; \ (OUT)[1] = IN##1_C; \ @@ -599,9 +596,7 @@ static volatile VP8CPUInfo lossless_last_cpuinfo_used = (OUT)[15] = IN##0_C; \ } while (0); -WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInit(void) { - if (lossless_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8LDspInit) { COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors) COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors_C) COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd) @@ -658,8 +653,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInit(void) { assert(VP8LConvertBGRAToRGB565 != NULL); assert(VP8LMapColor32b != NULL); assert(VP8LMapColor8b != NULL); - - lossless_last_cpuinfo_used = VP8GetCPUInfo; } #undef COPY_PREDICTOR_ARRAY diff --git a/thirdparty/libwebp/src/dsp/lossless.h b/thirdparty/libwebp/src/dsp/lossless.h index a99dbda686..b2bbdfc93c 100644 --- a/thirdparty/libwebp/src/dsp/lossless.h +++ b/thirdparty/libwebp/src/dsp/lossless.h @@ -25,10 +25,6 @@ extern "C" { #endif -#ifdef WEBP_EXPERIMENTAL_FEATURES -#include "src/enc/delta_palettization_enc.h" -#endif // WEBP_EXPERIMENTAL_FEATURES - //------------------------------------------------------------------------------ // Decoding diff --git a/thirdparty/libwebp/src/dsp/lossless_enc.c b/thirdparty/libwebp/src/dsp/lossless_enc.c index 92ca3c0542..d608326fef 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc.c @@ -863,12 +863,7 @@ extern void VP8LEncDspInitMIPS32(void); extern void VP8LEncDspInitMIPSdspR2(void); extern void VP8LEncDspInitMSA(void); -static volatile VP8CPUInfo lossless_enc_last_cpuinfo_used = - (VP8CPUInfo)&lossless_enc_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInit(void) { - if (lossless_enc_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8LEncDspInit) { VP8LDspInit(); #if !WEBP_NEON_OMIT_C_CODE @@ -1011,8 +1006,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInit(void) { assert(VP8LPredictorsSub_C[13] != NULL); assert(VP8LPredictorsSub_C[14] != NULL); assert(VP8LPredictorsSub_C[15] != NULL); - - lossless_enc_last_cpuinfo_used = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c b/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c index 1eaf35ca8e..f84a9909e1 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c @@ -46,16 +46,14 @@ static void SubtractGreenFromBlueAndRed_SSE2(uint32_t* argb_data, //------------------------------------------------------------------------------ // Color Transform +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + static void TransformColor_SSE2(const VP8LMultipliers* const m, uint32_t* argb_data, int num_pixels) { - const __m128i mults_rb = _mm_set_epi16( - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_)); - const __m128i mults_b2 = _mm_set_epi16( - CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0, - CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0); + const __m128i mults_rb = MK_CST_16(CST_5b(m->green_to_red_), + CST_5b(m->green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST_5b(m->red_to_blue_), 0); const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks int i; @@ -85,12 +83,8 @@ static void CollectColorBlueTransforms_SSE2(const uint32_t* argb, int stride, int tile_width, int tile_height, int green_to_blue, int red_to_blue, int histo[]) { - const __m128i mults_r = _mm_set_epi16( - CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0, - CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0); - const __m128i mults_g = _mm_set_epi16( - 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue), - 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue)); + const __m128i mults_r = MK_CST_16(CST_5b(red_to_blue), 0); + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_blue)); const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask const __m128i mask_b = _mm_set1_epi32(0x0000ff); // blue mask int y; @@ -135,9 +129,7 @@ static void CollectColorBlueTransforms_SSE2(const uint32_t* argb, int stride, static void CollectColorRedTransforms_SSE2(const uint32_t* argb, int stride, int tile_width, int tile_height, int green_to_red, int histo[]) { - const __m128i mults_g = _mm_set_epi16( - 0, CST_5b(green_to_red), 0, CST_5b(green_to_red), - 0, CST_5b(green_to_red), 0, CST_5b(green_to_red)); + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_red)); const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask const __m128i mask = _mm_set1_epi32(0xff); @@ -174,6 +166,7 @@ static void CollectColorRedTransforms_SSE2(const uint32_t* argb, int stride, } } #undef SPAN +#undef MK_CST_16 //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c b/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c index 3526a342d3..2e12a712eb 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c @@ -18,6 +18,9 @@ #include <smmintrin.h> #include "src/dsp/lossless.h" +// For sign-extended multiplying constants, pre-shifted by 5: +#define CST_5b(X) (((int16_t)((uint16_t)(X) << 8)) >> 5) + //------------------------------------------------------------------------------ // Subtract-Green Transform @@ -39,12 +42,103 @@ static void SubtractGreenFromBlueAndRed_SSE41(uint32_t* argb_data, } //------------------------------------------------------------------------------ +// Color Transform + +#define SPAN 8 +static void CollectColorBlueTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const __m128i mults_r = _mm_set1_epi16(CST_5b(red_to_blue)); + const __m128i mults_g = _mm_set1_epi16(CST_5b(green_to_blue)); + const __m128i mask_g = _mm_set1_epi16(0xff00); // green mask + const __m128i mask_gb = _mm_set1_epi32(0xffff); // green/blue mask + const __m128i mask_b = _mm_set1_epi16(0x00ff); // blue mask + const __m128i shuffler_lo = _mm_setr_epi8(-1, 2, -1, 6, -1, 10, -1, 14, -1, + -1, -1, -1, -1, -1, -1, -1); + const __m128i shuffler_hi = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, -1, 6, -1, 10, -1, 14); + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i r0 = _mm_shuffle_epi8(in0, shuffler_lo); + const __m128i r1 = _mm_shuffle_epi8(in1, shuffler_hi); + const __m128i r = _mm_or_si128(r0, r1); // r 0 + const __m128i gb0 = _mm_and_si128(in0, mask_gb); + const __m128i gb1 = _mm_and_si128(in1, mask_gb); + const __m128i gb = _mm_packus_epi32(gb0, gb1); // g b + const __m128i g = _mm_and_si128(gb, mask_g); // g 0 + const __m128i A = _mm_mulhi_epi16(r, mults_r); // x dbr + const __m128i B = _mm_mulhi_epi16(g, mults_g); // x dbg + const __m128i C = _mm_sub_epi8(gb, B); // x b' + const __m128i D = _mm_sub_epi8(C, A); // x b'' + const __m128i E = _mm_and_si128(D, mask_b); // 0 b'' + _mm_storeu_si128((__m128i*)values, E); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorBlueTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_blue, red_to_blue, histo); + } + } +} + +static void CollectColorRedTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + const __m128i mults_g = _mm_set1_epi16(CST_5b(green_to_red)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask = _mm_set1_epi16(0xff); + + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i g0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i g1 = _mm_and_si128(in1, mask_g); + const __m128i g = _mm_packus_epi32(g0, g1); // g 0 + const __m128i A0 = _mm_srli_epi32(in0, 16); // 0 0 | x r + const __m128i A1 = _mm_srli_epi32(in1, 16); + const __m128i A = _mm_packus_epi32(A0, A1); // x r + const __m128i B = _mm_mulhi_epi16(g, mults_g); // x dr + const __m128i C = _mm_sub_epi8(A, B); // x r' + const __m128i D = _mm_and_si128(C, mask); // 0 r' + _mm_storeu_si128((__m128i*)values, D); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorRedTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, green_to_red, + histo); + } + } +} + +//------------------------------------------------------------------------------ // Entry point extern void VP8LEncDspInitSSE41(void); WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE41(void) { VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_SSE41; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms_SSE41; + VP8LCollectColorRedTransforms = CollectColorRedTransforms_SSE41; } #else // !WEBP_USE_SSE41 diff --git a/thirdparty/libwebp/src/dsp/lossless_sse2.c b/thirdparty/libwebp/src/dsp/lossless_sse2.c index 653b466cd6..17d7576419 100644 --- a/thirdparty/libwebp/src/dsp/lossless_sse2.c +++ b/thirdparty/libwebp/src/dsp/lossless_sse2.c @@ -453,14 +453,11 @@ static void TransformColorInverse_SSE2(const VP8LMultipliers* const m, int num_pixels, uint32_t* dst) { // sign-extended multiplying constants, pre-shifted by 5. #define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend - const __m128i mults_rb = _mm_set_epi16( - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_)); - const __m128i mults_b2 = _mm_set_epi16( - CST(red_to_blue_), 0, CST(red_to_blue_), 0, - CST(red_to_blue_), 0, CST(red_to_blue_), 0); +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + const __m128i mults_rb = MK_CST_16(CST(green_to_red_), CST(green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST(red_to_blue_), 0); +#undef MK_CST_16 #undef CST const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks int i; @@ -503,11 +500,11 @@ static void ConvertBGRAToRGB_SSE2(const uint32_t* src, int num_pixels, __m128i in5 = _mm_loadu_si128(in + 5); __m128i in6 = _mm_loadu_si128(in + 6); __m128i in7 = _mm_loadu_si128(in + 7); - VP8L32bToPlanar(&in0, &in1, &in2, &in3); - VP8L32bToPlanar(&in4, &in5, &in6, &in7); + VP8L32bToPlanar_SSE2(&in0, &in1, &in2, &in3); + VP8L32bToPlanar_SSE2(&in4, &in5, &in6, &in7); // At this points, in1/in5 contains red only, in2/in6 green only ... // Pack the colors in 24b RGB. - VP8PlanarTo24b(&in1, &in5, &in2, &in6, &in3, &in7); + VP8PlanarTo24b_SSE2(&in1, &in5, &in2, &in6, &in3, &in7); _mm_storeu_si128(out + 0, in1); _mm_storeu_si128(out + 1, in5); _mm_storeu_si128(out + 2, in2); diff --git a/thirdparty/libwebp/src/dsp/rescaler.c b/thirdparty/libwebp/src/dsp/rescaler.c index 4b6b7834e5..f307d35056 100644 --- a/thirdparty/libwebp/src/dsp/rescaler.c +++ b/thirdparty/libwebp/src/dsp/rescaler.c @@ -204,11 +204,7 @@ extern void WebPRescalerDspInitMIPSdspR2(void); extern void WebPRescalerDspInitMSA(void); extern void WebPRescalerDspInitNEON(void); -static volatile VP8CPUInfo rescaler_last_cpuinfo_used = - (VP8CPUInfo)&rescaler_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInit(void) { - if (rescaler_last_cpuinfo_used == VP8GetCPUInfo) return; +WEBP_DSP_INIT_FUNC(WebPRescalerDspInit) { #if !defined(WEBP_REDUCE_SIZE) #if !WEBP_NEON_OMIT_C_CODE WebPRescalerExportRowExpand = WebPRescalerExportRowExpand_C; @@ -253,5 +249,4 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInit(void) { assert(WebPRescalerImportRowExpand != NULL); assert(WebPRescalerImportRowShrink != NULL); #endif // WEBP_REDUCE_SIZE - rescaler_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/rescaler_sse2.c b/thirdparty/libwebp/src/dsp/rescaler_sse2.c index f93b204fe1..64c50deab5 100644 --- a/thirdparty/libwebp/src/dsp/rescaler_sse2.c +++ b/thirdparty/libwebp/src/dsp/rescaler_sse2.c @@ -36,7 +36,7 @@ static void LoadTwoPixels_SSE2(const uint8_t* const src, __m128i* out) { } // input: 8 bytes ABCDEFGH -> output: A0B0C0D0E0F0G0H0 -static void LoadHeightPixels_SSE2(const uint8_t* const src, __m128i* out) { +static void LoadEightPixels_SSE2(const uint8_t* const src, __m128i* out) { const __m128i zero = _mm_setzero_si128(); const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH *out = _mm_unpacklo_epi8(A, zero); @@ -50,13 +50,15 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, int accum = x_add; __m128i cur_pixels; + // SSE2 implementation only works with 16b signed arithmetic at max. + if (wrk->src_width < 8 || accum >= (1 << 15)) { + WebPRescalerImportRowExpand_C(wrk, src); + return; + } + assert(!WebPRescalerInputDone(wrk)); assert(wrk->x_expand); if (wrk->num_channels == 4) { - if (wrk->src_width < 2) { - WebPRescalerImportRowExpand_C(wrk, src); - return; - } LoadTwoPixels_SSE2(src, &cur_pixels); src += 4; while (1) { @@ -75,11 +77,7 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, } else { int left; const uint8_t* const src_limit = src + wrk->src_width - 8; - if (wrk->src_width < 8) { - WebPRescalerImportRowExpand_C(wrk, src); - return; - } - LoadHeightPixels_SSE2(src, &cur_pixels); + LoadEightPixels_SSE2(src, &cur_pixels); src += 7; left = 7; while (1) { @@ -94,7 +92,7 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, if (--left) { cur_pixels = _mm_srli_si128(cur_pixels, 2); } else if (src <= src_limit) { - LoadHeightPixels_SSE2(src, &cur_pixels); + LoadEightPixels_SSE2(src, &cur_pixels); src += 7; left = 7; } else { // tail diff --git a/thirdparty/libwebp/src/dsp/ssim.c b/thirdparty/libwebp/src/dsp/ssim.c index dc1b518a33..989ce8254c 100644 --- a/thirdparty/libwebp/src/dsp/ssim.c +++ b/thirdparty/libwebp/src/dsp/ssim.c @@ -139,12 +139,7 @@ VP8AccumulateSSEFunc VP8AccumulateSSE; extern void VP8SSIMDspInitSSE2(void); -static volatile VP8CPUInfo ssim_last_cpuinfo_used = - (VP8CPUInfo)&ssim_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInit(void) { - if (ssim_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8SSIMDspInit) { #if !defined(WEBP_REDUCE_SIZE) VP8SSIMGetClipped = SSIMGetClipped_C; VP8SSIMGet = SSIMGet_C; @@ -161,6 +156,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInit(void) { } #endif } - - ssim_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/upsampling.c b/thirdparty/libwebp/src/dsp/upsampling.c index e72626a82a..9b60da5bbb 100644 --- a/thirdparty/libwebp/src/dsp/upsampling.c +++ b/thirdparty/libwebp/src/dsp/upsampling.c @@ -217,13 +217,9 @@ WebPYUV444Converter WebPYUV444Converters[MODE_LAST]; extern void WebPInitYUV444ConvertersMIPSdspR2(void); extern void WebPInitYUV444ConvertersSSE2(void); +extern void WebPInitYUV444ConvertersSSE41(void); -static volatile VP8CPUInfo upsampling_last_cpuinfo_used1 = - (VP8CPUInfo)&upsampling_last_cpuinfo_used1; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444Converters(void) { - if (upsampling_last_cpuinfo_used1 == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitYUV444Converters) { WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgba_C; WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgra_C; WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgb_C; @@ -242,29 +238,29 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444Converters(void) { WebPInitYUV444ConvertersSSE2(); } #endif +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitYUV444ConvertersSSE41(); + } +#endif #if defined(WEBP_USE_MIPS_DSP_R2) if (VP8GetCPUInfo(kMIPSdspR2)) { WebPInitYUV444ConvertersMIPSdspR2(); } #endif } - upsampling_last_cpuinfo_used1 = VP8GetCPUInfo; } //------------------------------------------------------------------------------ // Main calls extern void WebPInitUpsamplersSSE2(void); +extern void WebPInitUpsamplersSSE41(void); extern void WebPInitUpsamplersNEON(void); extern void WebPInitUpsamplersMIPSdspR2(void); extern void WebPInitUpsamplersMSA(void); -static volatile VP8CPUInfo upsampling_last_cpuinfo_used2 = - (VP8CPUInfo)&upsampling_last_cpuinfo_used2; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { - if (upsampling_last_cpuinfo_used2 == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitUpsamplers) { #ifdef FANCY_UPSAMPLING #if !WEBP_NEON_OMIT_C_CODE WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_C; @@ -287,6 +283,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { WebPInitUpsamplersSSE2(); } #endif +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitUpsamplersSSE41(); + } +#endif #if defined(WEBP_USE_MIPS_DSP_R2) if (VP8GetCPUInfo(kMIPSdspR2)) { WebPInitUpsamplersMIPSdspR2(); @@ -310,6 +311,7 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { assert(WebPUpsamplers[MODE_BGRA] != NULL); assert(WebPUpsamplers[MODE_rgbA] != NULL); assert(WebPUpsamplers[MODE_bgrA] != NULL); +#if !defined(WEBP_REDUCE_CSP) || !WEBP_NEON_OMIT_C_CODE assert(WebPUpsamplers[MODE_RGB] != NULL); assert(WebPUpsamplers[MODE_BGR] != NULL); assert(WebPUpsamplers[MODE_ARGB] != NULL); @@ -317,9 +319,9 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { assert(WebPUpsamplers[MODE_RGB_565] != NULL); assert(WebPUpsamplers[MODE_Argb] != NULL); assert(WebPUpsamplers[MODE_rgbA_4444] != NULL); +#endif #endif // FANCY_UPSAMPLING - upsampling_last_cpuinfo_used2 = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/upsampling_msa.c b/thirdparty/libwebp/src/dsp/upsampling_msa.c index 535ffb772c..99eea70e7d 100644 --- a/thirdparty/libwebp/src/dsp/upsampling_msa.c +++ b/thirdparty/libwebp/src/dsp/upsampling_msa.c @@ -264,6 +264,7 @@ static void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { bgr[2] = Clip8(r1 >> 6); } +#if !defined(WEBP_REDUCE_CSP) static void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { const int y1 = MultHi(y, 19077); const int r1 = y1 + MultHi(v, 26149) - 14234; @@ -306,6 +307,7 @@ static void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, uint8_t* const argb) { argb[0] = 0xff; YuvToRgb(y, u, v, argb + 1); } +#endif // WEBP_REDUCE_CSP static void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, uint8_t* const bgra) { YuvToBgr(y, u, v, bgra); @@ -317,6 +319,7 @@ static void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, uint8_t* const rgba) { rgba[3] = 0xff; } +#if !defined(WEBP_REDUCE_CSP) static void YuvToRgbLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { v16u8 R, G, B; @@ -370,6 +373,7 @@ static void YuvToBgrLine(const uint8_t* y, const uint8_t* u, memcpy(dst, temp, length * 3 * sizeof(*dst)); } } +#endif // WEBP_REDUCE_CSP static void YuvToRgbaLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { @@ -427,6 +431,7 @@ static void YuvToBgraLine(const uint8_t* y, const uint8_t* u, } } +#if !defined(WEBP_REDUCE_CSP) static void YuvToArgbLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { v16u8 R, G, B; @@ -526,6 +531,7 @@ static void YuvToRgb565Line(const uint8_t* y, const uint8_t* u, memcpy(dst, temp, length * 2 * sizeof(*dst)); } } +#endif // WEBP_REDUCE_CSP #define UPSAMPLE_32PIXELS(a, b, c, d) do { \ v16u8 s = __msa_aver_u_b(a, d); \ diff --git a/thirdparty/libwebp/src/dsp/upsampling_sse2.c b/thirdparty/libwebp/src/dsp/upsampling_sse2.c index fd5d303982..340f1e2ac2 100644 --- a/thirdparty/libwebp/src/dsp/upsampling_sse2.c +++ b/thirdparty/libwebp/src/dsp/upsampling_sse2.c @@ -104,21 +104,6 @@ static void Upsample32Pixels_SSE2(const uint8_t r1[], const uint8_t r2[], Upsample32Pixels_SSE2(r1, r2, out); \ } -#define CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, \ - top_dst, bottom_dst, cur_x, num_pixels) { \ - int n; \ - for (n = 0; n < (num_pixels); ++n) { \ - FUNC((top_y)[(cur_x) + n], r_u[n], r_v[n], \ - (top_dst) + ((cur_x) + n) * (XSTEP)); \ - } \ - if ((bottom_y) != NULL) { \ - for (n = 0; n < (num_pixels); ++n) { \ - FUNC((bottom_y)[(cur_x) + n], r_u[64 + n], r_v[64 + n], \ - (bottom_dst) + ((cur_x) + n) * (XSTEP)); \ - } \ - } \ -} - #define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ top_dst, bottom_dst, cur_x) do { \ FUNC##32_SSE2((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ @@ -135,7 +120,7 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ int uv_pos, pos; \ /* 16byte-aligned array to cache reconstructed u and v */ \ - uint8_t uv_buf[4 * 32 + 15]; \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ uint8_t* const r_v = r_u + 32; \ \ @@ -160,11 +145,22 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ } \ if (len > 1) { \ const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ assert(left_over > 0); \ UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ - CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, \ - pos, len - pos); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ } \ } diff --git a/thirdparty/libwebp/src/dsp/upsampling_sse41.c b/thirdparty/libwebp/src/dsp/upsampling_sse41.c new file mode 100644 index 0000000000..648d456027 --- /dev/null +++ b/thirdparty/libwebp/src/dsp/upsampling_sse41.c @@ -0,0 +1,239 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include <assert.h> +#include <smmintrin.h> +#include <string.h> +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +#if !defined(WEBP_REDUCE_CSP) + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE41(const uint8_t r1[], const uint8_t r2[], + uint8_t* const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE41(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE41((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE41((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE4_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE4 variants of the fancy upsampler. +SSE4_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE41, VP8YuvToRgb, 3) +SSE4_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE41, VP8YuvToBgr, 3) + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE4_UPSAMPLE_FUNC + +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE41; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE41(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE41, VP8YuvToRgb32_SSE41, WebPYuv444ToRgb_C, 3); +YUV444_FUNC(Yuv444ToBgr_SSE41, VP8YuvToBgr32_SSE41, WebPYuv444ToBgr_C, 3); +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE41; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE41) + +#endif // WEBP_USE_SSE41 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE41)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE41) +#endif diff --git a/thirdparty/libwebp/src/dsp/yuv.c b/thirdparty/libwebp/src/dsp/yuv.c index bddf81fe09..14e67fc28e 100644 --- a/thirdparty/libwebp/src/dsp/yuv.c +++ b/thirdparty/libwebp/src/dsp/yuv.c @@ -71,15 +71,11 @@ void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, WebPSamplerRowFunc WebPSamplers[MODE_LAST]; extern void WebPInitSamplersSSE2(void); +extern void WebPInitSamplersSSE41(void); extern void WebPInitSamplersMIPS32(void); extern void WebPInitSamplersMIPSdspR2(void); -static volatile VP8CPUInfo yuv_last_cpuinfo_used = - (VP8CPUInfo)&yuv_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { - if (yuv_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitSamplers) { WebPSamplers[MODE_RGB] = YuvToRgbRow; WebPSamplers[MODE_RGBA] = YuvToRgbaRow; WebPSamplers[MODE_BGR] = YuvToBgrRow; @@ -99,6 +95,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { WebPInitSamplersSSE2(); } #endif // WEBP_USE_SSE2 +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitSamplersSSE41(); + } +#endif // WEBP_USE_SSE41 #if defined(WEBP_USE_MIPS32) if (VP8GetCPUInfo(kMIPS32)) { WebPInitSamplersMIPS32(); @@ -110,7 +111,6 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { } #endif // WEBP_USE_MIPS_DSP_R2 } - yuv_last_cpuinfo_used = VP8GetCPUInfo; } //----------------------------------------------------------------------------- @@ -254,17 +254,13 @@ void (*WebPSharpYUVUpdateRGB)(const int16_t* ref, const int16_t* src, void (*WebPSharpYUVFilterRow)(const int16_t* A, const int16_t* B, int len, const uint16_t* best_y, uint16_t* out); -static volatile VP8CPUInfo rgba_to_yuv_last_cpuinfo_used = - (VP8CPUInfo)&rgba_to_yuv_last_cpuinfo_used; - extern void WebPInitConvertARGBToYUVSSE2(void); +extern void WebPInitConvertARGBToYUVSSE41(void); extern void WebPInitConvertARGBToYUVNEON(void); extern void WebPInitSharpYUVSSE2(void); extern void WebPInitSharpYUVNEON(void); -WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { - if (rgba_to_yuv_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitConvertARGBToYUV) { WebPConvertARGBToY = ConvertARGBToY_C; WebPConvertARGBToUV = WebPConvertARGBToUV_C; @@ -286,6 +282,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { WebPInitSharpYUVSSE2(); } #endif // WEBP_USE_SSE2 +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitConvertARGBToYUVSSE41(); + } +#endif // WEBP_USE_SSE41 } #if defined(WEBP_USE_NEON) @@ -304,6 +305,4 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { assert(WebPSharpYUVUpdateY != NULL); assert(WebPSharpYUVUpdateRGB != NULL); assert(WebPSharpYUVFilterRow != NULL); - - rgba_to_yuv_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/yuv.h b/thirdparty/libwebp/src/dsp/yuv.h index c8a55832d4..eb787270d2 100644 --- a/thirdparty/libwebp/src/dsp/yuv.h +++ b/thirdparty/libwebp/src/dsp/yuv.h @@ -166,6 +166,19 @@ void VP8YuvToRgb56532_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, #endif // WEBP_USE_SSE2 +//----------------------------------------------------------------------------- +// SSE41 extra functions (mostly for upsampling_sse41.c) + +#if defined(WEBP_USE_SSE41) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); + +#endif // WEBP_USE_SSE41 + //------------------------------------------------------------------------------ // RGB -> YUV conversion diff --git a/thirdparty/libwebp/src/dsp/yuv_sse2.c b/thirdparty/libwebp/src/dsp/yuv_sse2.c index 6810bf8d15..baa48d5371 100644 --- a/thirdparty/libwebp/src/dsp/yuv_sse2.c +++ b/thirdparty/libwebp/src/dsp/yuv_sse2.c @@ -180,7 +180,7 @@ static WEBP_INLINE void PlanarTo24b_SSE2(__m128i* const in0, __m128i* const in1, // Repeat the same permutations twice more: // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 - VP8PlanarTo24b(in0, in1, in2, in3, in4, in5); + VP8PlanarTo24b_SSE2(in0, in1, in2, in3, in4, in5); _mm_storeu_si128((__m128i*)(rgb + 0), *in0); _mm_storeu_si128((__m128i*)(rgb + 16), *in1); @@ -492,7 +492,7 @@ static WEBP_INLINE void RGB32PackedToPlanar_SSE2(const uint32_t* const argb, __m128i a1 = LOAD_16(argb + 4); __m128i a2 = LOAD_16(argb + 8); __m128i a3 = LOAD_16(argb + 12); - VP8L32bToPlanar(&a0, &a1, &a2, &a3); + VP8L32bToPlanar_SSE2(&a0, &a1, &a2, &a3); rgb[0] = _mm_unpacklo_epi8(a1, zero); rgb[1] = _mm_unpackhi_epi8(a1, zero); rgb[2] = _mm_unpacklo_epi8(a2, zero); diff --git a/thirdparty/libwebp/src/dsp/yuv_sse41.c b/thirdparty/libwebp/src/dsp/yuv_sse41.c new file mode 100644 index 0000000000..579d1f7402 --- /dev/null +++ b/thirdparty/libwebp/src/dsp/yuv_sse41.c @@ -0,0 +1,613 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE41) + +#include "src/dsp/common_sse41.h" +#include <stdlib.h> +#include <smmintrin.h> + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE41(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(*(const uint32_t*)src); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_HI_16_SSE41(u), + V0 = Load_HI_16_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_UV_HI_8_SSE41(u), + V0 = Load_UV_HI_8_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5, + uint8_t* const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE41(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE41(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE41; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE41; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +#define WEBP_SSE41_SHUFF(OUT) do { \ + const __m128i tmp0 = _mm_shuffle_epi8(A0, shuff0); \ + const __m128i tmp1 = _mm_shuffle_epi8(A1, shuff1); \ + const __m128i tmp2 = _mm_shuffle_epi8(A2, shuff2); \ + const __m128i tmp3 = _mm_shuffle_epi8(A3, shuff0); \ + const __m128i tmp4 = _mm_shuffle_epi8(A4, shuff1); \ + const __m128i tmp5 = _mm_shuffle_epi8(A5, shuff2); \ + \ + /* OR everything to get one channel */ \ + const __m128i tmp6 = _mm_or_si128(tmp0, tmp1); \ + const __m128i tmp7 = _mm_or_si128(tmp3, tmp4); \ + out[OUT + 0] = _mm_or_si128(tmp6, tmp2); \ + out[OUT + 1] = _mm_or_si128(tmp7, tmp5); \ +} while (0); + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE41( + const uint8_t* const rgb, __m128i* const out /*out[6]*/) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(rgb + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(rgb + 16)); + const __m128i A2 = _mm_loadu_si128((const __m128i*)(rgb + 32)); + const __m128i A3 = _mm_loadu_si128((const __m128i*)(rgb + 48)); + const __m128i A4 = _mm_loadu_si128((const __m128i*)(rgb + 64)); + const __m128i A5 = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + // Compute RR. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 13, 10, 7, 4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(0) + } + // Compute GG. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(2) + } + // Compute BB. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 14, 11, 8, 5, 2); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(4) + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE41( + const uint32_t* const argb, __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE41(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE41(const uint8_t* rgb, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE41(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE41(const uint32_t* argb, uint8_t* y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + ConvertRGBToY_SSE41(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE41(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE41(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE41(const uint32_t* argb, + uint8_t* u, uint8_t* v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE41(&argb[i + 16], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE41( + const uint16_t* const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // aarrggbb as 16-bit. + const __m128i shuff0 = + _mm_set_epi8(-1, -1, -1, -1, 13, 12, 5, 4, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i shuff1 = + _mm_set_epi8(13, 12, 5, 4, -1, -1, -1, -1, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i A0 = _mm_shuffle_epi8(in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(in1, shuff1); + const __m128i A2 = _mm_shuffle_epi8(in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(in3, shuff1); + // R0R1G0G1 + // B0B1**** + // R2R3G2G3 + // B2B3**** + // (OR is used to free port 5 for the unpack) + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_or_si128(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_or_si128(A2, A3); + // Gather the channels. + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpackhi_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE41(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE41(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE41(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE41(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE41; + WebPConvertARGBToUV = ConvertARGBToUV_SSE41; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE41; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE41; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE41; +} + +//------------------------------------------------------------------------------ + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE41) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/thirdparty/libwebp/src/enc/alpha_enc.c b/thirdparty/libwebp/src/enc/alpha_enc.c index 7e8d87f22e..dce9ca957d 100644 --- a/thirdparty/libwebp/src/enc/alpha_enc.c +++ b/thirdparty/libwebp/src/enc/alpha_enc.c @@ -361,7 +361,8 @@ static int EncodeAlpha(VP8Encoder* const enc, //------------------------------------------------------------------------------ // Main calls -static int CompressAlphaJob(VP8Encoder* const enc, void* dummy) { +static int CompressAlphaJob(void* arg1, void* dummy) { + VP8Encoder* const enc = (VP8Encoder*)arg1; const WebPConfig* config = enc->config_; uint8_t* alpha_data = NULL; size_t alpha_size = 0; @@ -394,7 +395,7 @@ void VP8EncInitAlpha(VP8Encoder* const enc) { WebPGetWorkerInterface()->Init(worker); worker->data1 = enc; worker->data2 = NULL; - worker->hook = (WebPWorkerHook)CompressAlphaJob; + worker->hook = CompressAlphaJob; } } diff --git a/thirdparty/libwebp/src/enc/analysis_enc.c b/thirdparty/libwebp/src/enc/analysis_enc.c index 08f471f5f8..a47ff7d4e8 100644 --- a/thirdparty/libwebp/src/enc/analysis_enc.c +++ b/thirdparty/libwebp/src/enc/analysis_enc.c @@ -434,7 +434,9 @@ typedef struct { } SegmentJob; // main work call -static int DoSegmentsJob(SegmentJob* const job, VP8EncIterator* const it) { +static int DoSegmentsJob(void* arg1, void* arg2) { + SegmentJob* const job = (SegmentJob*)arg1; + VP8EncIterator* const it = (VP8EncIterator*)arg2; int ok = 1; if (!VP8IteratorIsDone(it)) { uint8_t tmp[32 + WEBP_ALIGN_CST]; @@ -462,7 +464,7 @@ static void InitSegmentJob(VP8Encoder* const enc, SegmentJob* const job, WebPGetWorkerInterface()->Init(&job->worker); job->worker.data1 = job; job->worker.data2 = &job->it; - job->worker.hook = (WebPWorkerHook)DoSegmentsJob; + job->worker.hook = DoSegmentsJob; VP8IteratorInit(enc, &job->it); VP8IteratorSetRow(&job->it, start_row); VP8IteratorSetCountDown(&job->it, (end_row - start_row) * enc->mb_w_); diff --git a/thirdparty/libwebp/src/enc/frame_enc.c b/thirdparty/libwebp/src/enc/frame_enc.c index 2b0dc66410..1aec376e44 100644 --- a/thirdparty/libwebp/src/enc/frame_enc.c +++ b/thirdparty/libwebp/src/enc/frame_enc.c @@ -198,7 +198,7 @@ static void SetSegmentProbas(VP8Encoder* const enc) { for (n = 0; n < enc->mb_w_ * enc->mb_h_; ++n) { const VP8MBInfo* const mb = &enc->mb_info_[n]; - p[mb->segment_]++; + ++p[mb->segment_]; } #if !defined(WEBP_DISABLE_STATS) if (enc->pic_->stats != NULL) { @@ -520,6 +520,14 @@ static void StoreSideInfo(const VP8EncIterator* const it) { #endif } +static void ResetSideInfo(const VP8EncIterator* const it) { + VP8Encoder* const enc = it->enc_; + WebPPicture* const pic = enc->pic_; + if (pic->stats != NULL) { + memset(enc->block_count_, 0, sizeof(enc->block_count_)); + } + ResetSSE(enc); +} #else // defined(WEBP_DISABLE_STATS) static void ResetSSE(VP8Encoder* const enc) { (void)enc; @@ -528,10 +536,16 @@ static void StoreSideInfo(const VP8EncIterator* const it) { VP8Encoder* const enc = it->enc_; WebPPicture* const pic = enc->pic_; if (pic->extra_info != NULL) { - memset(pic->extra_info, 0, - enc->mb_w_ * enc->mb_h_ * sizeof(*pic->extra_info)); + if (it->x_ == 0 && it->y_ == 0) { // only do it once, at start + memset(pic->extra_info, 0, + enc->mb_w_ * enc->mb_h_ * sizeof(*pic->extra_info)); + } } } + +static void ResetSideInfo(const VP8EncIterator* const it) { + (void)it; +} #endif // !defined(WEBP_DISABLE_STATS) static double GetPSNR(uint64_t mse, uint64_t size) { @@ -570,7 +584,7 @@ static uint64_t OneStatPass(VP8Encoder* const enc, VP8RDLevel rd_opt, VP8IteratorImport(&it, NULL); if (VP8Decimate(&it, &info, rd_opt)) { // Just record the number of skips and act like skip_proba is not used. - enc->proba_.nb_skip_++; + ++enc->proba_.nb_skip_; } RecordResiduals(&it, &info); size += info.R + info.H; @@ -841,6 +855,9 @@ int VP8EncTokenLoop(VP8Encoder* const enc) { if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) { ++num_pass_left; enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation... + if (is_last_pass) { + ResetSideInfo(&it); + } continue; // ...and start over } if (is_last_pass) { @@ -871,4 +888,3 @@ int VP8EncTokenLoop(VP8Encoder* const enc) { #endif // DISABLE_TOKEN_BUFFER //------------------------------------------------------------------------------ - diff --git a/thirdparty/libwebp/src/enc/histogram_enc.c b/thirdparty/libwebp/src/enc/histogram_enc.c index 056a972dda..9fdbc627a1 100644 --- a/thirdparty/libwebp/src/enc/histogram_enc.c +++ b/thirdparty/libwebp/src/enc/histogram_enc.c @@ -200,14 +200,9 @@ static WEBP_INLINE double BitsEntropyRefine(const VP8LBitEntropy* entropy) { } } -double VP8LBitsEntropy(const uint32_t* const array, int n, - uint32_t* const trivial_symbol) { +double VP8LBitsEntropy(const uint32_t* const array, int n) { VP8LBitEntropy entropy; VP8LBitsEntropyUnrefined(array, n, &entropy); - if (trivial_symbol != NULL) { - *trivial_symbol = - (entropy.nonzeros == 1) ? entropy.nonzero_code : VP8L_NON_TRIVIAL_SYM; - } return BitsEntropyRefine(&entropy); } @@ -605,7 +600,7 @@ static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo, } // Implement a Lehmer random number generator with a multiplicative constant of -// 48271 and a modulo constant of 2^31 − 1. +// 48271 and a modulo constant of 2^31 - 1. static uint32_t MyRand(uint32_t* const seed) { *seed = (uint32_t)(((uint64_t)(*seed) * 48271u) % 2147483647u); assert(*seed > 0); @@ -1031,7 +1026,7 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize, } } - // TODO(vikasa): Optimize HistogramRemap for low-effort compression mode also. + // TODO(vrabaud): Optimize HistogramRemap for low-effort compression mode. // Find the optimal map from original histograms to the final ones. HistogramRemap(orig_histo, image_histo, histogram_symbols); diff --git a/thirdparty/libwebp/src/enc/histogram_enc.h b/thirdparty/libwebp/src/enc/histogram_enc.h index 15b1fbda34..e8c4c83f6f 100644 --- a/thirdparty/libwebp/src/enc/histogram_enc.h +++ b/thirdparty/libwebp/src/enc/histogram_enc.h @@ -109,10 +109,7 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize, uint16_t* const histogram_symbols); // Returns the entropy for the symbols in the input array. -// Also sets trivial_symbol to the code value, if the array has only one code -// value. Otherwise, set it to VP8L_NON_TRIVIAL_SYM. -double VP8LBitsEntropy(const uint32_t* const array, int n, - uint32_t* const trivial_symbol); +double VP8LBitsEntropy(const uint32_t* const array, int n); // Estimate how many bits the combined entropy of literals and distance // approximately maps to. diff --git a/thirdparty/libwebp/src/enc/iterator_enc.c b/thirdparty/libwebp/src/enc/iterator_enc.c index cfacfd2401..7c47d51272 100644 --- a/thirdparty/libwebp/src/enc/iterator_enc.c +++ b/thirdparty/libwebp/src/enc/iterator_enc.c @@ -26,6 +26,9 @@ static void InitLeft(VP8EncIterator* const it) { memset(it->u_left_, 129, 8); memset(it->v_left_, 129, 8); it->left_nz_[8] = 0; + if (it->top_derr_ != NULL) { + memset(&it->left_derr_, 0, sizeof(it->left_derr_)); + } } static void InitTop(VP8EncIterator* const it) { @@ -33,6 +36,9 @@ static void InitTop(VP8EncIterator* const it) { const size_t top_size = enc->mb_w_ * 16; memset(enc->y_top_, 127, 2 * top_size); memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_)); + if (enc->top_derr_ != NULL) { + memset(enc->top_derr_, 0, enc->mb_w_ * sizeof(*enc->top_derr_)); + } } void VP8IteratorSetRow(VP8EncIterator* const it, int y) { @@ -76,6 +82,7 @@ void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it) { it->y_left_ = (uint8_t*)WEBP_ALIGN(it->yuv_left_mem_ + 1); it->u_left_ = it->y_left_ + 16 + 16; it->v_left_ = it->u_left_ + 16; + it->top_derr_ = enc->top_derr_; VP8IteratorReset(it); } @@ -450,4 +457,3 @@ int VP8IteratorRotateI4(VP8EncIterator* const it, } //------------------------------------------------------------------------------ - diff --git a/thirdparty/libwebp/src/enc/near_lossless_enc.c b/thirdparty/libwebp/src/enc/near_lossless_enc.c index cadd14c664..5517a7e271 100644 --- a/thirdparty/libwebp/src/enc/near_lossless_enc.c +++ b/thirdparty/libwebp/src/enc/near_lossless_enc.c @@ -146,6 +146,6 @@ int VP8ApplyNearLossless(const WebPPicture* const picture, int quality, // Define a stub to suppress compiler warnings. extern void VP8LNearLosslessStub(void); -WEBP_TSAN_IGNORE_FUNCTION void VP8LNearLosslessStub(void) {} +void VP8LNearLosslessStub(void) {} #endif // (WEBP_NEAR_LOSSLESS == 1) diff --git a/thirdparty/libwebp/src/enc/picture_csp_enc.c b/thirdparty/libwebp/src/enc/picture_csp_enc.c index d531dd0282..02d9df76d5 100644 --- a/thirdparty/libwebp/src/enc/picture_csp_enc.c +++ b/thirdparty/libwebp/src/enc/picture_csp_enc.c @@ -28,11 +28,11 @@ // If defined, use table to compute x / alpha. #define USE_INVERSE_ALPHA_TABLE -static const union { - uint32_t argb; - uint8_t bytes[4]; -} test_endian = { 0xff000000u }; -#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff) +#ifdef WORDS_BIGENDIAN +#define ALPHA_OFFSET 0 // uint32_t 0xff000000 is 0xff,00,00,00 in memory +#else +#define ALPHA_OFFSET 3 // uint32_t 0xff000000 is 0x00,00,00,ff in memory +#endif //------------------------------------------------------------------------------ // Detection of non-trivial transparency @@ -61,7 +61,7 @@ int WebPPictureHasTransparency(const WebPPicture* picture) { return CheckNonOpaque(picture->a, picture->width, picture->height, 1, picture->a_stride); } else { - const int alpha_offset = ALPHA_IS_LAST ? 3 : 0; + const int alpha_offset = ALPHA_OFFSET; return CheckNonOpaque((const uint8_t*)picture->argb + alpha_offset, picture->width, picture->height, 4, picture->argb_stride * sizeof(*picture->argb)); @@ -126,7 +126,7 @@ static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { #else -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTables(void) {} +static void InitGammaTables(void) {} static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) { return v; } static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { return (int)(base_value << shift); @@ -170,29 +170,33 @@ typedef uint16_t fixed_y_t; // unsigned type with extra SFIX precision for W #if defined(USE_GAMMA_COMPRESSION) -// float variant of gamma-correction // We use tables of different size and precision for the Rec709 / BT2020 // transfer function. #define kGammaF (1./0.45) -static float kGammaToLinearTabF[MAX_Y_T + 1]; // size scales with Y_FIX -static float kLinearToGammaTabF[kGammaTabSize + 2]; -static volatile int kGammaTablesFOk = 0; - -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { - if (!kGammaTablesFOk) { +static uint32_t kLinearToGammaTabS[kGammaTabSize + 2]; +#define GAMMA_TO_LINEAR_BITS 14 +static uint32_t kGammaToLinearTabS[MAX_Y_T + 1]; // size scales with Y_FIX +static volatile int kGammaTablesSOk = 0; + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesS(void) { + assert(2 * GAMMA_TO_LINEAR_BITS < 32); // we use uint32_t intermediate values + if (!kGammaTablesSOk) { int v; const double norm = 1. / MAX_Y_T; const double scale = 1. / kGammaTabSize; const double a = 0.09929682680944; const double thresh = 0.018053968510807; + const double final_scale = 1 << GAMMA_TO_LINEAR_BITS; for (v = 0; v <= MAX_Y_T; ++v) { const double g = norm * v; + double value; if (g <= thresh * 4.5) { - kGammaToLinearTabF[v] = (float)(g / 4.5); + value = g / 4.5; } else { const double a_rec = 1. / (1. + a); - kGammaToLinearTabF[v] = (float)pow(a_rec * (g + a), kGammaF); + value = pow(a_rec * (g + a), kGammaF); } + kGammaToLinearTabS[v] = (uint32_t)(value * final_scale + .5); } for (v = 0; v <= kGammaTabSize; ++v) { const double g = scale * v; @@ -202,37 +206,44 @@ static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { } else { value = (1. + a) * pow(g, 1. / kGammaF) - a; } - kLinearToGammaTabF[v] = (float)(MAX_Y_T * value); + // we already incorporate the 1/2 rounding constant here + kLinearToGammaTabS[v] = + (uint32_t)(MAX_Y_T * value) + (1 << GAMMA_TO_LINEAR_BITS >> 1); } // to prevent small rounding errors to cause read-overflow: - kLinearToGammaTabF[kGammaTabSize + 1] = kLinearToGammaTabF[kGammaTabSize]; - kGammaTablesFOk = 1; + kLinearToGammaTabS[kGammaTabSize + 1] = kLinearToGammaTabS[kGammaTabSize]; + kGammaTablesSOk = 1; } } -static WEBP_INLINE float GammaToLinearF(int v) { - return kGammaToLinearTabF[v]; +// return value has a fixed-point precision of GAMMA_TO_LINEAR_BITS +static WEBP_INLINE uint32_t GammaToLinearS(int v) { + return kGammaToLinearTabS[v]; } -static WEBP_INLINE int LinearToGammaF(float value) { - const float v = value * kGammaTabSize; - const int tab_pos = (int)v; - const float x = v - (float)tab_pos; // fractional part - const float v0 = kLinearToGammaTabF[tab_pos + 0]; - const float v1 = kLinearToGammaTabF[tab_pos + 1]; - const float y = v1 * x + v0 * (1.f - x); // interpolate - return (int)(y + .5); +static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) { + // 'value' is in GAMMA_TO_LINEAR_BITS fractional precision + const uint32_t v = value * kGammaTabSize; + const uint32_t tab_pos = v >> GAMMA_TO_LINEAR_BITS; + // fractional part, in GAMMA_TO_LINEAR_BITS fixed-point precision + const uint32_t x = v - (tab_pos << GAMMA_TO_LINEAR_BITS); // fractional part + // v0 / v1 are in GAMMA_TO_LINEAR_BITS fixed-point precision (range [0..1]) + const uint32_t v0 = kLinearToGammaTabS[tab_pos + 0]; + const uint32_t v1 = kLinearToGammaTabS[tab_pos + 1]; + // Final interpolation. Note that rounding is already included. + const uint32_t v2 = (v1 - v0) * x; // note: v1 >= v0. + const uint32_t result = v0 + (v2 >> GAMMA_TO_LINEAR_BITS); + return result; } #else -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) {} -static WEBP_INLINE float GammaToLinearF(int v) { - const float norm = 1.f / MAX_Y_T; - return norm * v; +static void InitGammaTablesS(void) {} +static WEBP_INLINE uint32_t GammaToLinearS(int v) { + return (v << GAMMA_TO_LINEAR_BITS) / MAX_Y_T; } -static WEBP_INLINE int LinearToGammaF(float value) { - return (int)(MAX_Y_T * value + .5); +static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) { + return (MAX_Y_T * value) >> GAMMA_TO_LINEAR_BITS; } #endif // USE_GAMMA_COMPRESSION @@ -254,26 +265,22 @@ static int RGBToGray(int r, int g, int b) { return (luma >> YUV_FIX); } -static float RGBToGrayF(float r, float g, float b) { - return (float)(0.2126 * r + 0.7152 * g + 0.0722 * b); -} - -static int ScaleDown(int a, int b, int c, int d) { - const float A = GammaToLinearF(a); - const float B = GammaToLinearF(b); - const float C = GammaToLinearF(c); - const float D = GammaToLinearF(d); - return LinearToGammaF(0.25f * (A + B + C + D)); +static uint32_t ScaleDown(int a, int b, int c, int d) { + const uint32_t A = GammaToLinearS(a); + const uint32_t B = GammaToLinearS(b); + const uint32_t C = GammaToLinearS(c); + const uint32_t D = GammaToLinearS(d); + return LinearToGammaS((A + B + C + D + 2) >> 2); } static WEBP_INLINE void UpdateW(const fixed_y_t* src, fixed_y_t* dst, int w) { int i; for (i = 0; i < w; ++i) { - const float R = GammaToLinearF(src[0 * w + i]); - const float G = GammaToLinearF(src[1 * w + i]); - const float B = GammaToLinearF(src[2 * w + i]); - const float Y = RGBToGrayF(R, G, B); - dst[i] = (fixed_y_t)LinearToGammaF(Y); + const uint32_t R = GammaToLinearS(src[0 * w + i]); + const uint32_t G = GammaToLinearS(src[1 * w + i]); + const uint32_t B = GammaToLinearS(src[2 * w + i]); + const uint32_t Y = RGBToGray(R, G, B); + dst[i] = (fixed_y_t)LinearToGammaS(Y); } } @@ -863,7 +870,7 @@ static int ImportYUVAFromRGBA(const uint8_t* r_ptr, } if (use_iterative_conversion) { - InitGammaTablesF(); + InitGammaTablesS(); if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) { return 0; } @@ -990,10 +997,10 @@ static int PictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace, return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); } else { const uint8_t* const argb = (const uint8_t*)picture->argb; - const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1; - const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2; - const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3; - const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0; + const uint8_t* const a = argb + (0 ^ ALPHA_OFFSET); + const uint8_t* const r = argb + (1 ^ ALPHA_OFFSET); + const uint8_t* const g = argb + (2 ^ ALPHA_OFFSET); + const uint8_t* const b = argb + (3 ^ ALPHA_OFFSET); picture->colorspace = WEBP_YUV420; return ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, @@ -1044,7 +1051,8 @@ int WebPPictureYUVAToARGB(WebPPicture* picture) { const int argb_stride = 4 * picture->argb_stride; uint8_t* dst = (uint8_t*)picture->argb; const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y; - WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST); + WebPUpsampleLinePairFunc upsample = + WebPGetLinePairConverter(ALPHA_OFFSET > 0); // First row, with replicated top samples. upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width); @@ -1087,6 +1095,7 @@ static int Import(WebPPicture* const picture, const uint8_t* rgb, int rgb_stride, int step, int swap_rb, int import_alpha) { int y; + // swap_rb -> b,g,r,a , !swap_rb -> r,g,b,a const uint8_t* r_ptr = rgb + (swap_rb ? 2 : 0); const uint8_t* g_ptr = rgb + 1; const uint8_t* b_ptr = rgb + (swap_rb ? 0 : 2); @@ -1104,19 +1113,32 @@ static int Import(WebPPicture* const picture, WebPInitAlphaProcessing(); if (import_alpha) { + // dst[] byte order is {a,r,g,b} for big-endian, {b,g,r,a} for little endian uint32_t* dst = picture->argb; - const int do_copy = - (!swap_rb && !ALPHA_IS_LAST) || (swap_rb && ALPHA_IS_LAST); + const int do_copy = (ALPHA_OFFSET == 3) && swap_rb; assert(step == 4); - for (y = 0; y < height; ++y) { - if (do_copy) { + if (do_copy) { + for (y = 0; y < height; ++y) { memcpy(dst, rgb, width * 4); - } else { + rgb += rgb_stride; + dst += picture->argb_stride; + } + } else { + for (y = 0; y < height; ++y) { +#ifdef WORDS_BIGENDIAN + // BGRA or RGBA input order. + const uint8_t* a_ptr = rgb + 3; + WebPPackARGB(a_ptr, r_ptr, g_ptr, b_ptr, width, dst); + r_ptr += rgb_stride; + g_ptr += rgb_stride; + b_ptr += rgb_stride; +#else // RGBA input order. Need to swap R and B. VP8LConvertBGRAToRGBA((const uint32_t*)rgb, width, (uint8_t*)dst); +#endif + rgb += rgb_stride; + dst += picture->argb_stride; } - rgb += rgb_stride; - dst += picture->argb_stride; } } else { uint32_t* dst = picture->argb; diff --git a/thirdparty/libwebp/src/enc/picture_psnr_enc.c b/thirdparty/libwebp/src/enc/picture_psnr_enc.c index 362a7c79be..1a2f0bef3e 100644 --- a/thirdparty/libwebp/src/enc/picture_psnr_enc.c +++ b/thirdparty/libwebp/src/enc/picture_psnr_enc.c @@ -18,6 +18,7 @@ #include <math.h> #include <stdlib.h> +#include "src/dsp/dsp.h" #include "src/enc/vp8i_enc.h" #include "src/utils/utils.h" @@ -169,6 +170,12 @@ int WebPPlaneDistortion(const uint8_t* src, size_t src_stride, return 1; } +#ifdef WORDS_BIGENDIAN +#define BLUE_OFFSET 3 // uint32_t 0x000000ff is 0x00,00,00,ff in memory +#else +#define BLUE_OFFSET 0 // uint32_t 0x000000ff is 0xff,00,00,00 in memory +#endif + int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, int type, float results[5]) { int w, h, c; @@ -195,8 +202,10 @@ int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, float distortion; const size_t stride0 = 4 * (size_t)p0.argb_stride; const size_t stride1 = 4 * (size_t)p1.argb_stride; - if (!WebPPlaneDistortion((const uint8_t*)p0.argb + c, stride0, - (const uint8_t*)p1.argb + c, stride1, + // results are reported as BGRA + const int offset = c ^ BLUE_OFFSET; + if (!WebPPlaneDistortion((const uint8_t*)p0.argb + offset, stride0, + (const uint8_t*)p1.argb + offset, stride1, w, h, 4, type, &distortion, results + c)) { goto Error; } @@ -214,6 +223,8 @@ int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, return ok; } +#undef BLUE_OFFSET + #else // defined(WEBP_DISABLE_STATS) int WebPPlaneDistortion(const uint8_t* src, size_t src_stride, const uint8_t* ref, size_t ref_stride, diff --git a/thirdparty/libwebp/src/enc/quant_enc.c b/thirdparty/libwebp/src/enc/quant_enc.c index 3b1a3129b5..35bfaf21ef 100644 --- a/thirdparty/libwebp/src/enc/quant_enc.c +++ b/thirdparty/libwebp/src/enc/quant_enc.c @@ -826,6 +826,85 @@ static int ReconstructIntra4(VP8EncIterator* const it, return nz; } +//------------------------------------------------------------------------------ +// DC-error diffusion + +// Diffusion weights. We under-correct a bit (15/16th of the error is actually +// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. +#define C1 7 // fraction of error sent to the 4x4 block below +#define C2 8 // fraction of error sent to the 4x4 block on the right +#define DSHIFT 4 +#define DSCALE 1 // storage descaling, needed to make the error fit int8_t + +// Quantize as usual, but also compute and return the quantization error. +// Error is already divided by DSHIFT. +static int QuantizeSingle(int16_t* const v, const VP8Matrix* const mtx) { + int V = *v; + const int sign = (V < 0); + if (sign) V = -V; + if (V > (int)mtx->zthresh_[0]) { + const int qV = QUANTDIV(V, mtx->iq_[0], mtx->bias_[0]) * mtx->q_[0]; + const int err = (V - qV); + *v = sign ? -qV : qV; + return (sign ? -err : err) >> DSCALE; + } + *v = 0; + return (sign ? -V : V) >> DSCALE; +} + +static void CorrectDCValues(const VP8EncIterator* const it, + const VP8Matrix* const mtx, + int16_t tmp[][16], VP8ModeScore* const rd) { + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. + int ch; + for (ch = 0; ch <= 1; ++ch) { + const int8_t* const top = it->top_derr_[it->x_][ch]; + const int8_t* const left = it->left_derr_[ch]; + int16_t (* const c)[16] = &tmp[ch * 4]; + int err0, err1, err2, err3; + c[0][0] += (C1 * top[0] + C2 * left[0]) >> (DSHIFT - DSCALE); + err0 = QuantizeSingle(&c[0][0], mtx); + c[1][0] += (C1 * top[1] + C2 * err0) >> (DSHIFT - DSCALE); + err1 = QuantizeSingle(&c[1][0], mtx); + c[2][0] += (C1 * err0 + C2 * left[1]) >> (DSHIFT - DSCALE); + err2 = QuantizeSingle(&c[2][0], mtx); + c[3][0] += (C1 * err1 + C2 * err2) >> (DSHIFT - DSCALE); + err3 = QuantizeSingle(&c[3][0], mtx); + // error 'err' is bounded by mtx->q_[0] which is 132 at max. Hence + // err >> DSCALE will fit in an int8_t type if DSCALE>=1. + assert(abs(err1) <= 127 && abs(err2) <= 127 && abs(err3) <= 127); + rd->derr[ch][0] = (int8_t)err1; + rd->derr[ch][1] = (int8_t)err2; + rd->derr[ch][2] = (int8_t)err3; + } +} + +static void StoreDiffusionErrors(VP8EncIterator* const it, + const VP8ModeScore* const rd) { + int ch; + for (ch = 0; ch <= 1; ++ch) { + int8_t* const top = it->top_derr_[it->x_][ch]; + int8_t* const left = it->left_derr_[ch]; + left[0] = rd->derr[ch][0]; // restore err1 + left[1] = 3 * rd->derr[ch][2] >> 2; // ... 3/4th of err3 + top[0] = rd->derr[ch][1]; // ... err2 + top[1] = rd->derr[ch][2] - left[1]; // ... 1/4th of err3. + } +} + +#undef C1 +#undef C2 +#undef DSHIFT +#undef DSCALE + +//------------------------------------------------------------------------------ + static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, uint8_t* const yuv_out, int mode) { const VP8Encoder* const enc = it->enc_; @@ -839,6 +918,8 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, for (n = 0; n < 8; n += 2) { VP8FTransform2(src + VP8ScanUV[n], ref + VP8ScanUV[n], tmp[n]); } + if (it->top_derr_ != NULL) CorrectDCValues(it, &dqm->uv_, tmp, rd); + if (DO_TRELLIS_UV && it->do_trellis_) { int ch, x, y; for (ch = 0, n = 0; ch <= 2; ch += 2) { @@ -1101,6 +1182,9 @@ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { CopyScore(&rd_best, &rd_uv); rd->mode_uv = mode; memcpy(rd->uv_levels, rd_uv.uv_levels, sizeof(rd->uv_levels)); + if (it->top_derr_ != NULL) { + memcpy(rd->derr, rd_uv.derr, sizeof(rd_uv.derr)); + } SwapPtr(&dst, &tmp_dst); } } @@ -1109,6 +1193,9 @@ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { if (dst != dst0) { // copy 16x8 block if needed VP8Copy16x8(dst, dst0); } + if (it->top_derr_ != NULL) { // store diffusion errors for next block + StoreDiffusionErrors(it, rd); + } } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/enc/vp8i_enc.h b/thirdparty/libwebp/src/enc/vp8i_enc.h index 3463491e9d..624e8f8e66 100644 --- a/thirdparty/libwebp/src/enc/vp8i_enc.h +++ b/thirdparty/libwebp/src/enc/vp8i_enc.h @@ -30,9 +30,9 @@ extern "C" { // Various defines and enums // version numbers -#define ENC_MAJ_VERSION 0 -#define ENC_MIN_VERSION 6 -#define ENC_REV_VERSION 1 +#define ENC_MAJ_VERSION 1 +#define ENC_MIN_VERSION 0 +#define ENC_REV_VERSION 0 enum { MAX_LF_LEVELS = 64, // Maximum loop filter level MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost @@ -120,6 +120,9 @@ static WEBP_INLINE int QUANTDIV(uint32_t n, uint32_t iQ, uint32_t B) { // Uncomment the following to remove token-buffer code: // #define DISABLE_TOKEN_BUFFER +// quality below which error-diffusion is enabled +#define ERROR_DIFFUSION_QUALITY 98 + //------------------------------------------------------------------------------ // Headers @@ -201,6 +204,8 @@ typedef struct { score_t i4_penalty_; // penalty for using Intra4 } VP8SegmentInfo; +typedef int8_t DError[2 /* u/v */][2 /* top or left */]; + // Handy transient struct to accumulate score and info during RD-optimization // and mode evaluation. typedef struct { @@ -213,6 +218,7 @@ typedef struct { uint8_t modes_i4[16]; // mode numbers for intra4 predictions int mode_uv; // mode number of chroma prediction uint32_t nz; // non-zero blocks + int8_t derr[2][3]; // DC diffusion errors for U/V for blocks #1/2/3 } VP8ModeScore; // Iterator structure to iterate through macroblocks, pointing to the @@ -242,6 +248,9 @@ typedef struct { int count_down0_; // starting counter value (for progress) int percent0_; // saved initial progress percent + DError left_derr_; // left error diffusion (u/v) + DError *top_derr_; // top diffusion error - NULL if disabled + uint8_t* y_left_; // left luma samples (addressable from index -1 to 15). uint8_t* u_left_; // left u samples (addressable from index -1 to 7) uint8_t* v_left_; // left v samples (addressable from index -1 to 7) @@ -401,6 +410,7 @@ struct VP8Encoder { uint8_t* uv_top_; // top u/v samples. // U and V are packed into 16 bytes (8 U + 8 V) LFStats* lf_stats_; // autofilter stats (if NULL, autofilter is off) + DError* top_derr_; // diffusion error (NULL if disabled) }; //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/enc/vp8l_enc.c b/thirdparty/libwebp/src/enc/vp8l_enc.c index 312e521906..a89184eb08 100644 --- a/thirdparty/libwebp/src/enc/vp8l_enc.c +++ b/thirdparty/libwebp/src/enc/vp8l_enc.c @@ -26,8 +26,6 @@ #include "src/utils/utils.h" #include "src/webp/format_constants.h" -#include "src/enc/delta_palettization_enc.h" - // Maximum number of histogram images (sub-blocks). #define MAX_HUFF_IMAGE_SIZE 2600 @@ -259,7 +257,7 @@ static int AnalyzeEntropy(const uint32_t* argb, ++histo[kHistoAlphaPred * 256]; for (j = 0; j < kHistoTotal; ++j) { - entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256, NULL); + entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256); } entropy[kDirect] = entropy_comp[kHistoAlpha] + entropy_comp[kHistoRed] + @@ -384,8 +382,7 @@ static int EncoderAnalyze(VP8LEncoder* const enc, AnalyzeAndCreatePalette(pic, low_effort, enc->palette_, &enc->palette_size_); - // TODO(jyrki): replace the decision to be based on an actual estimate - // of entropy, or even spatial variance of entropy. + // Empirical bit sizes. enc->histo_bits_ = GetHistoBits(method, use_palette, pic->width, pic->height); enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); @@ -756,7 +753,6 @@ static WebPEncodingError StoreImageToBitMask( // Don't write the distance with the extra bits code since // the distance can be up to 18 bits of extra bits, and the prefix // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. - // TODO(jyrki): optimize this further. VP8LPrefixEncode(distance, &code, &n_bits, &bits); WriteHuffmanCode(bw, codes + 4, code); VP8LPutBits(bw, bits, n_bits); @@ -1464,49 +1460,6 @@ static WebPEncodingError EncodePalette(VP8LBitWriter* const bw, int low_effort, 20 /* quality */, low_effort); } -#ifdef WEBP_EXPERIMENTAL_FEATURES - -static WebPEncodingError EncodeDeltaPalettePredictorImage( - VP8LBitWriter* const bw, VP8LEncoder* const enc, int quality, - int low_effort) { - const WebPPicture* const pic = enc->pic_; - const int width = pic->width; - const int height = pic->height; - - const int pred_bits = 5; - const int transform_width = VP8LSubSampleSize(width, pred_bits); - const int transform_height = VP8LSubSampleSize(height, pred_bits); - const int pred = 7; // default is Predictor7 (Top/Left Average) - const int tiles_per_row = VP8LSubSampleSize(width, pred_bits); - const int tiles_per_col = VP8LSubSampleSize(height, pred_bits); - uint32_t* predictors; - int tile_x, tile_y; - WebPEncodingError err = VP8_ENC_OK; - - predictors = (uint32_t*)WebPSafeMalloc(tiles_per_col * tiles_per_row, - sizeof(*predictors)); - if (predictors == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; - - for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { - for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { - predictors[tile_y * tiles_per_row + tile_x] = 0xff000000u | (pred << 8); - } - } - - VP8LPutBits(bw, TRANSFORM_PRESENT, 1); - VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); - VP8LPutBits(bw, pred_bits - 2, 3); - err = EncodeImageNoHuffman( - bw, predictors, &enc->hash_chain_, - (VP8LBackwardRefs*)&enc->refs_[0], // cast const away - (VP8LBackwardRefs*)&enc->refs_[1], - transform_width, transform_height, quality, low_effort); - WebPSafeFree(predictors); - return err; -} - -#endif // WEBP_EXPERIMENTAL_FEATURES - // ----------------------------------------------------------------------------- // VP8LEncoder @@ -1568,7 +1521,7 @@ static int EncodeStreamHook(void* input, void* data2) { WebPEncodingError err = VP8_ENC_OK; const int quality = (int)config->quality; const int low_effort = (config->method == 0); -#if (WEBP_NEAR_LOSSLESS == 1) || defined(WEBP_EXPERIMENTAL_FEATURES) +#if (WEBP_NEAR_LOSSLESS == 1) const int width = picture->width; #endif const int height = picture->height; @@ -1627,29 +1580,6 @@ static int EncodeStreamHook(void* input, void* data2) { enc->argb_content_ = kEncoderNone; #endif -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (config->use_delta_palette) { - enc->use_predict_ = 1; - enc->use_cross_color_ = 0; - enc->use_subtract_green_ = 0; - enc->use_palette_ = 1; - if (enc->argb_content_ != kEncoderNearLossless && - enc->argb_content_ != kEncoderPalette) { - err = MakeInputImageCopy(enc); - if (err != VP8_ENC_OK) goto Error; - } - err = WebPSearchOptimalDeltaPalette(enc); - if (err != VP8_ENC_OK) goto Error; - if (enc->use_palette_) { - err = AllocateTransformBuffer(enc, width, height); - if (err != VP8_ENC_OK) goto Error; - err = EncodeDeltaPalettePredictorImage(bw, enc, quality, low_effort); - if (err != VP8_ENC_OK) goto Error; - use_delta_palette = 1; - } - } -#endif // WEBP_EXPERIMENTAL_FEATURES - // Encode palette if (enc->use_palette_) { err = EncodePalette(bw, low_effort, enc); @@ -1822,7 +1752,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, worker_interface->Init(worker); worker->data1 = param; worker->data2 = NULL; - worker->hook = (WebPWorkerHook)EncodeStreamHook; + worker->hook = EncodeStreamHook; } } @@ -1944,7 +1874,6 @@ int VP8LEncodeImage(const WebPConfig* const config, err = VP8LEncodeStream(config, picture, &bw, 1 /*use_cache*/); if (err != VP8_ENC_OK) goto Error; - // TODO(skal): have a fine-grained progress report in VP8LEncodeStream(). if (!WebPReportProgress(picture, 90, &percent)) goto UserAbort; // Finish the RIFF chunk. diff --git a/thirdparty/libwebp/src/enc/webp_enc.c b/thirdparty/libwebp/src/enc/webp_enc.c index 283cda8e7b..9f4b10c26c 100644 --- a/thirdparty/libwebp/src/enc/webp_enc.c +++ b/thirdparty/libwebp/src/enc/webp_enc.c @@ -159,12 +159,16 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, + WEBP_ALIGN_CST; // align all const size_t lf_stats_size = config->autofilter ? sizeof(*enc->lf_stats_) + WEBP_ALIGN_CST : 0; + const size_t top_derr_size = + (config->quality <= ERROR_DIFFUSION_QUALITY || config->pass > 1) ? + mb_w * sizeof(*enc->top_derr_) : 0; uint8_t* mem; const uint64_t size = (uint64_t)sizeof(*enc) // main struct + WEBP_ALIGN_CST // cache alignment + info_size // modes info + preds_size // prediction modes + samples_size // top/left samples + + top_derr_size // top diffusion error + nz_size // coeff context bits + lf_stats_size; // autofilter stats @@ -175,11 +179,12 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, " info: %ld\n" " preds: %ld\n" " top samples: %ld\n" + " top diffusion: %ld\n" " non-zero: %ld\n" " lf-stats: %ld\n" " total: %ld\n", sizeof(*enc) + WEBP_ALIGN_CST, info_size, - preds_size, samples_size, nz_size, lf_stats_size, size); + preds_size, samples_size, top_derr_size, nz_size, lf_stats_size, size); printf("Transient object sizes:\n" " VP8EncIterator: %ld\n" " VP8ModeScore: %ld\n" @@ -219,6 +224,8 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, enc->y_top_ = mem; enc->uv_top_ = enc->y_top_ + top_stride; mem += 2 * top_stride; + enc->top_derr_ = top_derr_size ? (DError*)mem : NULL; + mem += top_derr_size; assert(mem <= (uint8_t*)enc + size); enc->config_ = config; diff --git a/thirdparty/libwebp/src/mux/muxi.h b/thirdparty/libwebp/src/mux/muxi.h index b73e3fbd7a..6b57eea30f 100644 --- a/thirdparty/libwebp/src/mux/muxi.h +++ b/thirdparty/libwebp/src/mux/muxi.h @@ -26,9 +26,9 @@ extern "C" { //------------------------------------------------------------------------------ // Defines and constants. -#define MUX_MAJ_VERSION 0 -#define MUX_MIN_VERSION 4 -#define MUX_REV_VERSION 1 +#define MUX_MAJ_VERSION 1 +#define MUX_MIN_VERSION 0 +#define MUX_REV_VERSION 0 // Chunk object. typedef struct WebPChunk WebPChunk; diff --git a/thirdparty/libwebp/src/utils/endian_inl_utils.h b/thirdparty/libwebp/src/utils/endian_inl_utils.h index 4b2f91dfb8..3630a293bf 100644 --- a/thirdparty/libwebp/src/utils/endian_inl_utils.h +++ b/thirdparty/libwebp/src/utils/endian_inl_utils.h @@ -19,13 +19,6 @@ #include "src/dsp/dsp.h" #include "src/webp/types.h" -// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) -#if !defined(WORDS_BIGENDIAN) && \ - (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ - (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) -#define WORDS_BIGENDIAN -#endif - #if defined(WORDS_BIGENDIAN) #define HToLE32 BSwap32 #define HToLE16 BSwap16 |