diff options
119 files changed, 3468 insertions, 1196 deletions
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index 555d4f6df4..a363cc3694 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1030,7 +1030,7 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) { } Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, int p_depth) { - ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential inifite recursion detected. Bailing."); + ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential infinite recursion detected. Bailing."); uint8_t *buf = r_buffer; r_len = 0; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 3df4db9c5e..c29316c089 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1007,20 +1007,30 @@ bool ClassDB::get_signal(const StringName &p_class, const StringName &p_signal, return false; } -void ClassDB::add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix) { +void ClassDB::add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix, int p_indent_depth) { OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); ERR_FAIL_COND(!type); - type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_GROUP)); + String prefix = p_prefix; + if (p_indent_depth > 0) { + prefix = vformat("%s,%d", p_prefix, p_indent_depth); + } + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, prefix, PROPERTY_USAGE_GROUP)); } -void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix) { +void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix, int p_indent_depth) { OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); ERR_FAIL_COND(!type); - type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_SUBGROUP)); + String prefix = p_prefix; + if (p_indent_depth > 0) { + prefix = vformat("%s,%d", p_prefix, p_indent_depth); + } + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, prefix, PROPERTY_USAGE_SUBGROUP)); } void ClassDB::add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage) { diff --git a/core/object/class_db.h b/core/object/class_db.h index 5adf1a59a4..5d258a29bf 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -328,8 +328,8 @@ public: static bool get_signal(const StringName &p_class, const StringName &p_signal, MethodInfo *r_signal); static void get_signal_list(const StringName &p_class, List<MethodInfo> *p_signals, bool p_no_inheritance = false); - static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = ""); - static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = ""); + static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = "", int p_indent_depth = 0); + static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = "", int p_indent_depth = 0); static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_DEFAULT); static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix); static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1); diff --git a/core/object/object.h b/core/object/object.h index 1a0a81581d..be360703bc 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -142,7 +142,9 @@ enum PropertyUsageFlags { #define ADD_PROPERTYI(m_property, m_setter, m_getter, m_index) ::ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter), m_index) #define ADD_PROPERTY_DEFAULT(m_property, m_default) ::ClassDB::set_property_default_value(get_class_static(), m_property, m_default) #define ADD_GROUP(m_name, m_prefix) ::ClassDB::add_property_group(get_class_static(), m_name, m_prefix) +#define ADD_GROUP_INDENT(m_name, m_prefix, m_depth) ::ClassDB::add_property_group(get_class_static(), m_name, m_prefix, m_depth) #define ADD_SUBGROUP(m_name, m_prefix) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) +#define ADD_SUBGROUP_INDENT(m_name, m_prefix, m_depth) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix, m_depth) #define ADD_LINKED_PROPERTY(m_property, m_linked_property) ::ClassDB::add_linked_property(get_class_static(), m_property, m_linked_property) #define ADD_ARRAY_COUNT(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix) diff --git a/core/string/string_name.h b/core/string/string_name.h index f767f3e1ec..6f08d32981 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -188,7 +188,7 @@ StringName _scs_create(const char *p_chr, bool p_static = false); * - Control::get_theme_*(<name> and Window::get_theme_*(<name> functions. * - emit_signal(<name>,..) function * - call_deferred(<name>,..) function - * - Comparisons to a StringName in overriden _set and _get methods. + * - Comparisons to a StringName in overridden _set and _get methods. * * Use in places that can be called hundreds of times per frame (or more) is recommended, but this situation is very rare. If in doubt, do not use. */ diff --git a/doc/classes/ArrayOccluder3D.xml b/doc/classes/ArrayOccluder3D.xml index 993393cf50..cb682a7e62 100644 --- a/doc/classes/ArrayOccluder3D.xml +++ b/doc/classes/ArrayOccluder3D.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ArrayOccluder3D" inherits="Occluder3D" version="4.0"> <brief_description> + 3D polygon shape for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [ArrayOccluder3D] stores an arbitrary 3D polygon shape that can be used by the engine's occlusion culling system. This is analogous to [ArrayMesh], but for occluders. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index 0ad161a6fe..30e23820cf 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -47,7 +47,7 @@ </methods> <members> <member name="area_mask" type="int" setter="set_area_mask" getter="get_area_mask" default="1"> - Areas in which this sound plays. + Determines which [Area2D] layers affect the sound for reverb and audio bus effects. Areas can be used to redirect [AudioStream]s so that they play in a certain audio bus. An example of how you might use this is making a "water" area so that sounds played in the water are redirected through an audio bus to make them sound like they are being played underwater. </member> <member name="attenuation" type="float" setter="set_attenuation" getter="get_attenuation" default="1.0"> Dampens audio over distance with this as an exponent. diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index ce8a6693db..52f9e23d98 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -48,7 +48,7 @@ </methods> <members> <member name="area_mask" type="int" setter="set_area_mask" getter="get_area_mask" default="1"> - Areas in which this sound plays. + Determines which [Area3D] layers affect the sound for reverb and audio bus effects. Areas can be used to redirect [AudioStream]s so that they play in a certain audio bus. An example of how you might use this is making a "water" area so that sounds played in the water are redirected through an audio bus to make them sound like they are being played underwater. </member> <member name="attenuation_filter_cutoff_hz" type="float" setter="set_attenuation_filter_cutoff_hz" getter="get_attenuation_filter_cutoff_hz" default="5000.0"> Dampens audio using a low-pass filter above this frequency, in Hz. To disable the dampening effect entirely, set this to [code]20500[/code] as this frequency is above the human hearing limit. diff --git a/doc/classes/BoxOccluder3D.xml b/doc/classes/BoxOccluder3D.xml index 8c3b597193..d16cf55098 100644 --- a/doc/classes/BoxOccluder3D.xml +++ b/doc/classes/BoxOccluder3D.xml @@ -1,13 +1,17 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="BoxOccluder3D" inherits="Occluder3D" version="4.0"> <brief_description> + Cuboid shape for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [BoxOccluder3D] stores a cuboid shape that can be used by the engine's occlusion culling system. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> <members> <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)"> + The box's size in 3D units. </member> </members> </class> diff --git a/doc/classes/Container.xml b/doc/classes/Container.xml index 83655425fc..076a800e29 100644 --- a/doc/classes/Container.xml +++ b/doc/classes/Container.xml @@ -10,6 +10,20 @@ <tutorials> </tutorials> <methods> + <method name="_get_allowed_size_flags_horizontal" qualifiers="virtual const"> + <return type="PackedInt32Array" /> + <description> + Implement to return a list of allowed horizontal [enum Control.SizeFlags] for child nodes. This doesn't technically prevent the usages of any other size flags, if your implementation requires that. This only limits the options available to the user in the inspector dock. + [b]Note:[/b] Having no size flags is equal to having [constant Control.SIZE_SHRINK_BEGIN]. As such, this value is always implicitly allowed. + </description> + </method> + <method name="_get_allowed_size_flags_vertical" qualifiers="virtual const"> + <return type="PackedInt32Array" /> + <description> + Implement to return a list of allowed vertical [enum Control.SizeFlags] for child nodes. This doesn't technically prevent the usages of any other size flags, if your implementation requires that. This only limits the options available to the user in the inspector dock. + [b]Note:[/b] Having no size flags is equal to having [constant Control.SIZE_SHRINK_BEGIN]. As such, this value is always implicitly allowed. + </description> + </method> <method name="fit_child_in_rect"> <return type="void" /> <argument index="0" name="child" type="Control" /> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index b6c2dac33c..f2d727bb51 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1278,20 +1278,24 @@ <constant name="PRESET_MODE_KEEP_SIZE" value="3" enum="LayoutPresetMode"> The control's size will not change. </constant> + <constant name="SIZE_SHRINK_BEGIN" value="0" enum="SizeFlags"> + Tells the parent [Container] to align the node with its start, either the top or the left edge. It is mutually exclusive with [constant SIZE_FILL] and other shrink size flags, but can be used with [constant SIZE_EXPAND] in some containers. Use with [member size_flags_horizontal] and [member size_flags_vertical]. + [b]Note:[/b] Setting this flag is equal to not having any size flags. + </constant> <constant name="SIZE_FILL" value="1" enum="SizeFlags"> - Tells the parent [Container] to expand the bounds of this node to fill all the available space without pushing any other node. Use with [member size_flags_horizontal] and [member size_flags_vertical]. + Tells the parent [Container] to expand the bounds of this node to fill all the available space without pushing any other node. It is mutually exclusive with shrink size flags. Use with [member size_flags_horizontal] and [member size_flags_vertical]. </constant> <constant name="SIZE_EXPAND" value="2" enum="SizeFlags"> Tells the parent [Container] to let this node take all the available space on the axis you flag. If multiple neighboring nodes are set to expand, they'll share the space based on their stretch ratio. See [member size_flags_stretch_ratio]. Use with [member size_flags_horizontal] and [member size_flags_vertical]. </constant> <constant name="SIZE_EXPAND_FILL" value="3" enum="SizeFlags"> - Sets the node's size flags to both fill and expand. See the 2 constants above for more information. + Sets the node's size flags to both fill and expand. See [constant SIZE_FILL] and [constant SIZE_EXPAND] for more information. </constant> <constant name="SIZE_SHRINK_CENTER" value="4" enum="SizeFlags"> - Tells the parent [Container] to center the node in itself. It centers the control based on its bounding box, so it doesn't work with the fill or expand size flags. Use with [member size_flags_horizontal] and [member size_flags_vertical]. + Tells the parent [Container] to center the node in the available space. It is mutually exclusive with [constant SIZE_FILL] and other shrink size flags, but can be used with [constant SIZE_EXPAND] in some containers. Use with [member size_flags_horizontal] and [member size_flags_vertical]. </constant> <constant name="SIZE_SHRINK_END" value="8" enum="SizeFlags"> - Tells the parent [Container] to align the node with its end, either the bottom or the right edge. It doesn't work with the fill or expand size flags. Use with [member size_flags_horizontal] and [member size_flags_vertical]. + Tells the parent [Container] to align the node with its end, either the bottom or the right edge. It is mutually exclusive with [constant SIZE_FILL] and other shrink size flags, but can be used with [constant SIZE_EXPAND] in some containers. Use with [member size_flags_horizontal] and [member size_flags_vertical]. </constant> <constant name="MOUSE_FILTER_STOP" value="0" enum="MouseFilter"> The control will receive mouse button input events through [method _gui_input] if clicked on. And the control will receive the [signal mouse_entered] and [signal mouse_exited] signals. These events are automatically marked as handled, and they will not propagate further to other controls. This also results in blocking signals in other controls. diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml index 0e1bf99afc..3c2f735e72 100644 --- a/doc/classes/EngineDebugger.xml +++ b/doc/classes/EngineDebugger.xml @@ -67,7 +67,7 @@ <argument index="0" name="name" type="StringName" /> <argument index="1" name="profiler" type="EngineProfiler" /> <description> - Registers a profiler with the given [code]name[/code]. See [EngineProfiler] for more informations. + Registers a profiler with the given [code]name[/code]. See [EngineProfiler] for more information. </description> </method> <method name="send_message"> diff --git a/doc/classes/EngineProfiler.xml b/doc/classes/EngineProfiler.xml index 88780b1a41..60817338b8 100644 --- a/doc/classes/EngineProfiler.xml +++ b/doc/classes/EngineProfiler.xml @@ -5,7 +5,7 @@ </brief_description> <description> This class can be used to implement custom profilers that are able to interact with the engine and editor debugger. - See [EngineDebugger] and [EditorDebuggerPlugin] for more informations. + See [EngineDebugger] and [EditorDebuggerPlugin] for more information. </description> <tutorials> </tutorials> @@ -24,7 +24,7 @@ <argument index="2" name="physics_time" type="float" /> <argument index="3" name="physics_frame_time" type="float" /> <description> - Called once every engine iteration when the profiler is active with informations about the current frame. + Called once every engine iteration when the profiler is active with information about the current frame. </description> </method> <method name="_toggle" qualifiers="virtual"> diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index c8c0494378..f2dbcec228 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -171,11 +171,11 @@ </member> <member name="reflected_light_source" type="int" setter="set_reflection_source" getter="get_reflection_source" enum="Environment.ReflectionSource" default="0"> </member> - <member name="sdfgi_bounce_feedback" type="float" setter="set_sdfgi_bounce_feedback" getter="get_sdfgi_bounce_feedback" default="0.0"> + <member name="sdfgi_bounce_feedback" type="float" setter="set_sdfgi_bounce_feedback" getter="get_sdfgi_bounce_feedback" default="0.5"> </member> <member name="sdfgi_cascade0_distance" type="float" setter="set_sdfgi_cascade0_distance" getter="get_sdfgi_cascade0_distance" default="12.8"> </member> - <member name="sdfgi_cascades" type="int" setter="set_sdfgi_cascades" getter="get_sdfgi_cascades" default="6"> + <member name="sdfgi_cascades" type="int" setter="set_sdfgi_cascades" getter="get_sdfgi_cascades" default="4"> The number of cascades to use for SDFGI (between 1 and 8). A higher number of cascades allows displaying SDFGI further away while preserving detail up close, at the cost of performance. When using SDFGI on small-scale levels, [member sdfgi_cascades] can often be decreased between [code]1[/code] and [code]4[/code] to improve performance. </member> <member name="sdfgi_enabled" type="bool" setter="set_sdfgi_enabled" getter="is_sdfgi_enabled" default="false"> @@ -185,7 +185,7 @@ </member> <member name="sdfgi_energy" type="float" setter="set_sdfgi_energy" getter="get_sdfgi_energy" default="1.0"> </member> - <member name="sdfgi_max_distance" type="float" setter="set_sdfgi_max_distance" getter="get_sdfgi_max_distance" default="819.2"> + <member name="sdfgi_max_distance" type="float" setter="set_sdfgi_max_distance" getter="get_sdfgi_max_distance" default="204.8"> </member> <member name="sdfgi_min_cell_size" type="float" setter="set_sdfgi_min_cell_size" getter="get_sdfgi_min_cell_size" default="0.2"> </member> @@ -193,11 +193,11 @@ </member> <member name="sdfgi_probe_bias" type="float" setter="set_sdfgi_probe_bias" getter="get_sdfgi_probe_bias" default="1.1"> </member> - <member name="sdfgi_read_sky_light" type="bool" setter="set_sdfgi_read_sky_light" getter="is_sdfgi_reading_sky_light" default="false"> + <member name="sdfgi_read_sky_light" type="bool" setter="set_sdfgi_read_sky_light" getter="is_sdfgi_reading_sky_light" default="true"> </member> <member name="sdfgi_use_occlusion" type="bool" setter="set_sdfgi_use_occlusion" getter="is_sdfgi_using_occlusion" default="false"> </member> - <member name="sdfgi_y_scale" type="int" setter="set_sdfgi_y_scale" getter="get_sdfgi_y_scale" enum="Environment.SDFGIYScale" default="0"> + <member name="sdfgi_y_scale" type="int" setter="set_sdfgi_y_scale" getter="get_sdfgi_y_scale" enum="Environment.SDFGIYScale" default="1"> </member> <member name="sky" type="Sky" setter="set_sky" getter="get_sky"> The [Sky] resource used for this [Environment]. @@ -379,11 +379,11 @@ <constant name="GLOW_BLEND_MODE_MIX" value="4" enum="GlowBlendMode"> Mixes the glow with the underlying color to avoid increasing brightness as much while still maintaining a glow effect. </constant> - <constant name="SDFGI_Y_SCALE_DISABLED" value="0" enum="SDFGIYScale"> + <constant name="SDFGI_Y_SCALE_50_PERCENT" value="0" enum="SDFGIYScale"> </constant> <constant name="SDFGI_Y_SCALE_75_PERCENT" value="1" enum="SDFGIYScale"> </constant> - <constant name="SDFGI_Y_SCALE_50_PERCENT" value="2" enum="SDFGIYScale"> + <constant name="SDFGI_Y_SCALE_100_PERCENT" value="2" enum="SDFGIYScale"> </constant> </constants> </class> diff --git a/doc/classes/FontData.xml b/doc/classes/FontData.xml index 55b715c3fc..658f7dd34d 100644 --- a/doc/classes/FontData.xml +++ b/doc/classes/FontData.xml @@ -577,7 +577,7 @@ Font style flags, see [enum TextServer.FontStyle]. </member> <member name="force_autohinter" type="bool" setter="set_force_autohinter" getter="is_force_autohinter" default="false"> - If set to [code]true[/code], auto-hinting is supported and preffered over font built-in hinting. Used by dynamic fonts only. + If set to [code]true[/code], auto-hinting is supported and preferred over font built-in hinting. Used by dynamic fonts only. </member> <member name="hinting" type="int" setter="set_hinting" getter="get_hinting" enum="TextServer.Hinting" default="1"> Font hinting mode. Used by dynamic fonts only. diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index f97198658e..1fde3c8463 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -119,7 +119,7 @@ Particle starts with specified color. </constant> <constant name="EMIT_FLAG_CUSTOM" value="16" enum="EmitFlags"> - Particle starts with specificed [code]CUSTOM[/code] data. + Particle starts with specified [code]CUSTOM[/code] data. </constant> </constants> </class> diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 62ac846077..fc490eac96 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -150,7 +150,7 @@ Particle starts with specified color. </constant> <constant name="EMIT_FLAG_CUSTOM" value="16" enum="EmitFlags"> - Particle starts with specificed [code]CUSTOM[/code] data. + Particle starts with specified [code]CUSTOM[/code] data. </constant> <constant name="MAX_DRAW_PASSES" value="4"> Maximum number of draw passes supported. diff --git a/doc/classes/Occluder3D.xml b/doc/classes/Occluder3D.xml index 6c6c410bc3..01f009abdb 100644 --- a/doc/classes/Occluder3D.xml +++ b/doc/classes/Occluder3D.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Occluder3D" inherits="Resource" version="4.0"> <brief_description> + Occluder shape resource for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [Occluder3D] stores an occluder shape that can be used by the engine's occlusion culling system. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> @@ -10,11 +13,13 @@ <method name="get_indices" qualifiers="const"> <return type="PackedInt32Array" /> <description> + Returns the occluder shape's vertex indices. </description> </method> <method name="get_vertices" qualifiers="const"> <return type="PackedVector3Array" /> <description> + Returns the occluder shape's vertex positions. </description> </method> </methods> diff --git a/doc/classes/OccluderInstance3D.xml b/doc/classes/OccluderInstance3D.xml index 32e48f9a70..0b5fc0fd26 100644 --- a/doc/classes/OccluderInstance3D.xml +++ b/doc/classes/OccluderInstance3D.xml @@ -1,8 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="OccluderInstance3D" inherits="Node3D" version="4.0"> <brief_description> + Provides occlusion culling for 3D nodes, which improves performance in closed areas. </brief_description> <description> + Occlusion culling can improve rendering performance in closed/semi-open areas by hiding geometry that is occluded by other objects. + The occlusion culling system is mostly static. [OccluderInstance3D]s can be moved or hidden at run-time, but doing so will trigger a background recomputation that can take several frames. It is recommended to only move [OccluderInstance3D]s sporadically (e.g. for procedural generation purposes), rather than doing so every frame. + The occlusion culling system works by rendering the occluders on the CPU in parallel using [url=https://www.embree.org/]Embree[/url], drawing the result to a low-resolution buffer then using this to cull 3D nodes individually. In the 3D editor, you can preview the occlusion culling buffer by choosing [b]Perspective > Debug Advanced... > Occlusion Culling Buffer[/b] in the top-left corner of the 3D viewport. The occlusion culling buffer quality can be adjusted in the Project Settings. + [b]Baking:[/b] Select an [OccluderInstance3D] node, then use the [b]Bake Occluders[/b] button at the top of the 3D editor. Only opaque materials will be taken into account; transparent materials (alpha-blended or alpha-tested) will be ignored by the occluder generation. + [b]Note:[/b] Occlusion culling is only effective if [member ProjectSettings.rendering/occlusion_culling/use_occlusion_culling] is [code]true[/code]. Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it. Large open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. Large open scenes generally benefit more from mesh LOD and visibility ranges ([member GeometryInstance3D.visibility_range_begin] and [member GeometryInstance3D.visibility_range_end]) compared to occlusion culling. </description> <tutorials> </tutorials> @@ -11,7 +17,7 @@ <return type="bool" /> <argument index="0" name="layer_number" type="int" /> <description> - Returns whether or not the specified layer of the [member bake_mask] is enabled, given a [code]layer_number[/code] between 1 and 20. + Returns whether or not the specified layer of the [member bake_mask] is enabled, given a [code]layer_number[/code] between 1 and 32. </description> </method> <method name="set_bake_mask_value"> @@ -19,16 +25,25 @@ <argument index="0" name="layer_number" type="int" /> <argument index="1" name="value" type="bool" /> <description> - Based on [code]value[/code], enables or disables the specified layer in the [member bake_mask], given a [code]layer_number[/code] between 1 and 20. + Based on [code]value[/code], enables or disables the specified layer in the [member bake_mask], given a [code]layer_number[/code] between 1 and 32. </description> </method> </methods> <members> <member name="bake_mask" type="int" setter="set_bake_mask" getter="get_bake_mask" default="4294967295"> + The visual layers to account for when baking for occluders. Only [MeshInstance3D]s whose [member VisualInstance3D.layers] match with this [member bake_mask] will be included in the generated occluder mesh. By default, all objects are taken into account for the occluder baking. + To improve performance and avoid artifacts, it is recommended to exclude dynamic objects, small objects and fixtures from the baking process by moving them to a separate visual layer and excluding this layer in [member bake_mask]. </member> <member name="bake_simplification_distance" type="float" setter="set_bake_simplification_distance" getter="get_bake_simplification_distance" default="0.1"> + The simplification distance to use for simplifying the generated occluder polygon (in 3D units). Higher values result in a less detailed occluder mesh, which improves performance but reduces culling accuracy. + The occluder geometry is rendered on the CPU, so it is important to keep its geometry as simple as possible. Since the buffer is rendered at a low resolution, less detailed occluder meshes generally still work well. The default value is fairly aggressive, so you may have to decrase it if you run into false negatives (objects being occluded even though they are visible by the camera). A value of [code]0.01[/code] will act conservatively, and will keep geometry [i]perceptually[/i] unaffected in the occlusion culling buffer. Depending on the scene, a value of [code]0.01[/code] may still simplify the mesh noticeably compared to disabling simplification entirely. + Setting this to [code]0.0[/code] disables simplification entirely, but vertices in the exact same position will still be merged. The mesh will also be re-indexed to reduce both the number of vertices and indices. + [b]Note:[/b] This uses the [url=https://meshoptimizer.org/]meshoptimizer[/url] library under the hood, similar to LOD generation. </member> <member name="occluder" type="Occluder3D" setter="set_occluder" getter="get_occluder"> + The occluder resource for this [OccluderInstance3D]. You can generate an occluder resource by selecting an [OccluderInstance3D] node then using the [b]Bake Occluders[/b] button at the top of the editor. + You can also draw your own 2D occluder polygon by adding a new [PolygonOccluder3D] resource to the [member occluder] property in the inspector. + Alternatively, you can select a primitive occluder to use: [QuadOccluder3D], [BoxOccluder3D] or [SphereOccluder3D]. </member> </members> </class> diff --git a/doc/classes/PolygonOccluder3D.xml b/doc/classes/PolygonOccluder3D.xml index a4d910c983..e4bd84beac 100644 --- a/doc/classes/PolygonOccluder3D.xml +++ b/doc/classes/PolygonOccluder3D.xml @@ -1,13 +1,18 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PolygonOccluder3D" inherits="Occluder3D" version="4.0"> <brief_description> + Flat 2D polygon shape for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [PolygonOccluder3D] stores a polygon shape that can be used by the engine's occlusion culling system. When an [OccluderInstance3D] with a [PolygonOccluder3D] is selected in the editor, an editor will appear at the top of the 3D viewport so you can add/remove points. All points must be placed on the same 2D plane, which means it is not possible to create arbitrary 3D shapes with a single [PolygonOccluder3D]. To use arbitrary 3D shapes as occluders, use [ArrayOccluder3D] or [OccluderInstance3D]'s baking feature instead. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> <members> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()"> + The polygon to use for occlusion culling. The polygon can be convex or concave, but it should have as few points as possible to maximize performance. + The polygon must [i]not[/i] have intersecting lines. Otherwise, triangulation will fail (with an error message printed). </member> </members> </class> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ed124d1d15..4b5f7b2091 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1696,13 +1696,13 @@ If [code]true[/code], renders [VoxelGI] and SDFGI ([member Environment.sdfgi_enabled]) buffers at halved resolution (e.g. 960×540 when the viewport size is 1920×1080). This improves performance significantly when VoxelGI or SDFGI is enabled, at the cost of artifacts that may be visible on polygon edges. The loss in quality becomes less noticeable as the viewport resolution increases. [LightmapGI] rendering is not affected by this setting. [b]Note:[/b] This property is only read when the project starts. To set half-resolution GI at run-time, call [method RenderingServer.gi_set_use_half_resolution] instead. </member> - <member name="rendering/global_illumination/sdfgi/frames_to_converge" type="int" setter="" getter="" default="4"> + <member name="rendering/global_illumination/sdfgi/frames_to_converge" type="int" setter="" getter="" default="5"> </member> <member name="rendering/global_illumination/sdfgi/frames_to_update_lights" type="int" setter="" getter="" default="2"> </member> <member name="rendering/global_illumination/sdfgi/probe_ray_count" type="int" setter="" getter="" default="1"> </member> - <member name="rendering/global_illumination/voxel_gi/quality" type="int" setter="" getter="" default="1"> + <member name="rendering/global_illumination/voxel_gi/quality" type="int" setter="" getter="" default="0"> </member> <member name="rendering/lightmapping/bake_performance/max_rays_per_pass" type="int" setter="" getter="" default="32"> </member> @@ -1746,10 +1746,14 @@ [b]Note:[/b] This property is only read when the project starts. To adjust the automatic LOD threshold at runtime, set [member Viewport.mesh_lod_threshold] on the root [Viewport]. </member> <member name="rendering/occlusion_culling/bvh_build_quality" type="int" setter="" getter="" default="2"> + The [url=https://en.wikipedia.org/wiki/Bounding_volume_hierarchy]BVH[/url] quality to use when rendering the occlusion culling buffer. Higher values will result in more accurate occlusion culling, at the cost of higher CPU usage. </member> <member name="rendering/occlusion_culling/occlusion_rays_per_thread" type="int" setter="" getter="" default="512"> + Higher values will result in more accurate occlusion culling, at the cost of higher CPU usage. The occlusion culling buffer's pixel count is roughly equal to [code]occlusion_rays_per_thread * number_of_logical_cpu_cores[/code], so it will depend on the system's CPU. Therefore, CPUs with fewer cores will use a lower resolution to attempt keeping performance costs even across devices. </member> <member name="rendering/occlusion_culling/use_occlusion_culling" type="bool" setter="" getter="" default="false"> + If [code]true[/code], [OccluderInstance3D] nodes will be usable for occlusion culling in 3D in the root viewport. In custom viewports, [member Viewport.use_occlusion_culling] must be set to [code]true[/code] instead. + [b]Note:[/b] Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it. Large open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. Large open scenes generally benefit more from mesh LOD and visibility ranges ([member GeometryInstance3D.visibility_range_begin] and [member GeometryInstance3D.visibility_range_end]) compared to occlusion culling. </member> <member name="rendering/reflections/reflection_atlas/reflection_count" type="int" setter="" getter="" default="64"> Number of cubemaps to store in the reflection atlas. The number of [ReflectionProbe]s in a scene will be limited by this amount. A higher number requires more VRAM. @@ -1826,7 +1830,7 @@ <member name="rendering/shadows/directional_shadow/size.mobile" type="int" setter="" getter="" default="2048"> Lower-end override for [member rendering/shadows/directional_shadow/size] on mobile devices, due to performance concerns or driver support. </member> - <member name="rendering/shadows/directional_shadow/soft_shadow_quality" type="int" setter="" getter="" default="3"> + <member name="rendering/shadows/directional_shadow/soft_shadow_quality" type="int" setter="" getter="" default="2"> Quality setting for shadows cast by [DirectionalLight3D]s. Higher quality settings use more samples when reading from shadow maps and are thus slower. Low quality settings may result in shadows looking grainy. [b]Note:[/b] The Soft Very Low setting will automatically multiply [i]constant[/i] shadow blur by 0.75x to reduce the amount of noise visible. This automatic blur change only affects the constant blur factor defined in [member Light3D.shadow_blur], not the variable blur performed by [DirectionalLight3D]s' [member Light3D.light_angular_distance]. [b]Note:[/b] The Soft High and Soft Ultra settings will automatically multiply [i]constant[/i] shadow blur by 1.5× and 2× respectively to make better use of the increased sample count. This increased blur also improves stability of dynamic object shadows. @@ -1854,7 +1858,7 @@ <member name="rendering/shadows/shadow_atlas/size.mobile" type="int" setter="" getter="" default="2048"> Lower-end override for [member rendering/shadows/shadow_atlas/size] on mobile devices, due to performance concerns or driver support. </member> - <member name="rendering/shadows/shadows/soft_shadow_quality" type="int" setter="" getter="" default="3"> + <member name="rendering/shadows/shadows/soft_shadow_quality" type="int" setter="" getter="" default="2"> Quality setting for shadows cast by [OmniLight3D]s and [SpotLight3D]s. Higher quality settings use more samples when reading from shadow maps and are thus slower. Low quality settings may result in shadows looking grainy. [b]Note:[/b] The Soft Very Low setting will automatically multiply [i]constant[/i] shadow blur by 0.75x to reduce the amount of noise visible. This automatic blur change only affects the constant blur factor defined in [member Light3D.shadow_blur], not the variable blur performed by [DirectionalLight3D]s' [member Light3D.light_angular_distance]. [b]Note:[/b] The Soft High and Soft Ultra settings will automatically multiply shadow blur by 1.5× and 2× respectively to make better use of the increased sample count. This increased blur also improves stability of dynamic object shadows. diff --git a/doc/classes/QuadOccluder3D.xml b/doc/classes/QuadOccluder3D.xml index c1b89149f5..44cbfb88ff 100644 --- a/doc/classes/QuadOccluder3D.xml +++ b/doc/classes/QuadOccluder3D.xml @@ -1,13 +1,17 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="QuadOccluder3D" inherits="Occluder3D" version="4.0"> <brief_description> + Flat plane shape for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [QuadOccluder3D] stores a flat plane shape that can be used by the engine's occlusion culling system. See also [PolygonOccluder3D] if you need to customize the quad's shape. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> <members> <member name="size" type="Vector2" setter="set_size" getter="get_size" default="Vector2(1, 1)"> + The quad's size in 3D units. </member> </members> </class> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 82728c0570..82436b10a5 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -4181,11 +4181,11 @@ <constant name="ENV_SSIL_QUALITY_ULTRA" value="4" enum="EnvironmentSSILQuality"> Highest quality screen-space indirect lighting. Uses the adaptive target setting which can be dynamically adjusted to smoothly balance performance and visual quality. </constant> - <constant name="ENV_SDFGI_Y_SCALE_DISABLED" value="0" enum="EnvironmentSDFGIYScale"> + <constant name="ENV_SDFGI_Y_SCALE_50_PERCENT" value="0" enum="EnvironmentSDFGIYScale"> </constant> <constant name="ENV_SDFGI_Y_SCALE_75_PERCENT" value="1" enum="EnvironmentSDFGIYScale"> </constant> - <constant name="ENV_SDFGI_Y_SCALE_50_PERCENT" value="2" enum="EnvironmentSDFGIYScale"> + <constant name="ENV_SDFGI_Y_SCALE_100_PERCENT" value="2" enum="EnvironmentSDFGIYScale"> </constant> <constant name="ENV_SDFGI_RAY_COUNT_4" value="0" enum="EnvironmentSDFGIRayCount"> </constant> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 95dffd3e28..6a2812bb93 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -55,6 +55,12 @@ Returns the height of the content. </description> </method> + <method name="get_content_width" qualifiers="const"> + <return type="int" /> + <description> + Returns the width of the content. + </description> + </method> <method name="get_line_count" qualifiers="const"> <return type="int" /> <description> diff --git a/doc/classes/SphereOccluder3D.xml b/doc/classes/SphereOccluder3D.xml index 1ffa51e170..da847cc43f 100644 --- a/doc/classes/SphereOccluder3D.xml +++ b/doc/classes/SphereOccluder3D.xml @@ -1,13 +1,17 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="SphereOccluder3D" inherits="Occluder3D" version="4.0"> <brief_description> + Spherical shape for use with occlusion culling in [OccluderInstance3D]. </brief_description> <description> + [SphereOccluder3D] stores a sphere shape that can be used by the engine's occlusion culling system. + See [OccluderInstance3D]'s documentation for instructions on setting up occlusion culling. </description> <tutorials> </tutorials> <members> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> + The sphere's radius in 3D units. </member> </members> </class> diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index b500bd5658..b212cec5f2 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -557,7 +557,7 @@ <argument index="0" name="font_rid" type="RID" /> <argument index="1" name="force_autohinter" type="bool" /> <description> - If set to [code]true[/code] auto-hinting is preffered over font built-in hinting. + If set to [code]true[/code] auto-hinting is preferred over font built-in hinting. </description> </method> <method name="_font_set_global_oversampling" qualifiers="virtual"> diff --git a/doc/classes/TranslationServer.xml b/doc/classes/TranslationServer.xml index 6ece42da6b..546b7ec242 100644 --- a/doc/classes/TranslationServer.xml +++ b/doc/classes/TranslationServer.xml @@ -138,7 +138,7 @@ <return type="String" /> <argument index="0" name="locale" type="String" /> <description> - Retunrs [code]locale[/code] string standardized to match known locales (e.g. [code]en-US[/code] would be matched to [code]en_US[/code]). + Returns [code]locale[/code] string standardized to match known locales (e.g. [code]en-US[/code] would be matched to [code]en_US[/code]). </description> </method> <method name="translate" qualifiers="const"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 7a60ca9fa6..93c1e8417b 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -55,7 +55,7 @@ <method name="get_mouse_position" qualifiers="const"> <return type="Vector2" /> <description> - Returns the mouse's positon in this [Viewport] using the coordinate system of this [Viewport]. + Returns the mouse's position in this [Viewport] using the coordinate system of this [Viewport]. </description> </method> <method name="get_render_info"> @@ -128,7 +128,7 @@ <method name="gui_release_focus"> <return type="void" /> <description> - Removes the focus from the currently focussed [Control] within this viewport. If no [Control] has the focus, does nothing. + Removes the focus from the currently focused [Control] within this viewport. If no [Control] has the focus, does nothing. </description> </method> <method name="is_embedding_subwindows" qualifiers="const"> @@ -285,6 +285,8 @@ <member name="use_debanding" type="bool" setter="set_use_debanding" getter="is_using_debanding" default="false"> </member> <member name="use_occlusion_culling" type="bool" setter="set_use_occlusion_culling" getter="is_using_occlusion_culling" default="false"> + If [code]true[/code], [OccluderInstance3D] nodes will be usable for occlusion culling in 3D for this viewport. For the root viewport, [member ProjectSettings.rendering/occlusion_culling/use_occlusion_culling] must be set to [code]true[/code] instead. + [b]Note:[/b] Enabling occlusion culling has a cost on the CPU. Only enable occlusion culling if you actually plan to use it, and think whether your scene can actually benefit from occlusion culling. Large, open scenes with few or no objects blocking the view will generally not benefit much from occlusion culling. Large open scenes generally benefit more from mesh LOD and visibility ranges ([member GeometryInstance3D.visibility_range_begin] and [member GeometryInstance3D.visibility_range_end]) compared to occlusion culling. </member> <member name="use_xr" type="bool" setter="set_use_xr" getter="is_using_xr" default="false"> If [code]true[/code], the viewport will use the primary XR interface to render XR output. When applicable this can result in a stereoscopic image and the resulting render being output to a headset. diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 68f3b66f43..365beb434b 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -536,19 +536,19 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S # Inheritance tree # Ascendants if class_def.inherits: - inh = class_def.inherits.strip() + inherits = class_def.inherits.strip() f.write("**" + translate("Inherits:") + "** ") first = True - while inh in state.classes: + while inherits in state.classes: if not first: f.write(" **<** ") else: first = False - f.write(make_type(inh, state)) - inode = state.classes[inh].inherits + f.write(make_type(inherits, state)) + inode = state.classes[inherits].inherits if inode: - inh = inode.strip() + inherits = inode.strip() else: break f.write("\n\n") diff --git a/doc/translations/classes.pot b/doc/translations/classes.pot index a802e3a7ac..ca8bc21508 100644 --- a/doc/translations/classes.pot +++ b/doc/translations/classes.pot @@ -34818,7 +34818,7 @@ msgstr "" #: doc/classes/NavigationAgent.xml doc/classes/NavigationAgent2D.xml msgid "" "The minimal amount of time for which this agent's velocities, that are " -"computed with the collision avoidance algorithim, are safe with respect to " +"computed with the collision avoidance algorithm, are safe with respect to " "other agents. The larger the number, the sooner the agent will respond to " "other agents, but the less freedom in choosing its velocities. Must be " "positive." diff --git a/doc/translations/extract.py b/doc/translations/extract.py index f8223701d5..5708e0072d 100644 --- a/doc/translations/extract.py +++ b/doc/translations/extract.py @@ -222,10 +222,14 @@ def _make_translation_catalog(classes): desc_list = classes[class_name] for elem in desc_list.doc.iter(): if elem.tag in EXTRACT_TAGS: - if not elem.text or len(elem.text) == 0: + elem_text = elem.text + if elem.tag == "link": + elem_text = elem.attrib["title"] if "title" in elem.attrib else "" + if not elem_text or len(elem_text) == 0: continue - line_no = elem._start_line_number if elem.text[0] != "\n" else elem._start_line_number + 1 - desc_str = elem.text.strip() + + line_no = elem._start_line_number if elem_text[0] != "\n" else elem._start_line_number + 1 + desc_str = elem_text.strip() code_block_regions = _make_codeblock_regions(desc_str, desc_list.path) desc_msg = _strip_and_split_desc(desc_str, code_block_regions) desc_obj = Desc(line_no, desc_msg, desc_list) diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 246b908c14..12bb21a5a0 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -78,11 +78,11 @@ public: /* SHADOW ATLAS API */ RID shadow_atlas_create() override; - void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = false) override; + void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) override; void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) override; bool shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) override; - void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) override; + void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) override; int get_directional_light_shadow_size(RID p_light_intance) override; void set_directional_shadow_count(int p_count) override; diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 689d76ba26..1aa1bfddc8 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -860,7 +860,7 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) { free(device_queue_props); print_verbose(" #" + itos(i) + ": " + vendor + " " + name + " - " + (present_supported ? "Supported" : "Unsupported") + ", " + dev_type); - if (present_supported) { // Select first supported device of preffered type: Discrete > Integrated > Virtual > CPU > Other. + if (present_supported) { // Select first supported device of preferred type: Discrete > Integrated > Virtual > CPU > Other. switch (props.deviceType) { case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { if (type_selected < 4) { diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index da376c588e..67cdba043a 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -35,6 +35,8 @@ #include "scene/gui/view_panner.h" #include "scene/resources/text_line.h" +#include <limits.h> + float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) { float h = p_h; h = (h - v_scroll) / v_zoom; @@ -55,15 +57,16 @@ static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, con void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float scale = timeline->get_zoom_scale(); + int limit = timeline->get_name_limit(); - int right_limit = get_size().width - timeline->get_buttons_width(); + int right_limit = get_size().width; //selection may have altered the order of keys Map<float, int> key_order; for (int i = 0; i < animation->track_get_key_count(p_track); i++) { float ofs = animation->track_get_key_time(p_track, i); - if (moving_selection && track == p_track && selection.has(i)) { + if (moving_selection && selection.has(IntPair(p_track, i))) { ofs += moving_selection_offset.x; } @@ -82,11 +85,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset = animation->track_get_key_time(p_track, i); float height = animation->bezier_track_get_key_value(p_track, i); Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i); - if (track == p_track && moving_handle != 0 && moving_handle_key == i) { + if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i) { out_handle = moving_handle_right; } - if (moving_selection && track == p_track && selection.has(i)) { + if (moving_selection && selection.has(IntPair(p_track, i))) { offset += moving_selection_offset.x; height += moving_selection_offset.y; } @@ -96,11 +99,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset_n = animation->track_get_key_time(p_track, i_n); float height_n = animation->bezier_track_get_key_value(p_track, i_n); Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n); - if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) { + if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i_n) { in_handle = moving_handle_left; } - if (moving_selection && track == p_track && selection.has(i_n)) { + if (moving_selection && selection.has(IntPair(p_track, i_n))) { offset_n += moving_selection_offset.x; height_n += moving_selection_offset.y; } @@ -221,20 +224,10 @@ void AnimationBezierTrackEdit::_notification(int p_what) { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); } if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); - bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons")); bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons")); selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons")); } - if (p_what == NOTIFICATION_RESIZED) { - int right_limit = get_size().width - timeline->get_buttons_width(); - int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); - int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); - - right_column->set_position(Vector2(right_limit + hsep, vsep)); - right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2)); - } if (p_what == NOTIFICATION_DRAW) { if (animation.is_null()) { return; @@ -258,101 +251,191 @@ void AnimationBezierTrackEdit::_notification(int p_what) { draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE)); - int right_limit = get_size().width - timeline->get_buttons_width(); + int right_limit = get_size().width; - draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE)); - - String base_path = animation->track_get_path(track); - int end = base_path.find(":"); - if (end != -1) { - base_path = base_path.substr(0, end + 1); - } - - // NAMES AND ICON int vofs = vsep; int margin = 0; - { - NodePath path = animation->track_get_path(track); + Map<int, Color> subtrack_colors; + Color selected_track_color; + subtracks.clear(); + subtrack_icons.clear(); + + Map<String, Vector<int>> track_indices; + int track_count = animation->get_track_count(); + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { + continue; + } - Node *node = nullptr; + String base_path = animation->track_get_path(i); + if (is_filtered) { + if (root && root->has_node(base_path)) { + Node *node = root->get_node(base_path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + } + } - if (root && root->has_node(path)) { - node = root->get_node(path); + int end = base_path.find(":"); + if (end != -1) { + base_path = base_path.substr(0, end + 1); } + Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>(); + indices.push_back(i); + track_indices[base_path] = indices; + } - String text; + for (const KeyValue<String, Vector<int>> &E : track_indices) { + String base_path = E.key; - if (node) { - int ofs = 0; + Vector<int> tracks = E.value; - Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); + // NAMES AND ICON + { + NodePath path = animation->track_get_path(tracks[0]); - text = node->get_name(); - ofs += hsep; - ofs += icon->get_width(); + Node *node = nullptr; - TextLine text_buf = TextLine(text, font, font_size); - text_buf.set_width(limit - ofs - hsep); + if (root && root->has_node(path)) { + node = root->get_node(path); + } - int h = MAX(text_buf.get_size().y, icon->get_height()); + String text; - draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + if (node) { + int ofs = 0; - margin = icon->get_width(); + Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); - Vector2 string_pos = Point2(ofs, vofs + (h - text_buf.get_size().y) / 2 + text_buf.get_line_ascent()); - string_pos = string_pos.floor(); - text_buf.draw(get_canvas_item(), string_pos, color); + text = node->get_name(); + ofs += hsep; - vofs += h + vsep; - } - } + TextLine text_buf = TextLine(text, font, font_size); + text_buf.set_width(limit - ofs - icon->get_width() - hsep); - // RELATED TRACKS TITLES + int h = MAX(text_buf.get_size().y, icon->get_height()); - Map<int, Color> subtrack_colors; - subtracks.clear(); + draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + ofs += icon->get_width(); - for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) != Animation::TYPE_BEZIER) { - continue; - } - String path = animation->track_get_path(i); - if (!path.begins_with(base_path)) { - continue; //another node + margin = icon->get_width(); + + Vector2 string_pos = Point2(ofs, vofs); + string_pos = string_pos.floor(); + text_buf.draw(get_canvas_item(), string_pos, color); + + vofs += h + vsep; + } } - path = path.replace_first(base_path, ""); - Color cc = color; - TextLine text_buf = TextLine(path, font, font_size); - text_buf.set_width(limit - margin - hsep); + Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); + float remove_hpos = limit - hsep - remove->get_width(); + + Ref<Texture2D> lock = get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")); + Ref<Texture2D> unlock = get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")); + float lock_hpos = remove_hpos - hsep - lock->get_width(); + + Ref<Texture2D> visible = get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons")); + Ref<Texture2D> hidden = get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons")); + float visibility_hpos = lock_hpos - hsep - visible->get_width(); + + Ref<Texture2D> solo = get_theme_icon(SNAME("AudioBusSolo"), SNAME("EditorIcons")); + float solo_hpos = visibility_hpos - hsep - solo->get_width(); + + float buttons_width = remove->get_width() + lock->get_width() + visible->get_width() + solo->get_width() + hsep * 3; + + for (int i = 0; i < tracks.size(); ++i) { + // RELATED TRACKS TITLES + + int current_track = tracks[i]; + + String path = animation->track_get_path(current_track); + path = path.replace_first(base_path, ""); + + Color cc = color; + TextLine text_buf = TextLine(path, font, font_size); + text_buf.set_width(limit - margin - buttons_width); + + Rect2 rect = Rect2(margin, vofs, solo_hpos - hsep - solo->get_width(), text_buf.get_size().y + vsep); - Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, text_buf.get_size().y + vsep); - if (i != track) { cc.a *= 0.7; - uint32_t hash = path.hash(); - hash = ((hash >> 16) ^ hash) * 0x45d9f3b; - hash = ((hash >> 16) ^ hash) * 0x45d9f3b; - hash = (hash >> 16) ^ hash; - float h = (hash % 65535) / 65536.0; - Color subcolor; - subcolor.set_hsv(h, 0.2, 0.8); - subcolor.a = 0.5; - draw_rect(Rect2(0, vofs + text_buf.get_size().y * 0.1, margin - hsep, text_buf.get_size().y * 0.8), subcolor); - subtrack_colors[i] = subcolor; - - subtracks[i] = rect; - } else { - Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - ac.a = 0.5; - draw_rect(rect, ac); - } + float h; + if (path.ends_with(":x")) { + h = 0; + } else if (path.ends_with(":y")) { + h = 0.33f; + } else if (path.ends_with(":z")) { + h = 0.66f; + } else { + uint32_t hash = path.hash(); + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = (hash >> 16) ^ hash; + h = (hash % 65535) / 65536.0; + } + + if (current_track != selected_track) { + Color track_color; + if (locked_tracks.has(current_track)) { + track_color.set_hsv(h, 0, 0.4); + } else { + track_color.set_hsv(h, 0.2, 0.8); + } + track_color.a = 0.5; + draw_rect(Rect2(0, vofs, margin - hsep, text_buf.get_size().y * 0.8), track_color); + subtrack_colors[current_track] = track_color; + + subtracks[current_track] = rect; + } else { + Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + ac.a = 0.5; + draw_rect(rect, ac); + if (locked_tracks.has(selected_track)) { + selected_track_color.set_hsv(h, 0.0, 0.4); + } else { + selected_track_color.set_hsv(h, 0.8, 0.8); + } + } + + Vector2 string_pos = Point2(margin, vofs); + text_buf.draw(get_canvas_item(), string_pos, cc); + + float icon_start_height = vofs + rect.size.y / 2; + Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height()); + draw_texture(remove, remove_rect.position); + + Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height()); + if (locked_tracks.has(current_track)) { + draw_texture(lock, lock_rect.position); + } else { + draw_texture(unlock, lock_rect.position); + } + + Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2, visible->get_width(), visible->get_height()); + if (hidden_tracks.has(current_track)) { + draw_texture(hidden, visible_rect.position); + } else { + draw_texture(visible, visible_rect.position); + } - Vector2 string_pos = Point2(margin, vofs + text_buf.get_line_ascent()); - text_buf.draw(get_canvas_item(), string_pos, cc); + Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2, solo->get_width(), solo->get_height()); + draw_texture(solo, solo_rect.position); - vofs += text_buf.get_size().y + vsep; + Map<int, Rect2> track_icons; + track_icons[REMOVE_ICON] = remove_rect; + track_icons[LOCK_ICON] = lock_rect; + track_icons[VISIBILITY_ICON] = visible_rect; + track_icons[SOLO_ICON] = solo_rect; + + subtrack_icons[current_track] = track_icons; + + vofs += text_buf.get_size().y + vsep; + } } Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); @@ -398,6 +481,9 @@ void AnimationBezierTrackEdit::_notification(int p_what) { float scale = timeline->get_zoom_scale(); Ref<Texture2D> point = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")); for (const KeyValue<int, Color> &E : subtrack_colors) { + if (hidden_tracks.has(E.key)) { + continue; + } _draw_track(E.key, E.value); for (int i = 0; i < animation->track_get_key_count(E.key); i++) { @@ -412,70 +498,116 @@ void AnimationBezierTrackEdit::_notification(int p_what) { } } - //draw edited curve - const Color highlight = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); - _draw_track(track, highlight); + if (track_count > 0 && !hidden_tracks.has(selected_track)) { + //draw edited curve + _draw_track(selected_track, selected_track_color); + } } //draw editor handles { edit_points.clear(); - float scale = timeline->get_zoom_scale(); - for (int i = 0; i < animation->track_get_key_count(track); i++) { - float offset = animation->track_get_key_time(track, i); - float value = animation->bezier_track_get_key_value(track, i); - if (moving_selection && selection.has(i)) { - offset += moving_selection_offset.x; - value += moving_selection_offset.y; + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) { + continue; } - Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); - - Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i); - if (moving_handle != 0 && moving_handle_key == i) { - in_vec = moving_handle_left; + if (hidden_tracks.has(i) || locked_tracks.has(i)) { + continue; } - Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); - Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i); + int key_count = animation->track_get_key_count(i); + String path = animation->track_get_path(i); - if (moving_handle != 0 && moving_handle_key == i) { - out_vec = moving_handle_right; + if (is_filtered) { + if (root && root->has_node(path)) { + Node *node = root->get_node(path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + } } - Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + for (int j = 0; j < key_count; ++j) { + float offset = animation->track_get_key_time(i, j); + float value = animation->bezier_track_get_key_value(i, j); + + if (moving_selection && selection.has(IntPair(i, j))) { + offset += moving_selection_offset.x; + value += moving_selection_offset.y; + } - _draw_line_clipped(pos, pos_in, accent, limit, right_limit); - _draw_line_clipped(pos, pos_out, accent, limit, right_limit); + Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); - EditPoint ep; - if (pos.x >= limit && pos.x <= right_limit) { - ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); - ep.point_rect.size = bezier_icon->get_size(); - if (selection.has(i)) { - draw_texture(selected_icon, ep.point_rect.position); - draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); - draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); - } else { - draw_texture(bezier_icon, ep.point_rect.position); + Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j); + if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + in_vec = moving_handle_left; + } + Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); + + Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j); + + if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + out_vec = moving_handle_right; + } + + Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + + if (i == selected_track || selection.has(IntPair(i, j))) { + _draw_line_clipped(pos, pos_in, accent, limit, right_limit); + _draw_line_clipped(pos, pos_out, accent, limit, right_limit); + } + + EditPoint ep; + ep.track = i; + ep.key = j; + if (pos.x >= limit && pos.x <= right_limit) { + ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); + ep.point_rect.size = bezier_icon->get_size(); + if (selection.has(IntPair(i, j))) { + draw_texture(selected_icon, ep.point_rect.position); + draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + } else { + Color track_color = Color(1, 1, 1, 1); + if (i != selected_track) { + track_color = subtrack_colors[i]; + } + draw_texture(bezier_icon, ep.point_rect.position, track_color); + } + ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); + } + if (i == selected_track || selection.has(IntPair(i, j))) { + if (pos_in.x >= limit && pos_in.x <= right_limit) { + ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); + ep.in_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.in_rect.position); + ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); + } + if (pos_out.x >= limit && pos_out.x <= right_limit) { + ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); + ep.out_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.out_rect.position); + ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } + } + if (!locked_tracks.has(i)) { + edit_points.push_back(ep); } - ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); - } - if (pos_in.x >= limit && pos_in.x <= right_limit) { - ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); - ep.in_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.in_rect.position); - ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); } - if (pos_out.x >= limit && pos_out.x <= right_limit) { - ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); - ep.out_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.out_rect.position); - ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } + + for (int i = 0; i < edit_points.size(); ++i) { + if (edit_points[i].track == selected_track) { + EditPoint ep = edit_points[i]; + edit_points.remove_at(i); + edit_points.insert(0, ep); } - edit_points.push_back(ep); } } @@ -506,15 +638,7 @@ Ref<Animation> AnimationBezierTrackEdit::get_animation() const { void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) { animation = p_animation; - track = p_track; - if (is_connected("select_key", Callable(editor, "_key_selected"))) { - disconnect("select_key", Callable(editor, "_key_selected")); - } - if (is_connected("deselect_key", Callable(editor, "_key_deselected"))) { - disconnect("deselect_key", Callable(editor, "_key_deselected")); - } - connect("select_key", Callable(editor, "_key_selected"), varray(p_track), CONNECT_DEFERRED); - connect("deselect_key", Callable(editor, "_key_deselected"), varray(p_track), CONNECT_DEFERRED); + selected_track = p_track; update(); } @@ -529,11 +653,14 @@ void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) { void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) { timeline = p_timeline; timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed)); + timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed)); } void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) { editor = p_editor; connect("clear_selection", Callable(editor, "_clear_selection"), varray(false)); + connect("select_key", Callable(editor, "_key_selected"), varray(), CONNECT_DEFERRED); + connect("deselect_key", Callable(editor, "_key_deselected"), varray(), CONNECT_DEFERRED); } void AnimationBezierTrackEdit::_play_position_draw() { @@ -544,9 +671,11 @@ void AnimationBezierTrackEdit::_play_position_draw() { float scale = timeline->get_zoom_scale(); int h = get_size().height; - int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit(); + int limit = timeline->get_name_limit(); + + int px = (-timeline->get_value() + play_position_pos) * scale + limit; - if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + if (px >= limit && px < (get_size().width)) { Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE)); } @@ -565,11 +694,84 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) { root = p_root; } +void AnimationBezierTrackEdit::set_filtered(bool p_filtered) { + is_filtered = p_filtered; + if (animation == nullptr) { + return; + } + String base_path = animation->track_get_path(selected_track); + if (is_filtered) { + if (root && root->has_node(base_path)) { + Node *node = root->get_node(base_path); + if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { + continue; + } + + base_path = animation->track_get_path(i); + if (root && root->has_node(base_path)) { + node = root->get_node(base_path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + + set_animation_and_track(animation, i); + break; + } + } + } + } + } + update(); +} + void AnimationBezierTrackEdit::_zoom_changed() { update(); play_position->update(); } +void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { + if (locked_tracks.has(p_track)) { + locked_tracks.erase(p_track); + } + + Vector<int> updated_locked_tracks; + for (Set<int>::Element *E = locked_tracks.front(); E; E = E->next()) { + updated_locked_tracks.push_back(E->get()); + } + locked_tracks.clear(); + for (int i = 0; i < updated_locked_tracks.size(); ++i) { + if (updated_locked_tracks[i] > p_track) { + locked_tracks.insert(updated_locked_tracks[i] - 1); + } else { + locked_tracks.insert(updated_locked_tracks[i]); + } + } +} + +void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) { + if (hidden_tracks.has(p_track)) { + hidden_tracks.erase(p_track); + } + + Vector<int> updated_hidden_tracks; + for (Set<int>::Element *E = hidden_tracks.front(); E; E = E->next()) { + updated_hidden_tracks.push_back(E->get()); + } + hidden_tracks.clear(); + for (int i = 0; i < updated_hidden_tracks.size(); ++i) { + if (updated_hidden_tracks[i] > p_track) { + hidden_tracks.insert(updated_hidden_tracks[i] - 1); + } else { + hidden_tracks.insert(updated_hidden_tracks[i]); + } + } +} + String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } @@ -583,10 +785,10 @@ void AnimationBezierTrackEdit::_clear_selection() { void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode) { undo_redo->create_action(TTR("Update Selected Key Handles")); double ratio = timeline->get_zoom_scale() * v_zoom; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - const int key_index = E->get(); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, animation->bezier_track_get_key_handle_mode(track, key_index), ratio); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, p_mode, ratio); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + const IntPair track_key_pair = E->get(); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, p_mode, ratio); } undo_redo->commit_action(); } @@ -606,8 +808,8 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int int idx = animation->track_find_key(p_track, p_pos, true); ERR_FAIL_COND(idx < 0); - selection.insert(idx); - emit_signal(SNAME("select_key"), idx, true); + selection.insert(IntPair(p_track, idx)); + emit_signal(SNAME("select_key"), p_track, idx, true); update(); } @@ -631,14 +833,100 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } } + Ref<InputEventKey> key_press = p_event; + + if (key_press.is_valid() && key_press->is_pressed()) { + if (ED_GET_SHORTCUT("animation_bezier_editor/focus")->matches_event(p_event)) { + SelectionSet focused_keys; + if (selection.is_empty()) { + for (int i = 0; i < edit_points.size(); ++i) { + IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key); + focused_keys.insert(key_pair); + } + } else { + for (SelectionSet::Element *E = selection.front(); E; E = E->next()) { + focused_keys.insert(E->get()); + if (E->get().second > 0) { + IntPair previous_key = IntPair(E->get().first, E->get().second - 1); + focused_keys.insert(previous_key); + } + if (E->get().second < animation->track_get_key_count(E->get().first) - 1) { + IntPair next_key = IntPair(E->get().first, E->get().second + 1); + focused_keys.insert(next_key); + } + } + } + if (focused_keys.is_empty()) { + accept_event(); + return; + } + + float minimum_time = INFINITY; + float maximum_time = -INFINITY; + float minimum_value = INFINITY; + float maximum_value = -INFINITY; + + for (SelectionSet::Element *E = focused_keys.front(); E; E = E->next()) { + IntPair key_pair = E->get(); + + float time = animation->track_get_key_time(key_pair.first, key_pair.second); + float value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second); + + minimum_time = MIN(time, minimum_time); + maximum_time = MAX(time, maximum_time); + minimum_value = MIN(value, minimum_value); + maximum_value = MAX(value, maximum_value); + } + + float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(); + float padding = width * 0.1; + float desired_scale = (width - padding / 2) / (maximum_time - minimum_time); + minimum_time = MAX(0, minimum_time - (padding / 2) / desired_scale); + + float zv = Math::pow(100 / desired_scale, 0.125f); + if (zv < 1) { + zv = Math::pow(desired_scale / 100, 0.125f) - 1; + zv = 1 - zv; + } + float zoom_value = timeline->get_zoom()->get_max() - zv; + + timeline->get_zoom()->set_value(zoom_value); + timeline->call_deferred("set_value", minimum_time); + + v_scroll = (maximum_value + minimum_value) / 2.0; + v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9); + + update(); + accept_event(); + return; + } else if (ED_GET_SHORTCUT("animation_bezier_editor/select_all_keys")->matches_event(p_event)) { + for (int i = 0; i < edit_points.size(); ++i) { + selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); + } + + update(); + accept_event(); + return; + } else if (ED_GET_SHORTCUT("animation_bezier_editor/deselect_all_keys")->matches_event(p_event)) { + selection.clear(); + + update(); + accept_event(); + return; + } + } + Ref<InputEventMouseButton> mb = p_event; + int limit = timeline->get_name_limit(); if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { menu_insert_key = mb->get_position(); - if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) { + if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) { Vector2 popup_pos = get_screen_position() + mb->get_position(); menu->clear(); - menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); + if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) { + menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); + } if (selection.size()) { menu->add_separator(); menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE); @@ -649,50 +937,163 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED); } - menu->set_as_minsize(); - menu->set_position(popup_pos); - menu->popup(); + if (menu->get_item_count()) { + menu->set_as_minsize(); + menu->set_position(popup_pos); + menu->popup(); + } } } if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { for (const KeyValue<int, Rect2> &E : subtracks) { if (E.value.has_point(mb->get_position())) { - set_animation_and_track(animation, E.key); - _clear_selection(); + if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) { + set_animation_and_track(animation, E.key); + _clear_selection(); + } return; } } + for (const KeyValue<int, Map<int, Rect2>> &E : subtrack_icons) { + int track = E.key; + Map<int, Rect2> track_icons = E.value; + for (const KeyValue<int, Rect2> &I : track_icons) { + if (I.value.has_point(mb->get_position())) { + if (I.key == REMOVE_ICON) { + undo_redo->create_action("Remove Bezier Track"); + + undo_redo->add_do_method(this, "_update_locked_tracks_after", track); + undo_redo->add_do_method(this, "_update_hidden_tracks_after", track); + + undo_redo->add_do_method(animation.ptr(), "remove_track", track); + + undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track); + undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track)); + + for (int i = 0; i < animation->track_get_key_count(track); ++i) { + undo_redo->add_undo_method( + animation.ptr(), + "bezier_track_insert_key", + track, animation->track_get_key_time(track, i), + animation->bezier_track_get_key_value(track, i), + animation->bezier_track_get_key_in_handle(track, i), + animation->bezier_track_get_key_out_handle(track, i), + animation->bezier_track_get_key_handle_mode(track, i)); + } + + undo_redo->commit_action(); + + selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1); + return; + } else if (I.key == LOCK_ICON) { + if (locked_tracks.has(track)) { + locked_tracks.erase(track); + } else { + locked_tracks.insert(track); + if (selected_track == track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i); + break; + } + } + } + } + update(); + return; + } else if (I.key == VISIBILITY_ICON) { + if (hidden_tracks.has(track)) { + hidden_tracks.erase(track); + } else { + hidden_tracks.insert(track); + if (selected_track == track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i); + break; + } + } + } + } + + Vector<int> visible_tracks; + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + visible_tracks.push_back(i); + } + } + + if (visible_tracks.size() == 1) { + solo_track = visible_tracks[0]; + } else { + solo_track = -1; + } + + update(); + return; + } else if (I.key == SOLO_ICON) { + if (solo_track == track) { + solo_track = -1; + + hidden_tracks.clear(); + } else { + if (hidden_tracks.has(track)) { + hidden_tracks.erase(track); + } + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + if (i != track && !hidden_tracks.has(i)) { + hidden_tracks.insert(i); + } + } + } + + set_animation_and_track(animation, track); + solo_track = track; + } + update(); + return; + } + return; + } + } + } + for (int i = 0; i < edit_points.size(); i++) { //first check point //command makes it ignore the main point, so control point editors can be force-edited //path 2D editing in the 3D and 2D editors works the same way if (!mb->is_command_pressed()) { if (edit_points[i].point_rect.has_point(mb->get_position())) { + IntPair pair = IntPair(edit_points[i].track, edit_points[i].key); if (mb->is_shift_pressed()) { //add to selection - if (selection.has(i)) { - selection.erase(i); + if (selection.has(pair)) { + selection.erase(pair); } else { - selection.insert(i); + selection.insert(pair); } update(); - select_single_attempt = -1; - } else if (selection.has(i)) { + select_single_attempt = IntPair(-1, -1); + } else if (selection.has(pair)) { moving_selection_attempt = true; moving_selection = false; - moving_selection_from_key = i; + moving_selection_from_key = pair.second; + moving_selection_from_track = pair.first; moving_selection_offset = Vector2(); - select_single_attempt = i; + select_single_attempt = pair; update(); } else { moving_selection_attempt = true; moving_selection = true; - moving_selection_from_key = i; + moving_selection_from_key = pair.second; + moving_selection_from_track = pair.first; moving_selection_offset = Vector2(); + set_animation_and_track(animation, pair.first); selection.clear(); - selection.insert(i); + selection.insert(pair); update(); } return; @@ -701,26 +1102,27 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (edit_points[i].in_rect.has_point(mb->get_position())) { moving_handle = -1; - moving_handle_key = i; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + moving_handle_key = edit_points[i].key; + moving_handle_track = edit_points[i].track; + moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); + moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key); update(); return; } if (edit_points[i].out_rect.has_point(mb->get_position())) { moving_handle = 1; - moving_handle_key = i; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + moving_handle_key = edit_points[i].key; + moving_handle_track = edit_points[i].track; + moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); + moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key); update(); return; - ; } } //insert new point - if (mb->is_command_pressed() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_pressed()) { Array new_point; new_point.resize(6); @@ -733,34 +1135,35 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { new_point[4] = 0; new_point[5] = 0; - float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - while (animation->track_find_key(track, time, true) != -1) { + float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + while (animation->track_find_key(selected_track, time, true) != -1) { time += 0.001; } undo_redo->create_action(TTR("Add Bezier Point")); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); undo_redo->commit_action(); //then attempt to move - int index = animation->track_find_key(track, time, true); + int index = animation->track_find_key(selected_track, time, true); ERR_FAIL_COND(index == -1); _clear_selection(); - selection.insert(index); + selection.insert(IntPair(selected_track, index)); moving_selection_attempt = true; moving_selection = false; moving_selection_from_key = index; + moving_selection_from_track = selected_track; moving_selection_offset = Vector2(); - select_single_attempt = -1; + select_single_attempt = IntPair(-1, -1); update(); return; } //box select - if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) { box_selecting_attempt = true; box_selecting = false; box_selecting_add = false; @@ -786,14 +1189,44 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } Rect2 selection_rect(bs_from, bs_to - bs_from); + bool track_set = false; for (int i = 0; i < edit_points.size(); i++) { if (edit_points[i].point_rect.intersects(selection_rect)) { - selection.insert(i); + selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); + if (!track_set) { + track_set = true; + set_animation_and_track(animation, edit_points[i].track); + } } } } else { _clear_selection(); //clicked and nothing happened, so clear the selection + + //select by clicking on curve + int track_count = animation->get_track_count(); + + float animation_length = animation->get_length(); + animation->set_length(real_t(INT_MAX)); //bezier_track_interpolate doesn't find keys if they exist beyond anim length + + float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) { + continue; + } + + float track_h = animation->bezier_track_interpolate(i, time); + float track_height = _bezier_h_to_pixel(track_h); + + if (abs(mb->get_position().y - track_height) < 10) { + set_animation_and_track(animation, i); + break; + } + } + + animation->set_length(animation_length); } + box_selecting_attempt = false; box_selecting = false; update(); @@ -801,10 +1234,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { undo_redo->create_action(TTR("Move Bezier Points")); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key)); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key)); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key)); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key)); undo_redo->commit_action(); moving_handle = 0; @@ -819,60 +1252,60 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { List<AnimMoveRestore> to_restore; // 1-remove the keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); } // 2- remove overlapped keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newtime = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); - int idx = animation->track_find_key(track, newtime, true); + int idx = animation->track_find_key(E->get().first, newtime, true); if (idx == -1) { continue; } - if (selection.has(idx)) { + if (selection.has(IntPair(E->get().first, idx))) { continue; //already in selection, don't save } - undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, newtime); + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime); AnimMoveRestore amr; - amr.key = animation->track_get_key_value(track, idx); - amr.track = track; + amr.key = animation->track_get_key_value(E->get().first, idx); + amr.track = E->get().first; amr.time = newtime; to_restore.push_back(amr); } // 3-move the keys (re insert them) - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); /* if (newpos<0) continue; //no add at the beginning */ - Array key = animation->track_get_key_value(track, E->get()); + Array key = animation->track_get_key_value(E->get().first, E->get().second); float h = key[0]; h += moving_selection_offset.y; key[0] = h; - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1); } // 4-(undo) remove inserted keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); /* if (newpos<0) continue; //no remove what no inserted */ - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, newpos); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos); } // 5-(undo) reinsert keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(track, E->get()); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float oldpos = animation->track_get_key_time(E->get().first, E->get().second); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1); } // 6-(undo) reinsert overlapped keys @@ -885,20 +1318,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { // 7-reselect - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float oldpos = animation->track_get_key_time(E->get().first, E->get().second); float newpos = editor->snap_time(oldpos + moving_selection_offset.x); - undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos); - undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos); + undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos); + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos); } undo_redo->commit_action(); moving_selection = false; - } else if (select_single_attempt != -1) { + } else if (select_single_attempt != IntPair(-1, -1)) { selection.clear(); selection.insert(select_single_attempt); + set_animation_and_track(animation, select_single_attempt.first); } moving_selection_attempt = false; @@ -909,13 +1343,13 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (moving_selection_attempt && mm.is_valid()) { if (!moving_selection) { moving_selection = true; - select_single_attempt = -1; + select_single_attempt = IntPair(-1, -1); } float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; - float x = editor->snap_time(((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value()); + float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value()); - moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key)); + moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key)); update(); } @@ -938,17 +1372,17 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; float x = editor->snap_time((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key)); + Vector2 key_pos = Vector2(animation->track_get_key_time(selected_track, moving_handle_key), animation->bezier_track_get_key_value(selected_track, moving_handle_key)); Vector2 moving_handle_value = Vector2(x, y) - key_pos; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key); + moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key); + moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key); if (moving_handle == -1) { moving_handle_left = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { + if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { double ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -961,7 +1395,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } else if (moving_handle == 1) { moving_handle_right = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { + if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { double ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -980,12 +1414,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { undo_redo->create_action(TTR("Move Bezier Points")); if (moving_handle == -1) { double ratio = timeline->get_zoom_scale() * v_zoom; - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left, ratio); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio); } else if (moving_handle == 1) { double ratio = timeline->get_zoom_scale() * v_zoom; - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right, ratio); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio); } undo_redo->commit_action(); @@ -1028,27 +1462,32 @@ void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_or void AnimationBezierTrackEdit::_menu_selected(int p_index) { switch (p_index) { case MENU_KEY_INSERT: { - Array new_point; - new_point.resize(6); + if (animation->get_track_count() > 0) { + Array new_point; + new_point.resize(6); - float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; + float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; - new_point[0] = h; - new_point[1] = -0.25; - new_point[2] = 0; - new_point[3] = 0.25; - new_point[4] = 0; - new_point[5] = Animation::HANDLE_MODE_BALANCED; + new_point[0] = h; + new_point[1] = -0.25; + new_point[2] = 0; + new_point[3] = 0.25; + new_point[4] = 0; + new_point[5] = Animation::HANDLE_MODE_BALANCED; - float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - while (animation->track_find_key(track, time, true) != -1) { - time += 0.001; - } + int limit = timeline->get_name_limit(); - undo_redo->create_action(TTR("Add Bezier Point")); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); - undo_redo->commit_action(); + float time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + + while (animation->track_find_key(selected_track, time, true) != -1) { + time += 0.001; + } + + undo_redo->create_action(TTR("Add Bezier Point")); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); + undo_redo->commit_action(); + } } break; case MENU_KEY_DUPLICATE: { @@ -1072,8 +1511,8 @@ void AnimationBezierTrackEdit::duplicate_selection() { } float top_time = 1e10; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float t = animation->track_get_key_time(E->get().first, E->get().second); if (t < top_time) { top_time = t; } @@ -1083,21 +1522,21 @@ void AnimationBezierTrackEdit::duplicate_selection() { List<Pair<int, float>> new_selection_values; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float t = animation->track_get_key_time(E->get().first, E->get().second); float dst_time = t + (timeline->get_play_position() - top_time); - int existing_idx = animation->track_find_key(track, dst_time, true); + int existing_idx = animation->track_find_key(E->get().first, dst_time, true); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get())); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, dst_time); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second)); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time); Pair<int, float> p; - p.first = track; + p.first = E->get().first; p.second = dst_time; new_selection_values.push_back(p); if (existing_idx != -1) { - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx)); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, existing_idx), animation->track_get_key_transition(E->get().first, existing_idx)); } } @@ -1116,7 +1555,7 @@ void AnimationBezierTrackEdit::duplicate_selection() { continue; } - selection.insert(existing_idx); + selection.insert(IntPair(track, existing_idx)); } update(); @@ -1126,9 +1565,9 @@ void AnimationBezierTrackEdit::delete_selection() { if (selection.size()) { undo_redo->create_action(TTR("Anim Delete Keys")); - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, animation->track_get_key_time(E->get().first, E->get().second), animation->track_get_key_value(E->get().first, E->get().second), 1); } undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); @@ -1142,12 +1581,14 @@ void AnimationBezierTrackEdit::_bind_methods() { ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection); ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim); ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim); + ClassDB::bind_method("_update_hidden_tracks_after", &AnimationBezierTrackEdit::_update_hidden_tracks_after); + ClassDB::bind_method("_update_locked_tracks_after", &AnimationBezierTrackEdit::_update_locked_tracks_after); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"))); ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "ofs"))); - ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); - ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); + ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"))); ADD_SIGNAL(MethodInfo("clear_selection")); ADD_SIGNAL(MethodInfo("close_request")); @@ -1170,14 +1611,9 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() { set_clip_contents(true); - close_button = memnew(Button); - close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request"))); - close_button->set_text(TTR("Close")); - - right_column = memnew(VBoxContainer); - right_column->add_child(close_button); - right_column->add_spacer(); - add_child(right_column); + ED_SHORTCUT("animation_bezier_editor/focus", TTR("Focus"), Key::F); + ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTR("Select All Keys"), KeyModifierMask::CMD | Key::A); + ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTR("Deselect All Keys"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::A); menu = memnew(PopupMenu); add_child(menu); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index cf719a0355..fa6fc405f2 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -46,9 +46,6 @@ class AnimationBezierTrackEdit : public Control { MENU_KEY_SET_HANDLE_BALANCED, }; - VBoxContainer *right_column; - Button *close_button; - AnimationTimelineEdit *timeline = nullptr; UndoRedo *undo_redo = nullptr; Node *root = nullptr; @@ -56,7 +53,7 @@ class AnimationBezierTrackEdit : public Control { float play_position_pos = 0; Ref<Animation> animation; - int track; + int selected_track; Vector<Rect2> view_rects; @@ -66,6 +63,19 @@ class AnimationBezierTrackEdit : public Control { Map<int, Rect2> subtracks; + enum { + REMOVE_ICON, + LOCK_ICON, + SOLO_ICON, + VISIBILITY_ICON + }; + + Map<int, Map<int, Rect2>> subtrack_icons; + Set<int> locked_tracks; + Set<int> hidden_tracks; + int solo_track = -1; + bool is_filtered = false; + float v_scroll = 0; float v_zoom = 1; @@ -73,6 +83,9 @@ class AnimationBezierTrackEdit : public Control { void _zoom_changed(); + void _update_locked_tracks_after(int p_track); + void _update_hidden_tracks_after(int p_track); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _menu_selected(int p_index); @@ -80,10 +93,13 @@ class AnimationBezierTrackEdit : public Control { Vector2 insert_at_pos; + typedef Pair<int, int> IntPair; + bool moving_selection_attempt = false; - int select_single_attempt = -1; + IntPair select_single_attempt; bool moving_selection = false; int moving_selection_from_key; + int moving_selection_from_track; Vector2 moving_selection_offset; @@ -95,6 +111,7 @@ class AnimationBezierTrackEdit : public Control { int moving_handle = 0; //0 no move -1 or +1 out int moving_handle_key = 0; + int moving_handle_track = 0; Vector2 moving_handle_left; Vector2 moving_handle_right; int moving_handle_mode; // value from Animation::HandleMode @@ -119,11 +136,25 @@ class AnimationBezierTrackEdit : public Control { Rect2 point_rect; Rect2 in_rect; Rect2 out_rect; + int track; + int key; }; Vector<EditPoint> edit_points; - Set<int> selection; + struct SelectionCompare { + bool operator()(const IntPair &lh, const IntPair &rh) { + if (lh.first == rh.first) { + return lh.second < rh.second; + } else { + return lh.first < rh.first; + } + } + }; + + typedef Set<IntPair, SelectionCompare> SelectionSet; + + SelectionSet selection; Ref<ViewPanner> panner; void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); @@ -151,6 +182,7 @@ public: void set_timeline(AnimationTimelineEdit *p_timeline); void set_editor(AnimationTrackEditor *p_editor); void set_root(Node *p_root); + void set_filtered(bool p_filtered); void set_play_position(float p_pos); void update_play_position(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index adb91fb08d..ba5cdb2896 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2118,23 +2118,19 @@ void AnimationTrackEdit::_notification(int p_what) { update_mode_rect.position.y = 0; update_mode_rect.size.y = get_size().height; - ofs += update_icon->get_width() + hsep; - update_mode_rect.size.x += hsep; + ofs += update_icon->get_width() + hsep / 2; + update_mode_rect.size.x += hsep / 2; if (animation->track_get_type(track) == Animation::TYPE_VALUE) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect = Rect2(); } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")); update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2; - bezier_edit_rect.size = bezier_icon->get_size(); - draw_texture(bezier_icon, bezier_edit_rect.position); + update_mode_rect = Rect2(); } else { update_mode_rect = Rect2(); - bezier_edit_rect = Rect2(); } ofs += down_icon->get_width(); @@ -2160,8 +2156,8 @@ void AnimationTrackEdit::_notification(int p_what) { interp_mode_rect.position.y = 0; interp_mode_rect.size.y = get_size().height; - ofs += icon->get_width() + hsep; - interp_mode_rect.size.x += hsep; + ofs += icon->get_width() + hsep / 2; + interp_mode_rect.size.x += hsep / 2; if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2193,8 +2189,8 @@ void AnimationTrackEdit::_notification(int p_what) { loop_wrap_rect.position.y = 0; loop_wrap_rect.size.y = get_size().height; - ofs += icon->get_width() + hsep; - loop_wrap_rect.size.x += hsep; + ofs += icon->get_width() + hsep / 2; + loop_wrap_rect.size.x += hsep / 2; if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2213,7 +2209,7 @@ void AnimationTrackEdit::_notification(int p_what) { Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons")); - remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2; + remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()); remove_rect.position.y = int(get_size().height - icon->get_height()) / 2; remove_rect.size = icon->get_size(); @@ -2792,11 +2788,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (bezier_edit_rect.has_point(pos)) { - emit_signal(SNAME("bezier_edit")); - accept_event(); - } - // Check keyframes. if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible. @@ -3326,10 +3317,21 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { snap->set_disabled(false); snap_mode->set_disabled(false); + bezier_edit_icon->set_disabled(true); + imported_anim_warning->hide(); + bool import_warning_done = false; + bool bezier_done = false; for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_is_imported(i)) { imported_anim_warning->show(); + import_warning_done = true; + } + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + bezier_edit_icon->set_disabled(false); + bezier_done = true; + } + if (import_warning_done && bezier_done) { break; } } @@ -3343,6 +3345,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { step->set_read_only(true); snap->set_disabled(true); snap_mode->set_disabled(true); + bezier_edit_icon->set_disabled(true); } } @@ -4167,13 +4170,15 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD } break; case Animation::TYPE_BEZIER: { Array array; - array.resize(5); + array.resize(6); array[0] = p_id.value; array[1] = -0.25; array[2] = 0; array[3] = 0.25; array[4] = 0; + array[5] = Animation::HANDLE_MODE_BALANCED; value = array; + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_ANIMATION: { @@ -4399,7 +4404,6 @@ void AnimationTrackEditor::_update_tracks() { track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track), varray(i), CONNECT_DEFERRED); track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected), varray(i), CONNECT_DEFERRED); track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected), varray(i), CONNECT_DEFERRED); - track_edit->connect("bezier_edit", callable_mp(this, &AnimationTrackEditor::_bezier_edit), varray(i), CONNECT_DEFERRED); track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin)); track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection)); track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit)); @@ -4515,6 +4519,7 @@ void AnimationTrackEditor::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); + bezier_edit_icon->set_icon(get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"))); snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); @@ -4630,6 +4635,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { adding_track_path = path_to; prop_selector->set_type_filter(filter); prop_selector->select_property_from_instance(node); + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_AUDIO: { if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) { @@ -5294,6 +5300,20 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { } } +void AnimationTrackEditor::_toggle_bezier_edit() { + if (bezier_edit->is_visible()) { + _cancel_bezier_edit(); + } else { + int track_count = animation->get_track_count(); + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + _bezier_edit(i); + return; + } + } + } +} + void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { if (p_alt) { if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) { @@ -5322,6 +5342,7 @@ void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin void AnimationTrackEditor::_cancel_bezier_edit() { bezier_edit->hide(); scroll->show(); + bezier_edit_icon->set_pressed(false); } void AnimationTrackEditor::_bezier_edit(int p_for_track) { @@ -5908,6 +5929,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { void AnimationTrackEditor::_view_group_toggle() { _update_tracks(); view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); + bezier_edit->set_filtered(selected_filter->is_pressed()); } bool AnimationTrackEditor::is_grouping_tracks() { @@ -6153,6 +6175,15 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_spacer(); + bezier_edit_icon = memnew(Button); + bezier_edit_icon->set_flat(true); + bezier_edit_icon->set_disabled(true); + bezier_edit_icon->set_toggle_mode(true); + bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit)); + bezier_edit_icon->set_tooltip(TTR("Toggle between the bezier curve editor and track editor.")); + + bottom_hb->add_child(bezier_edit_icon); + selected_filter = memnew(Button); selected_filter->set_flat(true); selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 50c5c692c0..627c7c1d97 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -164,7 +164,6 @@ class AnimationTrackEdit : public Control { Rect2 interp_mode_rect; Rect2 loop_wrap_rect; Rect2 remove_rect; - Rect2 bezier_edit_rect; Ref<Texture2D> type_icon; Ref<Texture2D> selected_icon; @@ -300,6 +299,7 @@ class AnimationTrackEditor : public VBoxContainer { EditorSpinSlider *step; TextureRect *zoom_icon; Button *snap; + Button *bezier_edit_icon; OptionButton *snap_mode; Button *imported_anim_warning; @@ -431,6 +431,7 @@ class AnimationTrackEditor : public VBoxContainer { Vector<Ref<AnimationTrackEditPlugin>> track_edit_plugins; + void _toggle_bezier_edit(); void _cancel_bezier_edit(); void _bezier_edit(int p_for_track); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index cbfd6ae6de..d94f1d80e5 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1181,6 +1181,15 @@ void EditorInspectorSection::_notification(int p_what) { header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + inspector_margin += indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + inspector_margin += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + Size2 size = get_size() - Vector2(inspector_margin, 0); Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); for (int i = 0; i < get_child_count(); i++) { @@ -1216,14 +1225,31 @@ void EditorInspectorSection::_notification(int p_what) { bool rtl = is_layout_rtl(); - // Compute the height of the section header. + // Compute the height and width of the section header. int header_height = font->get_height(font_size); if (arrow.is_valid()) { header_height = MAX(header_height, arrow->get_height()); } header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - Rect2 header_rect = Rect2(Vector2(), Vector2(get_size().width, header_height)); + int section_indent = 0; + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + section_indent = indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + section_indent += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + + int header_width = get_size().width - section_indent; + int header_offset_x = 0.0; + if (!rtl) { + header_offset_x += section_indent; + } + + // Draw header area. + Rect2 header_rect = Rect2(Vector2(header_offset_x, 0.0), Vector2(header_width, header_height)); Color c = bg_color; c.a *= 0.4; if (foldable && header_rect.has_point(get_local_mouse_position())) { @@ -1231,24 +1257,46 @@ void EditorInspectorSection::_notification(int p_what) { } draw_rect(header_rect, c); + // Draw header title and folding arrow. const int arrow_margin = 2; const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); - draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT, text_width, font_size, color); + float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE) - section_indent; + Point2 text_offset = Point2(0, font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2); + HorizontalAlignment text_align = HORIZONTAL_ALIGNMENT_LEFT; + if (rtl) { + text_align = HORIZONTAL_ALIGNMENT_RIGHT; + } else { + text_offset.x = section_indent + Math::round(arrow_width + arrow_margin * EDSCALE); + } + draw_string(font, text_offset.floor(), label, text_align, text_width, font_size, color); if (arrow.is_valid()) { + Point2 arrow_position = Point2(0, (header_height - arrow->get_height()) / 2); if (rtl) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + arrow_position.x = get_size().width - section_indent - arrow->get_width() - Math::round(arrow_margin * EDSCALE); } else { - draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + arrow_position.x = section_indent + Math::round(arrow_margin * EDSCALE); } + draw_texture(arrow, arrow_position.floor()); } + // Draw dropping highlight. if (dropping && !vbox->is_visible_in_tree()) { Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); draw_rect(Rect2(Point2(), get_size()), accent_color, false); } + + // Draw section indentation. + if (section_indent_style.is_valid() && section_indent > 0) { + Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height)); + if (rtl) { + indent_rect.position.x = get_size().width - section_indent + section_indent_style->get_margin(SIDE_RIGHT); + } else { + indent_rect.position.x = section_indent_style->get_margin(SIDE_LEFT); + } + draw_style_box(section_indent_style, indent_rect); + } } break; case NOTIFICATION_DRAG_BEGIN: { Dictionary dd = get_viewport()->gui_get_drag_data(); @@ -1311,15 +1359,25 @@ Size2 EditorInspectorSection::get_minimum_size() const { ms.height += font->get_height(font_size) + get_theme_constant(SNAME("vseparation"), SNAME("Tree")); ms.width += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + ms.width += indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + ms.width += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + return ms; } -void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable) { +void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth) { section = p_section; label = p_label; object = p_object; bg_color = p_bg_color; foldable = p_foldable; + indent_depth = p_indent_depth; if (!foldable && !vbox_added) { add_child(vbox); @@ -1401,12 +1459,8 @@ void EditorInspectorSection::_bind_methods() { } EditorInspectorSection::EditorInspectorSection() { - object = nullptr; - foldable = false; vbox = memnew(VBoxContainer); - vbox_added = false; - dropping = false; dropping_unfold_timer = memnew(Timer); dropping_unfold_timer->set_wait_time(0.6); dropping_unfold_timer->set_one_shot(true); @@ -1422,6 +1476,7 @@ EditorInspectorSection::~EditorInspectorSection() { //////////////////////////////////////////////// //////////////////////////////////////////////// + int EditorInspectorArray::_get_array_count() { if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { List<PropertyInfo> object_property_list; @@ -1440,31 +1495,8 @@ void EditorInspectorArray::_add_button_pressed() { _move_element(-1, -1); } -void EditorInspectorArray::_first_page_button_pressed() { - emit_signal(SNAME("page_change_request"), 0); -} - -void EditorInspectorArray::_prev_page_button_pressed() { - emit_signal(SNAME("page_change_request"), MAX(0, page - 1)); -} - -void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) { - if (p_text.is_valid_int()) { - int new_page = p_text.to_int() - 1; - new_page = MIN(MAX(0, new_page), max_page); - page_line_edit->set_text(Variant(new_page)); - emit_signal(SNAME("page_change_request"), new_page); - } else { - page_line_edit->set_text(Variant(page)); - } -} - -void EditorInspectorArray::_next_page_button_pressed() { - emit_signal(SNAME("page_change_request"), MIN(max_page, page + 1)); -} - -void EditorInspectorArray::_last_page_button_pressed() { - emit_signal(SNAME("page_change_request"), max_page); +void EditorInspectorArray::_paginator_page_changed(int p_page) { + emit_signal("page_change_request", p_page); } void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) { @@ -1636,17 +1668,17 @@ void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { // Handle page change and update counts. if (p_element_index < 0) { int added_index = p_to_pos < 0 ? count : p_to_pos; - emit_signal(SNAME("page_change_request"), added_index / page_lenght); + emit_signal(SNAME("page_change_request"), added_index / page_length); count += 1; } else if (p_to_pos < 0) { count -= 1; - if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) { + if (page == max_page && (MAX(0, count - 1) / page_length != max_page)) { emit_signal(SNAME("page_change_request"), max_page - 1); } } - begin_array_index = page * page_lenght; - end_array_index = MIN(count, (page + 1) * page_lenght); - max_page = MAX(0, count - 1) / page_lenght; + begin_array_index = page * page_length; + end_array_index = MIN(count, (page + 1) * page_length); + max_page = MAX(0, count - 1) / page_length; } void EditorInspectorArray::_clear_array() { @@ -1860,9 +1892,9 @@ void EditorInspectorArray::_resize_dialog_confirmed() { void EditorInspectorArray::_setup() { // Setup counts. count = _get_array_count(); - begin_array_index = page * page_lenght; - end_array_index = MIN(count, (page + 1) * page_lenght); - max_page = MAX(0, count - 1) / page_lenght; + begin_array_index = page * page_length; + end_array_index = MIN(count, (page + 1) * page_length); + max_page = MAX(0, count - 1) / page_length; array_elements.resize(MAX(0, end_array_index - begin_array_index)); if (page < 0 || page > max_page) { WARN_PRINT(vformat("Invalid page number %d", page)); @@ -1920,18 +1952,12 @@ void EditorInspectorArray::_setup() { // Hide/show the add button. add_button->set_visible(page == max_page); - if (max_page == 0) { - hbox_pagination->hide(); - } else { - // Update buttons. - first_page_button->set_disabled(page == 0); - prev_page_button->set_disabled(page == 0); - next_page_button->set_disabled(page == max_page); - last_page_button->set_disabled(page == max_page); - - // Update page number and page count. - page_line_edit->set_text(vformat("%d", page + 1)); - page_count_label->set_text(vformat("/ %d", max_page + 1)); + // Add paginator if there's more than 1 page. + if (max_page > 0) { + EditorPaginator *paginator = memnew(EditorPaginator); + paginator->update(page, max_page); + paginator->connect("page_changed", callable_mp(this, &EditorInspectorArray::_paginator_page_changed)); + vbox->add_child(paginator); } } @@ -1996,10 +2022,6 @@ void EditorInspectorArray::_notification(int p_what) { } add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); - prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); - next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); - last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); update_minimum_size(); } break; case NOTIFICATION_DRAG_BEGIN: { @@ -2036,7 +2058,7 @@ void EditorInspectorArray::setup_with_move_element_function(Object *p_object, St array_element_prefix = p_array_element_prefix; page = p_page; - EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable); + EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable, 0); _setup(); } @@ -2047,7 +2069,7 @@ void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_ array_element_prefix = p_array_element_prefix; page = p_page; - EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable); + EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable, 0); _setup(); } @@ -2092,38 +2114,6 @@ EditorInspectorArray::EditorInspectorArray() { add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed)); vbox->add_child(add_button); - hbox_pagination = memnew(HBoxContainer); - hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL); - hbox_pagination->set_alignment(BoxContainer::ALIGNMENT_CENTER); - vbox->add_child(hbox_pagination); - - first_page_button = memnew(Button); - first_page_button->set_flat(true); - first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed)); - hbox_pagination->add_child(first_page_button); - - prev_page_button = memnew(Button); - prev_page_button->set_flat(true); - prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed)); - hbox_pagination->add_child(prev_page_button); - - page_line_edit = memnew(LineEdit); - page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted)); - page_line_edit->add_theme_constant_override("minimum_character_width", 2); - hbox_pagination->add_child(page_line_edit); - - page_count_label = memnew(Label); - hbox_pagination->add_child(page_count_label); - next_page_button = memnew(Button); - next_page_button->set_flat(true); - next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed)); - hbox_pagination->add_child(next_page_button); - - last_page_button = memnew(Button); - last_page_button->set_flat(true); - last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed)); - hbox_pagination->add_child(last_page_button); - control_dropping = memnew(Control); control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -2149,6 +2139,97 @@ EditorInspectorArray::EditorInspectorArray() { //////////////////////////////////////////////// //////////////////////////////////////////////// +void EditorPaginator::_first_page_button_pressed() { + emit_signal("page_changed", 0); +} + +void EditorPaginator::_prev_page_button_pressed() { + emit_signal("page_changed", MAX(0, page - 1)); +} + +void EditorPaginator::_page_line_edit_text_submitted(String p_text) { + if (p_text.is_valid_int()) { + int new_page = p_text.to_int() - 1; + new_page = MIN(MAX(0, new_page), max_page); + page_line_edit->set_text(Variant(new_page)); + emit_signal("page_changed", new_page); + } else { + page_line_edit->set_text(Variant(page)); + } +} + +void EditorPaginator::_next_page_button_pressed() { + emit_signal("page_changed", MIN(max_page, page + 1)); +} + +void EditorPaginator::_last_page_button_pressed() { + emit_signal("page_changed", max_page); +} + +void EditorPaginator::update(int p_page, int p_max_page) { + page = p_page; + max_page = p_max_page; + + // Update buttons. + first_page_button->set_disabled(page == 0); + prev_page_button->set_disabled(page == 0); + next_page_button->set_disabled(page == max_page); + last_page_button->set_disabled(page == max_page); + + // Update page number and page count. + page_line_edit->set_text(vformat("%d", page + 1)); + page_count_label->set_text(vformat("/ %d", max_page + 1)); +} + +void EditorPaginator::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); + prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); + next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); + last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); + } +} + +void EditorPaginator::_bind_methods() { + ADD_SIGNAL(MethodInfo("page_changed", PropertyInfo(Variant::INT, "page"))); +} + +EditorPaginator::EditorPaginator() { + set_h_size_flags(SIZE_EXPAND_FILL); + set_alignment(ALIGNMENT_CENTER); + + first_page_button = memnew(Button); + first_page_button->set_flat(true); + first_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_first_page_button_pressed)); + add_child(first_page_button); + + prev_page_button = memnew(Button); + prev_page_button->set_flat(true); + prev_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_prev_page_button_pressed)); + add_child(prev_page_button); + + page_line_edit = memnew(LineEdit); + page_line_edit->connect("text_submitted", callable_mp(this, &EditorPaginator::_page_line_edit_text_submitted)); + page_line_edit->add_theme_constant_override("minimum_character_width", 2); + add_child(page_line_edit); + + page_count_label = memnew(Label); + add_child(page_count_label); + + next_page_button = memnew(Button); + next_page_button->set_flat(true); + next_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_next_page_button_pressed)); + add_child(next_page_button); + + last_page_button = memnew(Button); + last_page_button->set_flat(true); + last_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_last_page_button_pressed)); + add_child(last_page_button); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; @@ -2349,6 +2430,7 @@ void EditorInspector::update_tree() { String group_base; String subgroup; String subgroup_base; + int section_depth = 0; VBoxContainer *category_vbox = nullptr; List<PropertyInfo> plist; @@ -2373,14 +2455,29 @@ void EditorInspector::update_tree() { if (p.usage & PROPERTY_USAGE_SUBGROUP) { // Setup a property sub-group. subgroup = p.name; - subgroup_base = p.hint_string; + + Vector<String> hint_parts = p.hint_string.split(","); + subgroup_base = hint_parts[0]; + if (hint_parts.size() > 1) { + section_depth = hint_parts[1].to_int(); + } else { + section_depth = 0; + } continue; } else if (p.usage & PROPERTY_USAGE_GROUP) { // Setup a property group. group = p.name; - group_base = p.hint_string; + + Vector<String> hint_parts = p.hint_string.split(","); + group_base = hint_parts[0]; + if (hint_parts.size() > 1) { + section_depth = hint_parts[1].to_int(); + } else { + section_depth = 0; + } + subgroup = ""; subgroup_base = ""; @@ -2392,6 +2489,7 @@ void EditorInspector::update_tree() { group_base = ""; subgroup = ""; subgroup_base = ""; + section_depth = 0; if (!show_categories) { continue; @@ -2633,7 +2731,7 @@ void EditorInspector::update_tree() { Color c = sscolor; c.a /= level; - section->setup(acc_path, component, object, c, use_folding); + section->setup(acc_path, component, object, c, use_folding, section_depth); // Add editors at the start of a group. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 09b25065dc..43f71740e3 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -261,17 +261,18 @@ class EditorInspectorSection : public Container { String label; String section; - bool vbox_added; // Optimization. + bool vbox_added = false; // Optimization. Color bg_color; - bool foldable; + bool foldable = false; + int indent_depth = 0; Timer *dropping_unfold_timer; - bool dropping; + bool dropping = false; void _test_unfold(); protected: - Object *object; + Object *object = nullptr; VBoxContainer *vbox; void _notification(int p_what); @@ -281,7 +282,7 @@ protected: public: virtual Size2 get_minimum_size() const override; - void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable); + void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0); VBoxContainer *get_vbox(); void unfold(); void fold(); @@ -317,18 +318,11 @@ class EditorInspectorArray : public EditorInspectorSection { LineEdit *new_size_line_edit; // Pagination - int page_lenght = 5; + int page_length = 5; int page = 0; int max_page = 0; int begin_array_index = 0; int end_array_index = 0; - HBoxContainer *hbox_pagination; - Button *first_page_button; - Button *prev_page_button; - LineEdit *page_line_edit; - Label *page_count_label; - Button *next_page_button; - Button *last_page_button; enum MenuOptions { OPTION_MOVE_UP = 0, @@ -356,12 +350,7 @@ class EditorInspectorArray : public EditorInspectorSection { int _get_array_count(); void _add_button_pressed(); - - void _first_page_button_pressed(); - void _prev_page_button_pressed(); - void _page_line_edit_text_submitted(String p_text); - void _next_page_button_pressed(); - void _last_page_button_pressed(); + void _paginator_page_changed(int p_page); void _rmb_popup_id_pressed(int p_id); @@ -402,6 +391,34 @@ public: EditorInspectorArray(); }; +class EditorPaginator : public HBoxContainer { + GDCLASS(EditorPaginator, HBoxContainer); + + int page = 0; + int max_page = 0; + Button *first_page_button; + Button *prev_page_button; + LineEdit *page_line_edit; + Label *page_count_label; + Button *next_page_button; + Button *last_page_button; + + void _first_page_button_pressed(); + void _prev_page_button_pressed(); + void _page_line_edit_text_submitted(String p_text); + void _next_page_button_pressed(); + void _last_page_button_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void update(int p_page, int p_max_page); + + EditorPaginator(); +}; + class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4b2f1c5104..cf5af38a07 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -132,6 +132,7 @@ #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/collision_polygon_2d_editor_plugin.h" #include "editor/plugins/collision_shape_2d_editor_plugin.h" +#include "editor/plugins/control_editor_plugin.h" #include "editor/plugins/cpu_particles_2d_editor_plugin.h" #include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" @@ -7024,6 +7025,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(InputEventEditorPlugin(this))); add_editor_plugin(memnew(SubViewportPreviewEditorPlugin(this))); add_editor_plugin(memnew(TextControlEditorPlugin(this))); + add_editor_plugin(memnew(ControlEditorPlugin(this))); for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) { add_editor_plugin(EditorPlugins::create(i, this)); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index c28c7db2cb..fc0c0e7d7b 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -259,8 +259,8 @@ void EditorPropertyArray::update_property() { } int size = array.call("size"); - int pages = MAX(0, size - 1) / page_length + 1; - page_index = MIN(page_index, pages - 1); + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); int offset = page_index * page_length; edit->set_text(arrtype + " (size " + itos(size) + ")"); @@ -292,35 +292,37 @@ void EditorPropertyArray::update_property() { size_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_length_changed)); hbox->add_child(size_slider); - page_hbox = memnew(HBoxContainer); - vbox->add_child(page_hbox); + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); - label = memnew(Label(TTR("Page: "))); - label->set_h_size_flags(SIZE_EXPAND_FILL); - page_hbox->add_child(label); + button_add_item = memnew(Button); + button_add_item->set_text(TTR("Add Element")); + button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + button_add_item->connect(SNAME("pressed"), callable_mp(this, &EditorPropertyArray::_add_element)); + vbox->add_child(button_add_item); - page_slider = memnew(EditorSpinSlider); - page_slider->set_step(1); - page_slider->set_h_size_flags(SIZE_EXPAND_FILL); - page_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_page_changed)); - page_hbox->add_child(page_slider); + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed)); + vbox->add_child(paginator); } else { // Bye bye children of the box. - for (int i = vbox->get_child_count() - 1; i >= 2; i--) { - Node *child = vbox->get_child(i); + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + Node *child = property_vbox->get_child(i); if (child == reorder_selected_element_hbox) { continue; // Don't remove the property that the user is moving. } child->queue_delete(); // Button still needed after pressed is called. - vbox->remove_child(child); + property_vbox->remove_child(child); } } size_slider->set_value(size); - page_slider->set_max(pages); - page_slider->set_value(page_index); - page_hbox->set_visible(pages > 1); + property_vbox->set_visible(size > 0); + button_add_item->set_visible(page_index == max_page); + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); if (array.get_type() == Variant::ARRAY) { array = array.call("duplicate"); @@ -343,7 +345,7 @@ void EditorPropertyArray::update_property() { } HBoxContainer *hbox = memnew(HBoxContainer); - vbox->add_child(hbox); + property_vbox->add_child(hbox); Button *reorder_button = memnew(Button); reorder_button->set_icon(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); @@ -397,7 +399,7 @@ void EditorPropertyArray::update_property() { } if (reorder_to_index % page_length > 0) { - vbox->move_child(vbox->get_child(2), reorder_to_index % page_length + 2); + property_vbox->move_child(property_vbox->get_child(0), reorder_to_index % page_length); } updating = false; @@ -510,6 +512,10 @@ void EditorPropertyArray::_notification(int p_what) { } change_type->add_separator(); change_type->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Remove Item"), Variant::VARIANT_MAX); + + if (Object::cast_to<Button>(button_add_item)) { + button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } } if (p_what == NOTIFICATION_DRAG_BEGIN) { @@ -542,7 +548,7 @@ void EditorPropertyArray::_edit_pressed() { update_property(); } -void EditorPropertyArray::_page_changed(double p_page) { +void EditorPropertyArray::_page_changed(int p_page) { if (updating) { return; } @@ -589,6 +595,10 @@ void EditorPropertyArray::_length_changed(double p_page) { update_property(); } +void EditorPropertyArray::_add_element() { + _length_changed(double(object->get_array().call("size")) + 1.0); +} + void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) { array_type = p_array_type; @@ -633,9 +643,9 @@ void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_eve reorder_to_index += direction; if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) { // Automatically move to the next/previous page. - page_slider->set_value(page_index + direction); + _page_changed(page_index + direction); } - vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length + 2); + property_vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length); // Ensure the moving element is visible. InspectorDock::get_inspector_singleton()->ensure_control_visible(reorder_selected_element_hbox); } @@ -645,7 +655,7 @@ void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_eve void EditorPropertyArray::_reorder_button_down(int p_index) { reorder_from_index = p_index; reorder_to_index = p_index; - reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(vbox->get_child(p_index % page_length + 2)); + reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(property_vbox->get_child(p_index % page_length)); reorder_selected_button = Object::cast_to<Button>(reorder_selected_element_hbox->get_child(0)); // Ideally it'd to be able to show the mouse but I had issues with // Control's `mouse_exit()`/`mouse_entered()` signals not getting called. @@ -685,6 +695,7 @@ void EditorPropertyArray::_bind_methods() { EditorPropertyArray::EditorPropertyArray() { object.instantiate(); page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + edit = memnew(Button); edit->set_h_size_flags(SIZE_EXPAND_FILL); edit->set_clip_text(true); @@ -694,9 +705,12 @@ EditorPropertyArray::EditorPropertyArray() { edit->connect("draw", callable_mp(this, &EditorPropertyArray::_button_draw)); add_child(edit); add_focusable(edit); + vbox = nullptr; - page_slider = nullptr; + property_vbox = nullptr; size_slider = nullptr; + button_add_item = nullptr; + paginator = nullptr; updating = false; change_type = memnew(PopupMenu); add_child(change_type); @@ -824,35 +838,32 @@ void EditorPropertyDictionary::update_property() { add_child(vbox); set_bottom_editor(vbox); - page_hbox = memnew(HBoxContainer); - vbox->add_child(page_hbox); - Label *label = memnew(Label(TTR("Page: "))); - label->set_h_size_flags(SIZE_EXPAND_FILL); - page_hbox->add_child(label); - page_slider = memnew(EditorSpinSlider); - page_slider->set_step(1); - page_hbox->add_child(page_slider); - page_slider->set_h_size_flags(SIZE_EXPAND_FILL); - page_slider->connect("value_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed)); + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); + + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed)); + vbox->add_child(paginator); } else { // Queue children for deletion, deleting immediately might cause errors. - for (int i = 1; i < vbox->get_child_count(); i++) { - vbox->get_child(i)->queue_delete(); + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + property_vbox->get_child(i)->queue_delete(); } } int size = dict.size(); - int pages = MAX(0, size - 1) / page_length + 1; + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); - page_slider->set_max(pages); - page_index = MIN(page_index, pages - 1); - page_slider->set_value(page_index); - page_hbox->set_visible(pages > 1); + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); int offset = page_index * page_length; int amount = MIN(size - offset, page_length); + int total_amount = page_index == max_page ? amount + 2 : amount; // For the "Add Key/Value Pair" box on last page. dict = dict.duplicate(); @@ -860,7 +871,7 @@ void EditorPropertyDictionary::update_property() { VBoxContainer *add_vbox = nullptr; double default_float_step = EDITOR_GET("interface/inspector/default_float_step"); - for (int i = 0; i < amount + 2; i++) { + for (int i = 0; i < total_amount; i++) { String prop_name; Variant key; Variant value; @@ -1074,15 +1085,9 @@ void EditorPropertyDictionary::update_property() { if (i == amount) { PanelContainer *pc = memnew(PanelContainer); - vbox->add_child(pc); - Ref<StyleBoxFlat> flat; - flat.instantiate(); - for (int j = 0; j < 4; j++) { - flat->set_default_margin(Side(j), 2 * EDSCALE); - } - flat->set_bg_color(get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); + property_vbox->add_child(pc); + pc->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("DictionaryAddItem"), SNAME("EditorStyles"))); - pc->add_theme_style_override("panel", flat); add_vbox = memnew(VBoxContainer); pc->add_child(add_vbox); } @@ -1110,7 +1115,7 @@ void EditorPropertyDictionary::update_property() { if (add_vbox) { add_vbox->add_child(hbox); } else { - vbox->add_child(hbox); + property_vbox->add_child(hbox); } hbox->add_child(prop); prop->set_h_size_flags(SIZE_EXPAND_FILL); @@ -1173,7 +1178,7 @@ void EditorPropertyDictionary::_edit_pressed() { update_property(); } -void EditorPropertyDictionary::_page_changed(double p_page) { +void EditorPropertyDictionary::_page_changed(int p_page) { if (updating) { return; } @@ -1187,6 +1192,7 @@ void EditorPropertyDictionary::_bind_methods() { EditorPropertyDictionary::EditorPropertyDictionary() { object.instantiate(); page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + edit = memnew(Button); edit->set_h_size_flags(SIZE_EXPAND_FILL); edit->set_clip_text(true); @@ -1194,9 +1200,10 @@ EditorPropertyDictionary::EditorPropertyDictionary() { edit->set_toggle_mode(true); add_child(edit); add_focusable(edit); + vbox = nullptr; - page_slider = nullptr; button_add_item = nullptr; + paginator = nullptr; updating = false; change_type = memnew(PopupMenu); add_child(change_type); diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index bb10cdf97e..292de6d6db 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -89,9 +89,10 @@ class EditorPropertyArray : public EditorProperty { int changing_type_index; Button *edit; VBoxContainer *vbox; + VBoxContainer *property_vbox; EditorSpinSlider *size_slider; - EditorSpinSlider *page_slider; - HBoxContainer *page_hbox; + Button *button_add_item; + EditorPaginator *paginator; Variant::Type array_type; Variant::Type subtype; PropertyHint subtype_hint; @@ -103,8 +104,9 @@ class EditorPropertyArray : public EditorProperty { HBoxContainer *reorder_selected_element_hbox = nullptr; Button *reorder_selected_button = nullptr; - void _page_changed(double p_page); + void _page_changed(int p_page); void _length_changed(double p_page); + void _add_element(); void _edit_pressed(); void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); void _change_type(Object *p_button, int p_index); @@ -144,12 +146,12 @@ class EditorPropertyDictionary : public EditorProperty { int changing_type_index; Button *edit; VBoxContainer *vbox; + VBoxContainer *property_vbox; EditorSpinSlider *size_slider; - EditorSpinSlider *page_slider; - HBoxContainer *page_hbox; Button *button_add_item; + EditorPaginator *paginator; - void _page_changed(double p_page); + void _page_changed(int p_page); void _edit_pressed(); void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); void _change_type(Object *p_button, int p_index); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1e373239a6..6d3dcd6bc9 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -159,7 +159,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#ffffff", "#414141"); // Pure white ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#000000", "#bfbfbf"); // Pure black - // Keep pure RGB colors as is, but list them for explicity. + // Keep pure RGB colors as is, but list them for explicitly. ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#ff0000", "#ff0000"); // Pure red ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#00ff00", "#00ff00"); // Pure green ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#0000ff", "#0000ff"); // Pure blue @@ -466,6 +466,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("font_color", "Editor", font_color); theme->set_color("highlighted_font_color", "Editor", font_hover_color); theme->set_color("disabled_font_color", "Editor", font_disabled_color); + theme->set_color("readonly_font_color", "Editor", font_readonly_color); theme->set_color("mono_color", "Editor", mono_color); @@ -875,9 +876,21 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("readonly_color", "EditorProperty", readonly_color); theme->set_color("readonly_warning_color", "EditorProperty", readonly_warning_color); + Ref<StyleBoxFlat> style_property_group_note = style_default->duplicate(); + Color property_group_note_color = accent_color; + property_group_note_color.a = 0.1; + style_property_group_note->set_bg_color(property_group_note_color); + theme->set_stylebox("bg_group_note", "EditorProperty", style_property_group_note); + Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35); theme->set_color("font_color", "EditorInspectorSection", inspector_section_color); + Color inspector_indent_color = accent_color; + inspector_indent_color.a = 0.2; + Ref<StyleBoxFlat> inspector_indent_style = make_flat_stylebox(inspector_indent_color, 2.0 * EDSCALE, 0, 2.0 * EDSCALE, 0); + theme->set_stylebox("indent_box", "EditorInspectorSection", inspector_indent_style); + theme->set_constant("indent_size", "EditorInspectorSection", 6.0 * EDSCALE); + theme->set_constant("inspector_margin", "Editor", 12 * EDSCALE); // Tree & ItemList background @@ -1501,6 +1514,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Ref<StyleBoxFlat> theme_preview_picker_label_sb = make_flat_stylebox(theme_preview_picker_label_bg_color, 4.0, 1.0, 4.0, 3.0); theme->set_stylebox("preview_picker_label", "ThemeEditor", theme_preview_picker_label_sb); + // Dictionary editor add item. + theme->set_stylebox("DictionaryAddItem", "EditorStyles", make_flat_stylebox(prop_subsection_color, 4, 4, 4, 4, corner_radius)); + // adaptive script theme constants // for comments and elements with lower relevance const Color dim_color = Color(font_color.r, font_color.g, font_color.b, 0.5); diff --git a/editor/icons/ControlAlignBottomCenter.svg b/editor/icons/ControlAlignCenterBottom.svg index ca7f0c2e01..ca7f0c2e01 100644 --- a/editor/icons/ControlAlignBottomCenter.svg +++ b/editor/icons/ControlAlignCenterBottom.svg diff --git a/editor/icons/ControlAlignLeftCenter.svg b/editor/icons/ControlAlignCenterLeft.svg index 612c36b4d6..612c36b4d6 100644 --- a/editor/icons/ControlAlignLeftCenter.svg +++ b/editor/icons/ControlAlignCenterLeft.svg diff --git a/editor/icons/ControlAlignRightCenter.svg b/editor/icons/ControlAlignCenterRight.svg index 43f8618c80..43f8618c80 100644 --- a/editor/icons/ControlAlignRightCenter.svg +++ b/editor/icons/ControlAlignCenterRight.svg diff --git a/editor/icons/ControlAlignTopCenter.svg b/editor/icons/ControlAlignCenterTop.svg index dca9c84ce6..dca9c84ce6 100644 --- a/editor/icons/ControlAlignTopCenter.svg +++ b/editor/icons/ControlAlignCenterTop.svg diff --git a/editor/icons/ControlHcenterWide.svg b/editor/icons/ControlAlignHCenterWide.svg index af3f9b495b..af3f9b495b 100644 --- a/editor/icons/ControlHcenterWide.svg +++ b/editor/icons/ControlAlignHCenterWide.svg diff --git a/editor/icons/ControlVcenterWide.svg b/editor/icons/ControlAlignVCenterWide.svg index decd1cbd12..decd1cbd12 100644 --- a/editor/icons/ControlVcenterWide.svg +++ b/editor/icons/ControlAlignVCenterWide.svg diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index aa8ad55ff3..a3ae0d9623 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -747,11 +747,11 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po return still_selected; } -List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { +List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) { List<CanvasItem *> selection; for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { + if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { selection.push_back(canvas_item); @@ -1831,7 +1831,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { Point2 drag_to_local = simple_xform.xform(drag_to); Point2 offset = drag_to_local - drag_from_local; - Size2 scale = canvas_item->call("get_scale"); + Size2 scale = canvas_item->_edit_get_scale(); Size2 original_scale = scale; real_t ratio = scale.y / scale.x; if (drag_type == DRAG_SCALE_BOTH) { @@ -1869,7 +1869,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } } - canvas_item->call("set_scale", scale); + canvas_item->_edit_set_scale(scale); return true; } @@ -3696,8 +3696,6 @@ void CanvasItemEditor::_notification(int p_what) { if (p_what == NOTIFICATION_PHYSICS_PROCESS) { EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); - bool has_container_parents = false; - int nb_control = 0; int nb_having_pivot = 0; // Update the viewport if the canvas_item changes @@ -3738,11 +3736,6 @@ void CanvasItemEditor::_notification(int p_what) { se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; viewport->update(); } - nb_control++; - - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; - } } if (canvas_item->_edit_use_pivot()) { @@ -3753,28 +3746,6 @@ void CanvasItemEditor::_notification(int p_what) { // Activate / Deactivate the pivot tool pivot_button->set_disabled(nb_having_pivot == 0); - // Show / Hide the layout and anchors mode buttons - if (nb_control > 0 && nb_control == selection.size()) { - presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); - - // Disable if the selected node is child of a container - if (has_container_parents) { - presets_menu->set_disabled(true); - presets_menu->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - anchor_mode_button->set_disabled(true); - anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - } else { - presets_menu->set_disabled(false); - presets_menu->set_tooltip(TTR("Presets for the anchors and margins values of a Control node.")); - anchor_mode_button->set_disabled(false); - anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their margins.")); - } - } else { - presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } - // Update the viewport if bones changes for (KeyValue<BoneKey, BoneList> &E : bone_list) { Object *b = ObjectDB::get_instance(E.key.from); @@ -3852,58 +3823,6 @@ void CanvasItemEditor::_notification(int p_what) { _update_context_menu_stylebox(); - presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = presets_menu->get_popup(); - - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); - p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->add_separator(); - p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); - - anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); pan_speed = int(EditorSettings::get_singleton()->get("editors/panning/2d_editor_pan_speed")); warped_panning = bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")); @@ -3920,27 +3839,6 @@ void CanvasItemEditor::_notification(int p_what) { } void CanvasItemEditor::_selection_changed() { - // Update the anchors_mode - int nbValidControls = 0; - int nbAnchorsMode = 0; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control) { - continue; - } - if (Object::cast_to<Container>(control->get_parent())) { - continue; - } - - nbValidControls++; - if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { - nbAnchorsMode++; - } - } - anchors_mode = (nbValidControls == nbAnchorsMode); - anchor_mode_button->set_pressed(anchors_mode); - if (!selected_from_canvas) { drag_type = DRAG_NONE; } @@ -4072,90 +3970,6 @@ void CanvasItemEditor::_update_scroll(real_t) { viewport->update(); } -void CanvasItemEditor::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_LEFT: - case PRESET_CENTER_TOP: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); - break; - case PRESET_LEFT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_WIDE: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); - break; - } - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); - - anchors_mode = false; - anchor_mode_button->set_pressed(anchors_mode); -} - -void CanvasItemEditor::_set_anchors_and_offsets_to_keep_ratio() { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - Point2 top_left_anchor = _position_to_anchor(control, Point2()); - Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); - undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); - undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); - - bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); - - anchors_mode = true; - anchor_mode_button->set_pressed(anchors_mode); - } - } - - undo_redo->commit_action(); -} - -void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); -} - void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) { p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); @@ -4298,21 +4112,6 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, } } -void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) { - List<CanvasItem *> selection = _get_edited_canvas_items(false, false); - for (CanvasItem *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control || Object::cast_to<Container>(control->get_parent())) { - continue; - } - - control->set_meta("_edit_use_anchors_", p_status); - } - - anchors_mode = p_status; - viewport->update(); -} - void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { if (p_game_running) { override_camera_button->set_disabled(false); @@ -4534,106 +4333,6 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_undo_method(viewport, "update", Variant()); undo_redo->commit_action(); } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { - _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { - _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { - _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER: { - _set_anchors_and_offsets_preset(PRESET_CENTER); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { - _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_WIDE: { - _set_anchors_and_offsets_preset(Control::PRESET_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { - _set_anchors_and_offsets_to_keep_ratio(); - } break; - - case ANCHORS_PRESET_TOP_LEFT: { - _set_anchors_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_PRESET_TOP_RIGHT: { - _set_anchors_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_PRESET_BOTTOM_LEFT: { - _set_anchors_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_PRESET_BOTTOM_RIGHT: { - _set_anchors_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_LEFT: { - _set_anchors_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_PRESET_CENTER_RIGHT: { - _set_anchors_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_TOP: { - _set_anchors_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_PRESET_CENTER_BOTTOM: { - _set_anchors_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_PRESET_CENTER: { - _set_anchors_preset(PRESET_CENTER); - } break; - case ANCHORS_PRESET_TOP_WIDE: { - _set_anchors_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_PRESET_LEFT_WIDE: { - _set_anchors_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_PRESET_RIGHT_WIDE: { - _set_anchors_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_PRESET_BOTTOM_WIDE: { - _set_anchors_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_PRESET_VCENTER_WIDE: { - _set_anchors_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_PRESET_HCENTER_WIDE: { - _set_anchors_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_PRESET_WIDE: { - _set_anchors_preset(Control::PRESET_WIDE); - } break; case ANIM_INSERT_KEY: case ANIM_INSERT_KEY_EXISTING: { @@ -5128,63 +4827,21 @@ void CanvasItemEditor::focus_selection() { } CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { - key_pos = true; - key_rot = true; - key_scale = false; - - show_grid = false; - show_origin = true; - show_viewport = true; - show_helpers = false; - show_rulers = true; - show_guides = true; - show_transformation_gizmos = true; - show_edit_locks = true; zoom = 1.0 / MAX(1, EDSCALE); view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH); previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen + grid_offset = Point2(); grid_step = Point2(8, 8); // A power-of-two value works better as a default primary_grid_steps = 8; // A power-of-two value works better as a default grid_step_multiplier = 0; + snap_rotation_offset = 0; snap_rotation_step = Math::deg2rad(15.0); snap_scale_step = 0.1f; - smart_snap_active = false; - grid_snap_active = false; - snap_node_parent = true; - snap_node_anchors = true; - snap_node_sides = true; - snap_node_center = true; - snap_other_nodes = true; - snap_guides = true; - snap_rotation = false; - snap_scale = false; - snap_relative = false; - // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. - // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. - snap_pixel = true; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - selected_from_canvas = false; - anchors_mode = false; - - drag_type = DRAG_NONE; - drag_from = Vector2(); - drag_to = Vector2(); - dragged_guide_pos = Point2(); - dragged_guide_index = -1; - is_hovering_h_guide = false; - is_hovering_v_guide = false; - pan_pressed = false; - - ruler_tool_active = false; - ruler_tool_origin = Point2(); - - bone_last_frame = 0; - - tool = TOOL_SELECT; undo_redo = p_editor->get_undo_redo(); editor = p_editor; editor_selection = p_editor->get_editor_selection(); @@ -5261,8 +4918,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { viewport->add_child(controls_vb); - updating_scroll = false; - // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. @@ -5492,28 +5147,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(context_menu_container); _update_context_menu_stylebox(); - presets_menu = memnew(MenuButton); - presets_menu->set_shortcut_context(this); - presets_menu->set_text(TTR("Layout")); - hbc_context_menu->add_child(presets_menu); - presets_menu->hide(); - presets_menu->set_switch_on_hover(true); - - p = presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - - anchors_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - - anchor_mode_button = memnew(Button); - anchor_mode_button->set_flat(true); - hbc_context_menu->add_child(anchor_mode_button); - anchor_mode_button->set_toggle_mode(true); - anchor_mode_button->hide(); - anchor_mode_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_anchor_mode)); - + // Animation controls. animation_hb = memnew(HBoxContainer); hbc_context_menu->add_child(animation_hb); animation_hb->add_child(memnew(VSeparator)); @@ -5599,6 +5233,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); + + // Store the singleton instance. singleton = this; // To ensure that scripts can parse the list of shortcuts correctly, we have to define @@ -5955,8 +5591,6 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian continue; } memdelete(instantiated_scene); - } else { - continue; } can_instantiate = true; break; diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 9fa44bfb25..73829b8abe 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CONTROL_EDITOR_PLUGIN_H -#define CONTROL_EDITOR_PLUGIN_H +#ifndef CANVAS_ITEM_EDITOR_PLUGIN_H +#define CANVAS_ITEM_EDITOR_PLUGIN_H #include "editor/editor_node.h" #include "editor/editor_plugin.h" @@ -39,6 +39,7 @@ #include "scene/gui/label.h" #include "scene/gui/panel_container.h" #include "scene/gui/spin_box.h" +#include "scene/gui/texture_rect.h" #include "scene/main/canvas_item.h" class CanvasItemEditorViewport; @@ -128,55 +129,6 @@ private: UNLOCK_SELECTED, GROUP_SELECTED, UNGROUP_SELECTED, - ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, - ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, - ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, - ANCHORS_AND_OFFSETS_PRESET_CENTER, - ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, - ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, - ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_WIDE, - ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, - ANCHORS_PRESET_TOP_LEFT, - ANCHORS_PRESET_TOP_RIGHT, - ANCHORS_PRESET_BOTTOM_LEFT, - ANCHORS_PRESET_BOTTOM_RIGHT, - ANCHORS_PRESET_CENTER_LEFT, - ANCHORS_PRESET_CENTER_RIGHT, - ANCHORS_PRESET_CENTER_TOP, - ANCHORS_PRESET_CENTER_BOTTOM, - ANCHORS_PRESET_CENTER, - ANCHORS_PRESET_TOP_WIDE, - ANCHORS_PRESET_LEFT_WIDE, - ANCHORS_PRESET_RIGHT_WIDE, - ANCHORS_PRESET_BOTTOM_WIDE, - ANCHORS_PRESET_VCENTER_WIDE, - ANCHORS_PRESET_HCENTER_WIDE, - ANCHORS_PRESET_WIDE, - OFFSETS_PRESET_TOP_LEFT, - OFFSETS_PRESET_TOP_RIGHT, - OFFSETS_PRESET_BOTTOM_LEFT, - OFFSETS_PRESET_BOTTOM_RIGHT, - OFFSETS_PRESET_CENTER_LEFT, - OFFSETS_PRESET_CENTER_RIGHT, - OFFSETS_PRESET_CENTER_TOP, - OFFSETS_PRESET_CENTER_BOTTOM, - OFFSETS_PRESET_CENTER, - OFFSETS_PRESET_TOP_WIDE, - OFFSETS_PRESET_LEFT_WIDE, - OFFSETS_PRESET_RIGHT_WIDE, - OFFSETS_PRESET_BOTTOM_WIDE, - OFFSETS_PRESET_VCENTER_WIDE, - OFFSETS_PRESET_HCENTER_WIDE, - OFFSETS_PRESET_WIDE, ANIM_INSERT_KEY, ANIM_INSERT_KEY_EXISTING, ANIM_INSERT_POS, @@ -226,7 +178,7 @@ private: bool selection_menu_additive_selection; - Tool tool; + Tool tool = TOOL_SELECT; Control *viewport; Control *viewport_scrollable; @@ -239,21 +191,20 @@ private: HBoxContainer *hbc_context_menu; Transform2D transform; - bool show_grid; - bool show_rulers; - bool show_guides; - bool show_origin; - bool show_viewport; - bool show_helpers; - bool show_edit_locks; - bool show_transformation_gizmos; + bool show_grid = false; + bool show_rulers = true; + bool show_guides = true; + bool show_origin = true; + bool show_viewport = true; + bool show_helpers = false; + bool show_edit_locks = true; + bool show_transformation_gizmos = true; real_t zoom; Point2 view_offset; Point2 previous_update_view_offset; - bool selected_from_canvas; - bool anchors_mode; + bool selected_from_canvas = false; Point2 grid_offset; Point2 grid_step; @@ -263,26 +214,30 @@ private: real_t snap_rotation_step; real_t snap_rotation_offset; real_t snap_scale_step; - bool smart_snap_active; - bool grid_snap_active; - - bool snap_node_parent; - bool snap_node_anchors; - bool snap_node_sides; - bool snap_node_center; - bool snap_other_nodes; - bool snap_guides; - bool snap_rotation; - bool snap_scale; - bool snap_relative; - bool snap_pixel; - bool key_pos; - bool key_rot; - bool key_scale; - bool pan_pressed; - - bool ruler_tool_active; - Point2 ruler_tool_origin; + bool smart_snap_active = false; + bool grid_snap_active = false; + + bool snap_node_parent = true; + bool snap_node_anchors = true; + bool snap_node_sides = true; + bool snap_node_center = true; + bool snap_other_nodes = true; + bool snap_guides = true; + bool snap_rotation = false; + bool snap_scale = false; + bool snap_relative = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + bool snap_pixel = true; + + bool key_pos = true; + bool key_rot = true; + bool key_scale = false; + + bool pan_pressed = false; + + bool ruler_tool_active = false; + Point2 ruler_tool_origin = Point2(); Point2 node_create_position; MenuOption last_option; @@ -310,7 +265,7 @@ private: uint64_t last_pass = 0; }; - uint64_t bone_last_frame; + uint64_t bone_last_frame = 0; struct BoneKey { ObjectID from; @@ -363,12 +318,6 @@ private: HBoxContainer *animation_hb; MenuButton *animation_menu; - MenuButton *presets_menu; - PopupMenu *anchors_and_margins_popup; - PopupMenu *anchors_popup; - - Button *anchor_mode_button; - Button *key_loc_button; Button *key_rot_button; Button *key_scale_button; @@ -382,15 +331,15 @@ private: Control *left_ruler; Point2 drag_start_origin; - DragType drag_type; - Point2 drag_from; - Point2 drag_to; + DragType drag_type = DRAG_NONE; + Point2 drag_from = Vector2(); + Point2 drag_to = Vector2(); Point2 drag_rotation_center; List<CanvasItem *> drag_selection; - int dragged_guide_index; - Point2 dragged_guide_pos; - bool is_hovering_h_guide; - bool is_hovering_v_guide; + int dragged_guide_index = -1; + Point2 dragged_guide_pos = Point2(); + bool is_hovering_h_guide = false; + bool is_hovering_v_guide = false; bool updating_value_dialog; @@ -432,7 +381,7 @@ private: Vector2 _position_to_anchor(const Control *p_control, Vector2 position); void _popup_callback(int p_op); - bool updating_scroll; + bool updating_scroll = false; void _update_scroll(real_t); void _update_scrollbars(); void _snap_changed(); @@ -444,7 +393,7 @@ private: UndoRedo *undo_redo; - List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); + List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true); Rect2 _get_encompassing_rect(const Node *p_node); @@ -518,12 +467,6 @@ private: const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, const Node *p_current); - void _set_anchors_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_to_keep_ratio(); - - void _button_toggle_anchor_mode(bool p_status); - VBoxContainer *controls_vb; EditorZoomWidget *zoom_widget; void _update_zoom(real_t p_zoom); @@ -602,8 +545,6 @@ public: void focus_selection(); - bool is_anchors_mode_enabled() { return anchors_mode; }; - EditorSelection *editor_selection; CanvasItemEditor(EditorNode *p_editor); @@ -683,4 +624,4 @@ public: ~CanvasItemEditorViewport(); }; -#endif +#endif //CANVAS_ITEM_EDITOR_PLUGIN_H diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp new file mode 100644 index 0000000000..c4ea098e92 --- /dev/null +++ b/editor/plugins/control_editor_plugin.cpp @@ -0,0 +1,1023 @@ +/*************************************************************************/ +/* control_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "control_editor_plugin.h" + +#include "editor/plugins/canvas_item_editor_plugin.h" + +void ControlPositioningWarning::_update_warning() { + if (!control_node) { + title_icon->set_texture(nullptr); + title_label->set_text(""); + hint_label->set_text(""); + return; + } + + Node *parent_node = control_node->get_parent_control(); + if (!parent_node) { + title_icon->set_texture(get_theme_icon(SNAME("SubViewport"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node doesn't have a control parent.")); + hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it.")); + } else if (Object::cast_to<Container>(parent_node)) { + title_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a container.")); + hint_label->set_text(TTR("Use container properties for positioning.")); + } else { + title_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a regular control.")); + hint_label->set_text(TTR("Use anchors and the rectangle for positioning.")); + } + + bg_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg_group_note"), SNAME("EditorProperty"))); +} + +void ControlPositioningWarning::_update_toggler() { + Ref<Texture2D> arrow; + if (hint_label->is_visible()) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); + set_tooltip(TTR("Collapse positioning hint.")); + } else { + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } + set_tooltip(TTR("Expand positioning hint.")); + } + + hint_icon->set_texture(arrow); +} + +void ControlPositioningWarning::set_control(Control *p_node) { + control_node = p_node; + _update_warning(); +} + +void ControlPositioningWarning::gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + bool state = !hint_label->is_visible(); + + hint_filler_left->set_visible(state); + hint_label->set_visible(state); + hint_filler_right->set_visible(state); + + _update_toggler(); + } +} + +void ControlPositioningWarning::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + _update_warning(); + _update_toggler(); + break; + } +} + +ControlPositioningWarning::ControlPositioningWarning() { + set_mouse_filter(MOUSE_FILTER_STOP); + + bg_panel = memnew(PanelContainer); + bg_panel->set_mouse_filter(MOUSE_FILTER_IGNORE); + add_child(bg_panel); + + grid = memnew(GridContainer); + grid->set_columns(3); + bg_panel->add_child(grid); + + title_icon = memnew(TextureRect); + title_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(title_icon); + + title_label = memnew(Label); + title_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + title_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + grid->add_child(title_label); + + hint_icon = memnew(TextureRect); + hint_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(hint_icon); + + // Filler. + hint_filler_left = memnew(Control); + hint_filler_left->hide(); + grid->add_child(hint_filler_left); + + hint_label = memnew(Label); + hint_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + hint_label->hide(); + grid->add_child(hint_label); + + // Filler. + hint_filler_right = memnew(Control); + hint_filler_right->hide(); + grid->add_child(hint_filler_right); +} + +void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + +void EditorPropertyAnchorsPreset::_option_selected(int p_which) { + int64_t val = options->get_item_metadata(p_which); + emit_changed(get_edited_property(), val); +} + +void EditorPropertyAnchorsPreset::update_property() { + int64_t which = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < options->get_item_count(); i++) { + Variant val = options->get_item_metadata(i); + if (val != Variant() && which == (int64_t)val) { + options->select(i); + return; + } + } +} + +void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { + options->clear(); + + Vector<String> split_after; + split_after.append("Custom"); + split_after.append("PresetWide"); + split_after.append("PresetBottomLeft"); + split_after.append("PresetCenter"); + + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + + String humanized_name = text_split[0]; + if (humanized_name.begins_with("Preset")) { + if (humanized_name == "PresetWide") { + humanized_name = "Full Rect"; + } else { + humanized_name = humanized_name.trim_prefix("Preset"); + humanized_name = humanized_name.capitalize(); + } + + String icon_name = text_split[0].trim_prefix("Preset"); + icon_name = "ControlAlign" + icon_name; + options->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(icon_name, "EditorIcons"), humanized_name); + } else { + options->add_item(humanized_name); + } + + options->set_item_metadata(j, current_val); + if (split_after.has(text_split[0])) { + options->add_separator(); + j++; + } + } +} + +EditorPropertyAnchorsPreset::EditorPropertyAnchorsPreset() { + options = memnew(OptionButton); + options->set_clip_text(true); + options->set_flat(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", callable_mp(this, &EditorPropertyAnchorsPreset::_option_selected)); +} + +void EditorPropertySizeFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flag_checks) { + check->set_disabled(p_read_only); + } + flag_presets->set_disabled(p_read_only); +}; + +void EditorPropertySizeFlags::_preset_selected(int p_which) { + int preset = flag_presets->get_item_id(p_which); + if (preset == SIZE_FLAGS_PRESET_CUSTOM) { + flag_options->set_visible(true); + return; + } + flag_options->set_visible(false); + + uint32_t value = 0; + switch (preset) { + case SIZE_FLAGS_PRESET_FILL: + value = Control::SIZE_FILL; + break; + case SIZE_FLAGS_PRESET_SHRINK_BEGIN: + value = Control::SIZE_SHRINK_BEGIN; + break; + case SIZE_FLAGS_PRESET_SHRINK_CENTER: + value = Control::SIZE_SHRINK_CENTER; + break; + case SIZE_FLAGS_PRESET_SHRINK_END: + value = Control::SIZE_SHRINK_END; + break; + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_expand_toggled() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + if (flag_expand->is_visible() && flag_expand->is_pressed()) { + value |= Control::SIZE_EXPAND; + } else { + value ^= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_flag_toggled() { + uint32_t value = 0; + for (int i = 0; i < flag_checks.size(); i++) { + if (flag_checks[i]->is_pressed()) { + int flag_value = flag_checks[i]->get_meta("_value"); + value |= flag_value; + } + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::update_property() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flag_checks.size(); i++) { + int flag_value = flag_checks[i]->get_meta("_value"); + if (value & flag_value) { + flag_checks[i]->set_pressed(true); + } else { + flag_checks[i]->set_pressed(false); + } + } + + bool is_expand = value & Control::SIZE_EXPAND; + flag_expand->set_pressed(is_expand); + + if (keep_selected_preset) { + keep_selected_preset = false; + return; + } + + FlagPreset preset = SIZE_FLAGS_PRESET_CUSTOM; + if (value == Control::SIZE_FILL || value == (Control::SIZE_FILL | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_FILL; + } else if (value == Control::SIZE_SHRINK_BEGIN || value == (Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_BEGIN; + } else if (value == Control::SIZE_SHRINK_CENTER || value == (Control::SIZE_SHRINK_CENTER | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_CENTER; + } else if (value == Control::SIZE_SHRINK_END || value == (Control::SIZE_SHRINK_END | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_END; + } + + int preset_idx = flag_presets->get_item_index(preset); + if (preset_idx >= 0) { + flag_presets->select(preset_idx); + } + flag_options->set_visible(preset == SIZE_FLAGS_PRESET_CUSTOM); +} + +void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vertical) { + vertical = p_vertical; + + if (p_options.size() == 0) { + flag_presets->clear(); + flag_presets->add_item(TTR("Container Default")); + flag_presets->set_disabled(true); + flag_expand->set_visible(false); + return; + } + + Map<int, String> flags; + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + flags[current_val] = text_split[0]; + + if (current_val == SIZE_EXPAND) { + continue; + } + + CheckBox *cb = memnew(CheckBox); + cb->set_text(text_split[0]); + cb->set_clip_text(true); + cb->set_meta("_value", current_val); + cb->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_flag_toggled)); + add_focusable(cb); + + flag_options->add_child(cb); + flag_checks.append(cb); + } + + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + String wide_preset_icon = SNAME("ControlAlignHCenterWide"); + if (vertical) { + wide_preset_icon = SNAME("ControlAlignVCenterWide"); + } + + flag_presets->clear(); + if (flags.has(SIZE_FILL)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(wide_preset_icon, SNAME("EditorIcons")), TTR("Fill"), SIZE_FLAGS_PRESET_FILL); + } + // Shrink Begin is the same as no flags at all, as such it cannot be disabled. + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), SIZE_FLAGS_PRESET_SHRINK_BEGIN); + if (flags.has(SIZE_SHRINK_CENTER)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), SIZE_FLAGS_PRESET_SHRINK_CENTER); + } + if (flags.has(SIZE_SHRINK_END)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), SIZE_FLAGS_PRESET_SHRINK_END); + } + flag_presets->add_separator(); + flag_presets->add_item(TTR("Custom"), SIZE_FLAGS_PRESET_CUSTOM); + + flag_expand->set_visible(flags.has(SIZE_EXPAND)); +} + +EditorPropertySizeFlags::EditorPropertySizeFlags() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + + flag_presets = memnew(OptionButton); + flag_presets->set_clip_text(true); + flag_presets->set_flat(true); + vb->add_child(flag_presets); + add_focusable(flag_presets); + set_label_reference(flag_presets); + flag_presets->connect("item_selected", callable_mp(this, &EditorPropertySizeFlags::_preset_selected)); + + flag_options = memnew(VBoxContainer); + flag_options->hide(); + vb->add_child(flag_options); + + flag_expand = memnew(CheckBox); + flag_expand->set_text(TTR("Expand")); + vb->add_child(flag_expand); + add_focusable(flag_expand); + flag_expand->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_expand_toggled)); +} + +bool EditorInspectorPluginControl::can_handle(Object *p_object) { + return Object::cast_to<Control>(p_object) != nullptr; +} + +void EditorInspectorPluginControl::parse_group(Object *p_object, const String &p_group) { + Control *control = Object::cast_to<Control>(p_object); + if (!control || p_group != "Layout") { + return; + } + + ControlPositioningWarning *pos_warning = memnew(ControlPositioningWarning); + pos_warning->set_control(control); + add_custom_control(pos_warning); +} + +bool EditorInspectorPluginControl::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { + Control *control = Object::cast_to<Control>(p_object); + if (!control) { + return false; + } + + if (p_path == "anchors_preset") { + EditorPropertyAnchorsPreset *prop_editor = memnew(EditorPropertyAnchorsPreset); + Vector<String> options = p_hint_text.split(","); + prop_editor->setup(options); + add_property_editor(p_path, prop_editor); + + return true; + } + + if (p_path == "size_flags_horizontal" || p_path == "size_flags_vertical") { + EditorPropertySizeFlags *prop_editor = memnew(EditorPropertySizeFlags); + Vector<String> options; + if (!p_hint_text.is_empty()) { + options = p_hint_text.split(","); + } + prop_editor->setup(options, p_path == "size_flags_vertical"); + add_property_editor(p_path, prop_editor); + + return true; + } + + return false; +} + +void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_LEFT: + case PRESET_CENTER_TOP: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); + break; + case PRESET_LEFT_WIDE: + case PRESET_TOP_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); + break; + } + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); + + anchors_mode = false; + anchor_mode_button->set_pressed(anchors_mode); +} + +void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + Point2 top_left_anchor = _position_to_anchor(control, Point2()); + Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); + undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); + undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); + + bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); + + anchors_mode = true; + anchor_mode_button->set_pressed(anchors_mode); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_container_h_preset(Control::SizeFlags p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Horizontal Size Flags")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_h_size_flags", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_container_v_preset(Control::SizeFlags p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Horizontal Size Flags")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_v_size_flags", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +Vector2 ControlEditorToolbar::_anchor_to_position(const Control *p_control, Vector2 anchor) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Transform2D parent_transform = p_control->get_transform().affine_inverse(); + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + if (p_control->is_layout_rtl()) { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } else { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } +} + +Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vector2 position) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + Vector2 output = Vector2(); + if (p_control->is_layout_rtl()) { + output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } else { + output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } + output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y; + return output; +} + +void ControlEditorToolbar::_button_toggle_anchor_mode(bool p_status) { + List<Control *> selection = _get_edited_controls(false, false); + for (Control *E : selection) { + if (Object::cast_to<Container>(E->get_parent())) { + continue; + } + + E->set_meta("_edit_use_anchors_", p_status); + } + + anchors_mode = p_status; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +bool ControlEditorToolbar::_is_node_locked(const Node *p_node) { + return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); +} + +List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) { + List<Control *> selection; + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); + if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(control))) { + selection.push_back(control); + } + } + + if (remove_controls_if_parent_in_selection) { + List<Control *> filtered_selection; + for (Control *E : selection) { + if (!selection.find(E->get_parent())) { + filtered_selection.push_back(E); + } + } + return filtered_selection; + } + + return selection; +} + +void ControlEditorToolbar::_popup_callback(int p_op) { + switch (p_op) { + case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { + _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { + _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { + _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { + _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER: { + _set_anchors_and_offsets_preset(PRESET_CENTER); + } break; + case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { + _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { + _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { + _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { + _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { + _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_WIDE: { + _set_anchors_and_offsets_preset(Control::PRESET_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { + _set_anchors_and_offsets_to_keep_ratio(); + } break; + + case ANCHORS_PRESET_TOP_LEFT: { + _set_anchors_preset(PRESET_TOP_LEFT); + } break; + case ANCHORS_PRESET_TOP_RIGHT: { + _set_anchors_preset(PRESET_TOP_RIGHT); + } break; + case ANCHORS_PRESET_BOTTOM_LEFT: { + _set_anchors_preset(PRESET_BOTTOM_LEFT); + } break; + case ANCHORS_PRESET_BOTTOM_RIGHT: { + _set_anchors_preset(PRESET_BOTTOM_RIGHT); + } break; + case ANCHORS_PRESET_CENTER_LEFT: { + _set_anchors_preset(PRESET_CENTER_LEFT); + } break; + case ANCHORS_PRESET_CENTER_RIGHT: { + _set_anchors_preset(PRESET_CENTER_RIGHT); + } break; + case ANCHORS_PRESET_CENTER_TOP: { + _set_anchors_preset(PRESET_CENTER_TOP); + } break; + case ANCHORS_PRESET_CENTER_BOTTOM: { + _set_anchors_preset(PRESET_CENTER_BOTTOM); + } break; + case ANCHORS_PRESET_CENTER: { + _set_anchors_preset(PRESET_CENTER); + } break; + case ANCHORS_PRESET_TOP_WIDE: { + _set_anchors_preset(PRESET_TOP_WIDE); + } break; + case ANCHORS_PRESET_LEFT_WIDE: { + _set_anchors_preset(PRESET_LEFT_WIDE); + } break; + case ANCHORS_PRESET_RIGHT_WIDE: { + _set_anchors_preset(PRESET_RIGHT_WIDE); + } break; + case ANCHORS_PRESET_BOTTOM_WIDE: { + _set_anchors_preset(PRESET_BOTTOM_WIDE); + } break; + case ANCHORS_PRESET_VCENTER_WIDE: { + _set_anchors_preset(PRESET_VCENTER_WIDE); + } break; + case ANCHORS_PRESET_HCENTER_WIDE: { + _set_anchors_preset(PRESET_HCENTER_WIDE); + } break; + case ANCHORS_PRESET_WIDE: { + _set_anchors_preset(Control::PRESET_WIDE); + } break; + + case CONTAINERS_H_PRESET_FILL: { + _set_container_h_preset(Control::SIZE_FILL); + } break; + case CONTAINERS_H_PRESET_FILL_EXPAND: { + _set_container_h_preset(Control::SIZE_EXPAND_FILL); + } break; + case CONTAINERS_H_PRESET_SHRINK_BEGIN: { + _set_container_h_preset(Control::SIZE_SHRINK_BEGIN); + } break; + case CONTAINERS_H_PRESET_SHRINK_CENTER: { + _set_container_h_preset(Control::SIZE_SHRINK_CENTER); + } break; + case CONTAINERS_H_PRESET_SHRINK_END: { + _set_container_h_preset(Control::SIZE_SHRINK_END); + } break; + + case CONTAINERS_V_PRESET_FILL: { + _set_container_v_preset(Control::SIZE_FILL); + } break; + case CONTAINERS_V_PRESET_FILL_EXPAND: { + _set_container_v_preset(Control::SIZE_EXPAND_FILL); + } break; + case CONTAINERS_V_PRESET_SHRINK_BEGIN: { + _set_container_v_preset(Control::SIZE_SHRINK_BEGIN); + } break; + case CONTAINERS_V_PRESET_SHRINK_CENTER: { + _set_container_v_preset(Control::SIZE_SHRINK_CENTER); + } break; + case CONTAINERS_V_PRESET_SHRINK_END: { + _set_container_v_preset(Control::SIZE_SHRINK_END); + } break; + } +} + +void ControlEditorToolbar::_selection_changed() { + // Update the anchors_mode. + int nb_controls = 0; + int nb_valid_controls = 0; + int nb_anchors_mode = 0; + + List<Node *> selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (!control) { + continue; + } + + nb_controls++; + if (Object::cast_to<Container>(control->get_parent())) { + continue; + } + + nb_valid_controls++; + if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { + nb_anchors_mode++; + } + } + + anchors_mode = (nb_valid_controls == nb_anchors_mode); + anchor_mode_button->set_pressed(anchors_mode); + + if (nb_controls > 0) { + set_physics_process(true); + } else { + set_physics_process(false); + set_visible(false); + } +} + +void ControlEditorToolbar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + anchor_layouts_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + + PopupMenu *p = anchor_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); + p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Current Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); + p->set_item_tooltip(19, TTR("Adjust anchors and offsets to match the current rect size.")); + + p->add_separator(); + p->add_submenu_item(TTR("Anchors only"), "Anchors"); + p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + + anchors_popup->clear(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); + + anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + + container_layouts_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + + p = container_h_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_H_PRESET_FILL); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_H_PRESET_FILL_EXPAND); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_H_PRESET_SHRINK_BEGIN); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_H_PRESET_SHRINK_CENTER); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_H_PRESET_SHRINK_END); + + p = container_v_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_V_PRESET_FILL); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_V_PRESET_FILL_EXPAND); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_V_PRESET_SHRINK_BEGIN); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_V_PRESET_SHRINK_CENTER); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_V_PRESET_SHRINK_END); + } break; + + case NOTIFICATION_PHYSICS_PROCESS: { + bool has_control_parents = false; + bool has_container_parents = false; + + // Update the viewport if the canvas_item changes + List<Control *> selection = _get_edited_controls(true); + for (Control *control : selection) { + if (Object::cast_to<Control>(control->get_parent())) { + has_control_parents = true; + } + if (Object::cast_to<Container>(control->get_parent())) { + has_container_parents = true; + } + } + + // Show / Hide the control layout buttons. + if (selection.size() > 0) { + set_visible(true); + + // Toggle anchor and container layout buttons depending on parents of the selected nodes. + // - If there are no control parents, enable everything. + // - If there are container parents, then enable only container buttons. + // - If there are NO container parents, then enable only anchor buttons. + bool enable_anchors = false; + bool enable_containers = false; + if (!has_control_parents) { + enable_anchors = true; + enable_containers = true; + } else if (has_container_parents) { + enable_containers = true; + } else { + enable_anchors = true; + } + + if (enable_anchors) { + anchor_presets_menu->set_disabled(false); + anchor_presets_menu->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); + anchor_mode_button->set_disabled(false); + anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their offsets.")); + } else { + anchor_presets_menu->set_disabled(true); + anchor_presets_menu->set_tooltip(TTR("Children of containers have their anchors and offsets values controlled by their parent.")); + anchor_mode_button->set_disabled(true); + anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and offsets values controlled by their parent.")); + } + + if (enable_containers) { + container_h_presets_menu->set_disabled(false); + container_h_presets_menu->set_tooltip(TTR("Horizontal sizing setting for children of a Container node.")); + container_v_presets_menu->set_disabled(false); + container_v_presets_menu->set_tooltip(TTR("Vertical sizing setting for children of a Container node.")); + } else { + container_h_presets_menu->set_disabled(true); + container_h_presets_menu->set_tooltip(TTR("Children of regular controls are controlled by their anchors and offsets.")); + container_v_presets_menu->set_disabled(true); + container_v_presets_menu->set_tooltip(TTR("Children of regular controls are controlled by their anchors and offsets.")); + } + } else { + set_visible(false); + } + } break; + } +} + +ControlEditorToolbar::ControlEditorToolbar(EditorNode *p_editor) { + anchor_layouts_icon = memnew(TextureRect); + anchor_layouts_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + add_child(anchor_layouts_icon); + + Label *l = memnew(Label); + l->set_text(TTR("Anchors")); + l->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + add_child(l); + + anchor_presets_menu = memnew(MenuButton); + anchor_presets_menu->set_shortcut_context(this); + anchor_presets_menu->set_text(TTR("Preset")); + add_child(anchor_presets_menu); + anchor_presets_menu->set_switch_on_hover(true); + + PopupMenu *p = anchor_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + anchors_popup = memnew(PopupMenu); + p->add_child(anchors_popup); + anchors_popup->set_name("Anchors"); + anchors_popup->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + anchor_mode_button = memnew(Button); + anchor_mode_button->set_flat(true); + anchor_mode_button->set_toggle_mode(true); + add_child(anchor_mode_button); + anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_button_toggle_anchor_mode)); + + add_child(memnew(VSeparator)); + + container_layouts_icon = memnew(TextureRect); + container_layouts_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + add_child(container_layouts_icon); + + l = memnew(Label); + l->set_text(TTR("Containers")); + l->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + add_child(l); + + container_h_presets_menu = memnew(MenuButton); + container_h_presets_menu->set_shortcut_context(this); + container_h_presets_menu->set_text(TTR("Horizontal")); + add_child(container_h_presets_menu); + container_h_presets_menu->set_switch_on_hover(true); + + p = container_h_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + container_v_presets_menu = memnew(MenuButton); + container_v_presets_menu->set_shortcut_context(this); + container_v_presets_menu->set_text(TTR("Vertical")); + add_child(container_v_presets_menu); + container_v_presets_menu->set_switch_on_hover(true); + + p = container_v_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + undo_redo = p_editor->get_undo_redo(); + editor_selection = p_editor->get_editor_selection(); + editor_selection->add_editor_plugin(this); + editor_selection->connect("selection_changed", callable_mp(this, &ControlEditorToolbar::_selection_changed)); + + singleton = this; +} + +ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; + +ControlEditorPlugin::ControlEditorPlugin(EditorNode *p_editor) { + editor = p_editor; + + toolbar = memnew(ControlEditorToolbar(editor)); + toolbar->hide(); + add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); + + Ref<EditorInspectorPluginControl> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h new file mode 100644 index 0000000000..610846a97b --- /dev/null +++ b/editor/plugins/control_editor_plugin.h @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* control_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 CONTROL_EDITOR_PLUGIN_H +#define CONTROL_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/texture_rect.h" + +class ControlPositioningWarning : public MarginContainer { + GDCLASS(ControlPositioningWarning, MarginContainer); + + Control *control_node = nullptr; + + PanelContainer *bg_panel = nullptr; + GridContainer *grid = nullptr; + TextureRect *title_icon = nullptr; + TextureRect *hint_icon = nullptr; + Label *title_label = nullptr; + Label *hint_label = nullptr; + Control *hint_filler_left = nullptr; + Control *hint_filler_right = nullptr; + + void _update_warning(); + void _update_toggler(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +protected: + void _notification(int p_notification); + +public: + void set_control(Control *p_node); + + ControlPositioningWarning(); +}; + +class EditorPropertyAnchorsPreset : public EditorProperty { + GDCLASS(EditorPropertyAnchorsPreset, EditorProperty); + OptionButton *options; + + void _option_selected(int p_which); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options); + virtual void update_property() override; + EditorPropertyAnchorsPreset(); +}; + +class EditorPropertySizeFlags : public EditorProperty { + GDCLASS(EditorPropertySizeFlags, EditorProperty); + + enum FlagPreset { + SIZE_FLAGS_PRESET_FILL, + SIZE_FLAGS_PRESET_SHRINK_BEGIN, + SIZE_FLAGS_PRESET_SHRINK_CENTER, + SIZE_FLAGS_PRESET_SHRINK_END, + SIZE_FLAGS_PRESET_CUSTOM, + }; + + OptionButton *flag_presets; + CheckBox *flag_expand; + VBoxContainer *flag_options; + Vector<CheckBox *> flag_checks; + + bool vertical = false; + + bool keep_selected_preset = false; + + void _preset_selected(int p_which); + void _expand_toggled(); + void _flag_toggled(); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options, bool p_vertical); + virtual void update_property() override; + EditorPropertySizeFlags(); +}; + +class EditorInspectorPluginControl : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginControl, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_group(Object *p_object, const String &p_group) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; +}; + +class ControlEditorToolbar : public HBoxContainer { + GDCLASS(ControlEditorToolbar, HBoxContainer); + + UndoRedo *undo_redo; + EditorSelection *editor_selection; + + enum MenuOption { + ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, + ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, + ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, + ANCHORS_AND_OFFSETS_PRESET_CENTER, + ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, + ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, + ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, + ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, + ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, + ANCHORS_AND_OFFSETS_PRESET_WIDE, + + ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, + + ANCHORS_PRESET_TOP_LEFT, + ANCHORS_PRESET_TOP_RIGHT, + ANCHORS_PRESET_BOTTOM_LEFT, + ANCHORS_PRESET_BOTTOM_RIGHT, + ANCHORS_PRESET_CENTER_LEFT, + ANCHORS_PRESET_CENTER_RIGHT, + ANCHORS_PRESET_CENTER_TOP, + ANCHORS_PRESET_CENTER_BOTTOM, + ANCHORS_PRESET_CENTER, + ANCHORS_PRESET_TOP_WIDE, + ANCHORS_PRESET_LEFT_WIDE, + ANCHORS_PRESET_RIGHT_WIDE, + ANCHORS_PRESET_BOTTOM_WIDE, + ANCHORS_PRESET_VCENTER_WIDE, + ANCHORS_PRESET_HCENTER_WIDE, + ANCHORS_PRESET_WIDE, + + // Offsets Presets are not currently in use. + OFFSETS_PRESET_TOP_LEFT, + OFFSETS_PRESET_TOP_RIGHT, + OFFSETS_PRESET_BOTTOM_LEFT, + OFFSETS_PRESET_BOTTOM_RIGHT, + OFFSETS_PRESET_CENTER_LEFT, + OFFSETS_PRESET_CENTER_RIGHT, + OFFSETS_PRESET_CENTER_TOP, + OFFSETS_PRESET_CENTER_BOTTOM, + OFFSETS_PRESET_CENTER, + OFFSETS_PRESET_TOP_WIDE, + OFFSETS_PRESET_LEFT_WIDE, + OFFSETS_PRESET_RIGHT_WIDE, + OFFSETS_PRESET_BOTTOM_WIDE, + OFFSETS_PRESET_VCENTER_WIDE, + OFFSETS_PRESET_HCENTER_WIDE, + OFFSETS_PRESET_WIDE, + + CONTAINERS_H_PRESET_FILL, + CONTAINERS_H_PRESET_FILL_EXPAND, + CONTAINERS_H_PRESET_SHRINK_BEGIN, + CONTAINERS_H_PRESET_SHRINK_CENTER, + CONTAINERS_H_PRESET_SHRINK_END, + CONTAINERS_V_PRESET_FILL, + CONTAINERS_V_PRESET_FILL_EXPAND, + CONTAINERS_V_PRESET_SHRINK_BEGIN, + CONTAINERS_V_PRESET_SHRINK_CENTER, + CONTAINERS_V_PRESET_SHRINK_END, + }; + + TextureRect *anchor_layouts_icon; + MenuButton *anchor_presets_menu; + PopupMenu *anchors_popup; + TextureRect *container_layouts_icon; + MenuButton *container_h_presets_menu; + MenuButton *container_v_presets_menu; + + Button *anchor_mode_button; + + bool anchors_mode = false; + + void _set_anchors_preset(Control::LayoutPreset p_preset); + void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); + void _set_anchors_and_offsets_to_keep_ratio(); + void _set_container_h_preset(Control::SizeFlags p_preset); + void _set_container_v_preset(Control::SizeFlags p_preset); + + Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor); + Vector2 _position_to_anchor(const Control *p_control, Vector2 position); + + void _button_toggle_anchor_mode(bool p_status); + + bool _is_node_locked(const Node *p_node); + List<Control *> _get_edited_controls(bool retrieve_locked = false, bool remove_controls_if_parent_in_selection = true); + void _popup_callback(int p_op); + void _selection_changed(); + +protected: + void _notification(int p_notification); + + static ControlEditorToolbar *singleton; + +public: + bool is_anchors_mode_enabled() { return anchors_mode; }; + + static ControlEditorToolbar *get_singleton() { return singleton; } + + ControlEditorToolbar(EditorNode *p_editor); +}; + +class ControlEditorPlugin : public EditorPlugin { + GDCLASS(ControlEditorPlugin, EditorPlugin); + + EditorNode *editor; + ControlEditorToolbar *toolbar; + +public: + virtual String get_name() const override { return "Control"; } + + ControlEditorPlugin(EditorNode *p_editor); +}; + +#endif //CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index bee3aa3bfe..5f58724594 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -385,8 +385,6 @@ int Node3DEditorViewport::get_selected_count() const { } void Node3DEditorViewport::cancel_transform() { - _edit.mode = TRANSFORM_NONE; - List<Node *> &selection = editor_selection->get_selected_node_list(); for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { @@ -402,7 +400,8 @@ void Node3DEditorViewport::cancel_transform() { sp->set_global_transform(se->original); } - surface->update(); + + finish_transform(); set_message(TTR("Transform Aborted."), 3); } @@ -3964,8 +3963,6 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant continue; } memdelete(instantiated_scene); - } else { - continue; } can_instantiate = true; break; @@ -4068,12 +4065,9 @@ void Node3DEditorViewport::commit_transform() { undo_redo->add_undo_method(sp, "set_global_transform", se->original); } undo_redo->commit_action(); - _edit.mode = TRANSFORM_NONE; - _edit.instant = false; - spatial_editor->set_local_coords_enabled(_edit.original_local); + + finish_transform(); set_message(""); - spatial_editor->update_transform_gizmo(); - surface->update(); } void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { @@ -4412,6 +4406,14 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { } } +void Node3DEditorViewport::finish_transform() { + spatial_editor->set_local_coords_enabled(_edit.original_local); + spatial_editor->update_transform_gizmo(); + _edit.mode = TRANSFORM_NONE; + _edit.instant = false; + surface->update(); +} + Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index) { cpu_time_history_index = 0; gpu_time_history_index = 0; diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 16d9815288..333702fd94 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -410,6 +410,7 @@ private: void begin_transform(TransformMode p_mode, bool instant); void commit_transform(); void update_transform(Point2 p_mousepos, bool p_shift); + void finish_transform(); protected: void _notification(int p_what); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0e362b13c6..a4f9563840 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1560,7 +1560,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_ for (int i = 0; i < anim->get_track_count(); i++) { NodePath track_np = anim->track_get_path(i); - Node *n = root->get_node(track_np); + Node *n = root->get_node_or_null(track_np); if (!n) { continue; } diff --git a/misc/hooks/pre-commit-black b/misc/hooks/pre-commit-black index 8e22e6068e..fd93bfe73c 100755 --- a/misc/hooks/pre-commit-black +++ b/misc/hooks/pre-commit-black @@ -139,11 +139,11 @@ fi while true; do if [ $terminal = "0" ] ; then if [ -x "$ZENITY" ] ; then - ans=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage") + choice=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage") if [ "$?" = "0" ] ; then yn="Y" else - if [ "$ans" = "Apply and stage" ] ; then + if [ "$choice" = "Apply and stage" ] ; then yn="S" else yn="N" @@ -151,10 +151,10 @@ while true; do fi elif [ -x "$XMSG" ] ; then $XMSG -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" - ans=$? - if [ "$ans" = "100" ] ; then + choice=$? + if [ "$choice" = "100" ] ; then yn="Y" - elif [ "$ans" = "200" ] ; then + elif [ "$choice" = "200" ] ; then yn="S" else yn="N" @@ -162,10 +162,10 @@ while true; do elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")" $PWSH -noprofile -executionpolicy bypass -file "$winmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" - ans=$? - if [ "$ans" = "100" ] ; then + choice=$? + if [ "$choice" = "100" ] ; then yn="Y" - elif [ "$ans" = "200" ] ; then + elif [ "$choice" = "200" ] ; then yn="S" else yn="N" diff --git a/misc/hooks/pre-commit-clang-format b/misc/hooks/pre-commit-clang-format index de5d9c3f06..2ee3f569d5 100755 --- a/misc/hooks/pre-commit-clang-format +++ b/misc/hooks/pre-commit-clang-format @@ -179,11 +179,11 @@ fi while true; do if [ $terminal = "0" ] ; then if [ -x "$ZENITY" ] ; then - ans=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage") + choice=$($ZENITY --text-info --filename="$patch" --width=800 --height=600 --title="Do you want to apply that patch?" --ok-label="Apply" --cancel-label="Do not apply" --extra-button="Apply and stage") if [ "$?" = "0" ] ; then yn="Y" else - if [ "$ans" = "Apply and stage" ] ; then + if [ "$choice" = "Apply and stage" ] ; then yn="S" else yn="N" @@ -191,10 +191,10 @@ while true; do fi elif [ -x "$XMSG" ] ; then $XMSG -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" - ans=$? - if [ "$ans" = "100" ] ; then + choice=$? + if [ "$choice" = "100" ] ; then yn="Y" - elif [ "$ans" = "200" ] ; then + elif [ "$choice" = "200" ] ; then yn="S" else yn="N" @@ -202,10 +202,10 @@ while true; do elif [ \( \( "$OSTYPE" = "msys" \) -o \( "$OSTYPE" = "win32" \) \) -a \( -x "$PWSH" \) ]; then winmessage="$(canonicalize_filename "./.git/hooks/winmessage.ps1")" $PWSH -noprofile -executionpolicy bypass -file "$winmessage" -file "$patch" -buttons "Apply":100,"Apply and stage":200,"Do not apply":0 -center -default "Do not apply" -geometry 800x600 -title "Do you want to apply that patch?" - ans=$? - if [ "$ans" = "100" ] ; then + choice=$? + if [ "$choice" = "100" ] ; then yn="Y" - elif [ "$ans" = "200" ] ; then + elif [ "$choice" = "200" ] ; then yn="S" else yn="N" diff --git a/misc/scripts/codespell.sh b/misc/scripts/codespell.sh new file mode 100644 index 0000000000..2822c6421b --- /dev/null +++ b/misc/scripts/codespell.sh @@ -0,0 +1,5 @@ +#!/bin/sh +SKIP_LIST="./thirdparty,*.gen.*,*.po,*.pot,package-lock.json,./core/string/locales.h,./DONORS.md,./misc/scripts/codespell.sh" +IGNORE_LIST="ba,childs,curvelinear,expct,fave,findn,gird,inout,lod,nd,numer,ois,ro,statics,te,varn" + +codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}" diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 0056fee7a4..a3dcfddc7f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -3373,9 +3373,9 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { orm_texture_index = _set_texture(state, orm_texture); } if (has_ao) { - Dictionary ot; - ot["index"] = orm_texture_index; - d["occlusionTexture"] = ot; + Dictionary occt; + occt["index"] = orm_texture_index; + d["occlusionTexture"] = occt; } if (has_roughness || has_metalness) { mrt["index"] = orm_texture_index; diff --git a/modules/text_server_adv/script_iterator.cpp b/modules/text_server_adv/script_iterator.cpp index 9e3d9138d0..06e934c67d 100644 --- a/modules/text_server_adv/script_iterator.cpp +++ b/modules/text_server_adv/script_iterator.cpp @@ -82,7 +82,7 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length paren_stack[paren_sp].pair_index = ch; paren_stack[paren_sp].script_code = script_code; } else if (paren_sp >= 0) { - // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be poped. + // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be popped. UChar32 paired_ch = u_getBidiPairedBracket(ch); while (paren_sp >= 0 && paren_stack[paren_sp].pair_index != paired_ch) { paren_sp -= 1; diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp index bba5410629..cf0111ee7c 100644 --- a/modules/visual_script/editor/visual_script_property_selector.cpp +++ b/modules/visual_script/editor/visual_script_property_selector.cpp @@ -493,7 +493,7 @@ VisualScriptPropertySelector::VisualScriptPropertySelector() { hbox->add_child(scope_combo); search_box = memnew(LineEdit); - search_box->set_tooltip(TTR("Enter \" \" to show all filterd options\nEnter \".\" to show all filterd methods, operators and constructors\nUse CTRL_KEY to drop property setters")); + search_box->set_tooltip(TTR("Enter \" \" to show all filtered options\nEnter \".\" to show all filtered methods, operators and constructors\nUse CTRL_KEY to drop property setters")); search_box->set_custom_minimum_size(Size2(200, 0) * EDSCALE); search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_update_results_s)); @@ -694,7 +694,7 @@ bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { class_doc.name = selector_ui->base_script; class_doc.inherits = script->get_instance_base_type(); - class_doc.brief_description = ".vs files not suported by EditorHelp::get_doc_data()"; + class_doc.brief_description = ".vs files not supported by EditorHelp::get_doc_data()"; class_doc.description = ""; Object *obj = ObjectDB::get_instance(script->get_instance_id()); @@ -711,9 +711,9 @@ bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { class_doc.signals.push_back(_get_method_doc(S->get())); } - List<PropertyInfo> propertys; - Object::cast_to<Script>(obj)->get_script_property_list(&propertys); - for (List<PropertyInfo>::Element *P = propertys.front(); P; P = P->next()) { + List<PropertyInfo> properties; + Object::cast_to<Script>(obj)->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *P = properties.front(); P; P = P->next()) { DocData::PropertyDoc pd = DocData::PropertyDoc(); pd.name = P->get().name; pd.type = Variant::get_type_name(P->get().type); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index bebb198126..1ef571b6ee 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -177,7 +177,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } } - // We assume OK while hostname resultion is pending. + // We assume OK while hostname resolution is pending. Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED; while (_ip_candidates.size()) { err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index b0f16337ed..a7a8801bdc 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -164,7 +164,7 @@ int DisplayServerAndroid::screen_get_dpi(int p_screen) const { float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const { GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); if (!godot_io_java) { - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return SCREEN_REFRESH_RATE_FALLBACK; } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 8a2788e848..ff0bcf0716 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -141,12 +141,12 @@ float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) { if (_get_screen_refresh_rate) { JNIEnv *env = get_jni_env(); if (env == nullptr) { - ERR_PRINT("An error occured while trying to get screen refresh rate."); + ERR_PRINT("An error occurred while trying to get screen refresh rate."); return fallback; } return (float)env->CallDoubleMethod(godot_io_instance, _get_screen_refresh_rate, (double)fallback); } - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return fallback; } diff --git a/platform/javascript/js/libs/library_godot_fetch.js b/platform/javascript/js/libs/library_godot_fetch.js index 285e50a035..007e7b70f5 100644 --- a/platform/javascript/js/libs/library_godot_fetch.js +++ b/platform/javascript/js/libs/library_godot_fetch.js @@ -89,6 +89,7 @@ const GodotFetch = { method: method, headers: headers, body: body, + credentials: 'include', }; obj.request = fetch(url, init); obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index bf9c9b1766..86c3534fc9 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -1077,7 +1077,7 @@ float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK); } else { - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return SCREEN_REFRESH_RATE_FALLBACK; } @@ -1105,14 +1105,14 @@ float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { } } - ERR_PRINT("An error occured while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occured. + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred. return SCREEN_REFRESH_RATE_FALLBACK; } else { - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return SCREEN_REFRESH_RATE_FALLBACK; } } - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return SCREEN_REFRESH_RATE_FALLBACK; } diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 2691664b96..137b1b45b0 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -1335,7 +1335,7 @@ float DisplayServerOSX::screen_get_refresh_rate(int p_screen) const { const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); return (float)displayRefreshRate; } - ERR_PRINT("An error occured while trying to get the screen refresh rate."); + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); return SCREEN_REFRESH_RATE_FALLBACK; } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 20268b3f6a..36c87f2683 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1023,6 +1023,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; } r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + r_style_ex |= WS_EX_ACCEPTFILES; } void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint) { @@ -1099,10 +1100,10 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.multiwindow_fs = false; - _update_window_style(false); + _update_window_style(p_window, false); } else { wd.multiwindow_fs = true; - _update_window_style(false); + _update_window_style(p_window, false); } if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) { @@ -1123,7 +1124,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.maximized = false; wd.minimized = false; - _update_window_style(false); + _update_window_style(p_window, false); MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp index e0cd44e05b..8a8964f69a 100644 --- a/scene/3d/spring_arm_3d.cpp +++ b/scene/3d/spring_arm_3d.cpp @@ -185,14 +185,14 @@ void SpringArm3D::process_spring() { } current_spring_length = spring_length * motion_delta; - Transform3D childs_transform; - childs_transform.origin = get_global_transform().origin + cast_direction * (spring_length * motion_delta); + Transform3D child_transform; + child_transform.origin = get_global_transform().origin + cast_direction * (spring_length * motion_delta); for (int i = get_child_count() - 1; 0 <= i; --i) { Node3D *child = Object::cast_to<Node3D>(get_child(i)); if (child) { - childs_transform.basis = child->get_global_transform().basis; - child->set_global_transform(childs_transform); + child_transform.basis = child->get_global_transform().basis; + child->set_global_transform(child_transform); } } } diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index 181d1bf33b..b59eda465e 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -70,6 +70,24 @@ void AspectRatioContainer::set_alignment_vertical(AlignmentMode p_alignment_vert queue_sort(); } +Vector<int> AspectRatioContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> AspectRatioContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void AspectRatioContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/aspect_ratio_container.h b/scene/gui/aspect_ratio_container.h index 4a168bad14..6740e2f329 100644 --- a/scene/gui/aspect_ratio_container.h +++ b/scene/gui/aspect_ratio_container.h @@ -72,6 +72,9 @@ public: void set_alignment_vertical(AlignmentMode p_alignment_vertical); AlignmentMode get_alignment_vertical() const { return alignment_vertical; } + + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; }; VARIANT_ENUM_CAST(AspectRatioContainer::StretchMode); diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 9827bd0cef..ed54410636 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -331,6 +331,30 @@ Control *BoxContainer::add_spacer(bool p_begin) { return c; } +Vector<int> BoxContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> BoxContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + BoxContainer::BoxContainer(bool p_vertical) { vertical = p_vertical; } diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 68d55e1aaf..3043c3ea45 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -62,6 +62,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + BoxContainer(bool p_vertical = false); }; diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp index f3306783f3..f6cd74583b 100644 --- a/scene/gui/center_container.cpp +++ b/scene/gui/center_container.cpp @@ -69,6 +69,14 @@ bool CenterContainer::is_using_top_left() const { return use_top_left; } +Vector<int> CenterContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> CenterContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} + void CenterContainer::_notification(int p_what) { if (p_what == NOTIFICATION_SORT_CHILDREN) { Size2 size = get_size(); diff --git a/scene/gui/center_container.h b/scene/gui/center_container.h index 16a10c8070..c35e0c4e29 100644 --- a/scene/gui/center_container.h +++ b/scene/gui/center_container.h @@ -48,6 +48,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + CenterContainer(); }; diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 7b213ec314..b8a5a06147 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -143,6 +143,34 @@ void Container::queue_sort() { pending_sort = true; } +Vector<int> Container::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) { + return flags; + } + + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> Container::get_allowed_size_flags_vertical() const { + Vector<int> flags; + if (GDVIRTUAL_CALL(_get_allowed_size_flags_vertical, flags)) { + return flags; + } + + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void Container::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -177,6 +205,9 @@ void Container::_bind_methods() { ClassDB::bind_method(D_METHOD("queue_sort"), &Container::queue_sort); ClassDB::bind_method(D_METHOD("fit_child_in_rect", "child", "rect"), &Container::fit_child_in_rect); + GDVIRTUAL_BIND(_get_allowed_size_flags_horizontal); + GDVIRTUAL_BIND(_get_allowed_size_flags_vertical); + BIND_CONSTANT(NOTIFICATION_PRE_SORT_CHILDREN); BIND_CONSTANT(NOTIFICATION_SORT_CHILDREN); diff --git a/scene/gui/container.h b/scene/gui/container.h index 0e986f46ef..9ec4ad3200 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -46,6 +46,9 @@ protected: virtual void move_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_horizontal) + GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_vertical) + void _notification(int p_what); static void _bind_methods(); @@ -57,6 +60,9 @@ public: void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); + virtual Vector<int> get_allowed_size_flags_horizontal() const; + virtual Vector<int> get_allowed_size_flags_vertical() const; + TypedArray<String> get_configuration_warnings() const override; Container(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index fdae8e2f1f..aa59df6337 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -47,7 +47,7 @@ #include "servers/text_server.h" #ifdef TOOLS_ENABLED -#include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/control_editor_plugin.h" #endif #ifdef TOOLS_ENABLED @@ -56,51 +56,71 @@ Dictionary Control::_edit_get_state() const { s["rotation"] = get_rotation(); s["scale"] = get_scale(); s["pivot"] = get_pivot_offset(); + Array anchors; anchors.push_back(get_anchor(SIDE_LEFT)); anchors.push_back(get_anchor(SIDE_TOP)); anchors.push_back(get_anchor(SIDE_RIGHT)); anchors.push_back(get_anchor(SIDE_BOTTOM)); s["anchors"] = anchors; + Array offsets; offsets.push_back(get_offset(SIDE_LEFT)); offsets.push_back(get_offset(SIDE_TOP)); offsets.push_back(get_offset(SIDE_RIGHT)); offsets.push_back(get_offset(SIDE_BOTTOM)); s["offsets"] = offsets; + + s["layout_mode"] = _get_layout_mode(); + s["anchors_layout_preset"] = _get_anchors_layout_preset(); + return s; } void Control::_edit_set_state(const Dictionary &p_state) { ERR_FAIL_COND((p_state.size() <= 0) || !p_state.has("rotation") || !p_state.has("scale") || - !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets")); + !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets") || + !p_state.has("layout_mode") || !p_state.has("anchors_layout_preset")); Dictionary state = p_state; set_rotation(state["rotation"]); set_scale(state["scale"]); set_pivot_offset(state["pivot"]); + Array anchors = state["anchors"]; + + // If anchors are not in their default position, force the anchor layout mode in place of position. + LayoutMode _layout = (LayoutMode)(int)state["layout_mode"]; + if (_layout == LayoutMode::LAYOUT_MODE_POSITION) { + bool anchors_mode = ((real_t)anchors[0] != 0.0 || (real_t)anchors[1] != 0.0 || (real_t)anchors[2] != 0.0 || (real_t)anchors[3] != 0.0); + if (anchors_mode) { + _layout = LayoutMode::LAYOUT_MODE_ANCHORS; + } + } + + _set_layout_mode(_layout); + if (_layout == LayoutMode::LAYOUT_MODE_ANCHORS) { + _set_anchors_layout_preset((int)state["anchors_layout_preset"]); + } + data.anchor[SIDE_LEFT] = anchors[0]; data.anchor[SIDE_TOP] = anchors[1]; data.anchor[SIDE_RIGHT] = anchors[2]; data.anchor[SIDE_BOTTOM] = anchors[3]; + Array offsets = state["offsets"]; data.offset[SIDE_LEFT] = offsets[0]; data.offset[SIDE_TOP] = offsets[1]; data.offset[SIDE_RIGHT] = offsets[2]; data.offset[SIDE_BOTTOM] = offsets[3]; + _size_changed(); } void Control::_edit_set_position(const Point2 &p_position) { -#ifdef TOOLS_ENABLED ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins."); - set_position(p_position, CanvasItemEditor::get_singleton()->is_anchors_mode_enabled() && Object::cast_to<Control>(data.parent)); -#else - // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED - set_position(p_position); -#endif + set_position(p_position, ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled() && Object::cast_to<Control>(data.parent)); }; Point2 Control::_edit_get_position() const { @@ -116,15 +136,9 @@ Size2 Control::_edit_get_scale() const { } void Control::_edit_set_rect(const Rect2 &p_edit_rect) { -#ifdef TOOLS_ENABLED ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins."); - set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); - set_size(p_edit_rect.size.snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); -#else - // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED - set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1))); - set_size(p_edit_rect.size.snapped(Vector2(1, 1))); -#endif + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); } Rect2 Control::_edit_get_rect() const { @@ -177,6 +191,7 @@ String Control::properties_managed_by_container[] = { "anchor_right", "anchor_bottom", "rect_position", + "rect_rotation", "rect_scale", "rect_size" }; @@ -430,6 +445,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } void Control::_validate_property(PropertyInfo &property) const { + // Update theme type variation options. if (property.name == "theme_type_variation") { List<StringName> names; @@ -455,10 +471,99 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } - if (!Object::cast_to<Container>(get_parent())) { - return; + + // Validate which positioning properties should be displayed depending on the parent and the layout mode. + Node *parent_node = get_parent_control(); + if (!parent_node) { + // If there is no parent, display both anchor and container options. + + // Set the layout mode to be disabled with the proper value. + if (property.name == "layout_mode") { + property.hint_string = "Position,Anchors,Container,Uncontrolled"; + property.usage |= PROPERTY_USAGE_READ_ONLY; + } + + // Use the layout mode to display or hide advanced anchoring properties. + bool use_custom_anchors = _get_anchors_layout_preset() == -1; // Custom "preset". + if (!use_custom_anchors && (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_"))) { + property.usage ^= PROPERTY_USAGE_EDITOR; + } + } else if (Object::cast_to<Container>(parent_node)) { + // If the parent is a container, display only container-related properties. + if (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_") || property.name == "anchors_preset" || + (property.name.begins_with("rect_") && property.name != "rect_min_size" && property.name != "rect_clip_content" && property.name != "rect_global_position")) { + property.usage ^= PROPERTY_USAGE_EDITOR; + + } else if (property.name == "layout_mode") { + // Set the layout mode to be disabled with the proper value. + property.hint_string = "Position,Anchors,Container,Uncontrolled"; + property.usage |= PROPERTY_USAGE_READ_ONLY; + } else if (property.name == "size_flags_horizontal" || property.name == "size_flags_vertical") { + // Filter allowed size flags based on the parent container configuration. + Container *parent_container = Object::cast_to<Container>(parent_node); + Vector<int> size_flags; + if (property.name == "size_flags_horizontal") { + size_flags = parent_container->get_allowed_size_flags_horizontal(); + } else if (property.name == "size_flags_vertical") { + size_flags = parent_container->get_allowed_size_flags_vertical(); + } + + // Enforce the order of the options, regardless of what the container provided. + String hint_string; + if (size_flags.has(SIZE_FILL)) { + hint_string += "Fill:1"; + } + if (size_flags.has(SIZE_EXPAND)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Expand:2"; + } + if (size_flags.has(SIZE_SHRINK_CENTER)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Shrink Center:4"; + } + if (size_flags.has(SIZE_SHRINK_END)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Shrink End:8"; + } + + if (hint_string.is_empty()) { + property.hint_string = ""; + property.usage |= PROPERTY_USAGE_READ_ONLY; + } else { + property.hint_string = hint_string; + } + } + } else { + // If the parent is NOT a container or not a control at all, display only anchoring-related properties. + if (property.name.begins_with("size_flags_")) { + property.usage ^= PROPERTY_USAGE_EDITOR; + + } else if (property.name == "layout_mode") { + // Set the layout mode to be enabled with proper options. + property.hint_string = "Position,Anchors"; + } + + // Use the layout mode to display or hide advanced anchoring properties. + bool use_anchors = _get_layout_mode() == LayoutMode::LAYOUT_MODE_ANCHORS; + if (!use_anchors && property.name == "anchors_preset") { + property.usage ^= PROPERTY_USAGE_EDITOR; + } + bool use_custom_anchors = use_anchors && _get_anchors_layout_preset() == -1; // Custom "preset". + if (!use_custom_anchors && (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_"))) { + property.usage ^= PROPERTY_USAGE_EDITOR; + } } + // Disable the property if it's managed by the parent container. + if (!Object::cast_to<Container>(parent_node)) { + return; + } bool property_is_managed_by_container = false; for (unsigned i = 0; i < properties_managed_by_container_count; i++) { property_is_managed_by_container = properties_managed_by_container[i] == property.name; @@ -1390,6 +1495,54 @@ void Control::_size_changed() { } } +void Control::_set_layout_mode(LayoutMode p_mode) { + bool list_changed = false; + + if (p_mode == LayoutMode::LAYOUT_MODE_POSITION || p_mode == LayoutMode::LAYOUT_MODE_ANCHORS) { + if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)p_mode) { + list_changed = true; + } + + set_meta("_edit_layout_mode", (int)p_mode); + + if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) { + set_meta("_edit_use_custom_anchors", false); + set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); + set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); + } + } else { + if (has_meta("_edit_layout_mode")) { + remove_meta("_edit_layout_mode"); + list_changed = true; + } + } + + if (list_changed) { + notify_property_list_changed(); + } +} + +Control::LayoutMode Control::_get_layout_mode() const { + Node *parent_node = get_parent_control(); + // In these modes the property is read-only. + if (!parent_node) { + return LayoutMode::LAYOUT_MODE_UNCONTROLLED; + } else if (Object::cast_to<Container>(parent_node)) { + return LayoutMode::LAYOUT_MODE_CONTAINER; + } + + // If anchors are not in the top-left position, this is definitely in anchors mode. + if (_get_anchors_layout_preset() != (int)LayoutPreset::PRESET_TOP_LEFT) { + return LayoutMode::LAYOUT_MODE_ANCHORS; + } + // Otherwise check what was saved. + if (has_meta("_edit_layout_mode")) { + return (LayoutMode)(int)get_meta("_edit_layout_mode"); + } + // Or fallback on default. + return LayoutMode::LAYOUT_MODE_POSITION; +} + void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { ERR_FAIL_INDEX((int)p_side, 4); @@ -1431,6 +1584,120 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, set_offset(p_side, p_pos); } +void Control::_set_anchors_layout_preset(int p_preset) { + set_meta("_edit_layout_mode", (int)LayoutMode::LAYOUT_MODE_ANCHORS); + + if (p_preset == -1) { + set_meta("_edit_use_custom_anchors", true); + notify_property_list_changed(); + return; // Keep settings as is. + } + set_meta("_edit_use_custom_anchors", false); + + LayoutPreset preset = (LayoutPreset)p_preset; + // Set correct anchors. + set_anchors_preset(preset); + + // Select correct preset mode. + switch (preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_LEFT: + case PRESET_CENTER_TOP: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); + break; + case PRESET_LEFT_WIDE: + case PRESET_TOP_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_MINSIZE); + break; + } + + // Select correct grow directions. + set_grow_direction_preset(preset); + + notify_property_list_changed(); +} + +int Control::_get_anchors_layout_preset() const { + // If the custom preset was selected by user, use it. + if (has_meta("_edit_use_custom_anchors") && (bool)get_meta("_edit_use_custom_anchors")) { + return -1; + } + + // Check anchors to determine if the current state matches a preset, or not. + + float left = get_anchor(SIDE_LEFT); + float right = get_anchor(SIDE_RIGHT); + float top = get_anchor(SIDE_TOP); + float bottom = get_anchor(SIDE_BOTTOM); + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_RIGHT; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_RIGHT; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER_RIGHT; + } + if (left == 0.5 && right == 0.5 && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_CENTER_TOP; + } + if (left == 0.5 && right == 0.5 && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_CENTER_BOTTOM; + } + if (left == 0.5 && right == 0.5 && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_LEFT_WIDE; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_RIGHT_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_WIDE; + } + + if (left == 0.5 && right == 0.5 && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_VCENTER_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_HCENTER_WIDE; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_WIDE; + } + + // Does not match any preset, return "Custom". + return -1; +} + void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { ERR_FAIL_INDEX((int)p_preset, 16); @@ -1687,6 +1954,62 @@ void Control::set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPreset set_offsets_preset(p_preset, p_resize_mode, p_margin); } +void Control::set_grow_direction_preset(LayoutPreset p_preset) { + // Select correct horizontal grow direction. + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_BOTTOM_LEFT: + case PRESET_CENTER_LEFT: + case PRESET_LEFT_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_END); + break; + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_RIGHT: + case PRESET_RIGHT_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_BEGIN); + break; + case PRESET_CENTER_TOP: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + case PRESET_TOP_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); + break; + } + + // Select correct vertical grow direction. + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_CENTER_TOP: + case PRESET_TOP_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_END); + break; + + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_BOTTOM_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_BEGIN); + break; + + case PRESET_CENTER_LEFT: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER: + case PRESET_LEFT_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); + break; + } +} + real_t Control::get_anchor(Side p_side) const { ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); @@ -2847,14 +3170,22 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("accept_event"), &Control::accept_event); ClassDB::bind_method(D_METHOD("get_minimum_size"), &Control::get_minimum_size); ClassDB::bind_method(D_METHOD("get_combined_minimum_size"), &Control::get_combined_minimum_size); + + ClassDB::bind_method(D_METHOD("_set_layout_mode", "mode"), &Control::_set_layout_mode); + ClassDB::bind_method(D_METHOD("_get_layout_mode"), &Control::_get_layout_mode); + ClassDB::bind_method(D_METHOD("_set_anchors_layout_preset", "preset"), &Control::_set_anchors_layout_preset); + ClassDB::bind_method(D_METHOD("_get_anchors_layout_preset"), &Control::_get_anchors_layout_preset); ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_offsets"), &Control::set_anchors_preset, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_offsets_preset", "preset", "resize_mode", "margin"), &Control::set_offsets_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_anchors_and_offsets_preset", "preset", "resize_mode", "margin"), &Control::set_anchors_and_offsets_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("_set_anchor", "side", "anchor"), &Control::_set_anchor); ClassDB::bind_method(D_METHOD("set_anchor", "side", "anchor", "keep_offset", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_anchor", "side"), &Control::get_anchor); ClassDB::bind_method(D_METHOD("set_offset", "side", "offset"), &Control::set_offset); + ClassDB::bind_method(D_METHOD("get_offset", "offset"), &Control::get_offset); ClassDB::bind_method(D_METHOD("set_anchor_and_offset", "side", "anchor", "offset", "push_opposite_anchor"), &Control::set_anchor_and_offset, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_begin", "position"), &Control::set_begin); ClassDB::bind_method(D_METHOD("set_end", "position"), &Control::set_end); ClassDB::bind_method(D_METHOD("set_position", "position", "keep_offsets"), &Control::set_position, DEFVAL(false)); @@ -2868,7 +3199,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Control::set_rotation); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale); ClassDB::bind_method(D_METHOD("set_pivot_offset", "pivot_offset"), &Control::set_pivot_offset); - ClassDB::bind_method(D_METHOD("get_offset", "offset"), &Control::get_offset); ClassDB::bind_method(D_METHOD("get_begin"), &Control::get_begin); ClassDB::bind_method(D_METHOD("get_end"), &Control::get_end); ClassDB::bind_method(D_METHOD("get_position"), &Control::get_position); @@ -2996,37 +3326,54 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); - ADD_GROUP("Anchor", "anchor_"); + ADD_GROUP("Layout", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); + ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); + + const String anchors_presets_options = "Custom:-1,PresetWide:15," + "PresetTopLeft:0,PresetTopRight:1,PresetBottomRight:3,PresetBottomLeft:2," + "PresetCenterLeft:4,PresetCenterTop:5,PresetCenterRight:6,PresetCenterBottom:7,PresetCenter:8," + "PresetLeftWide:9,PresetTopWide:10,PresetRightWide:11,PresetBottomWide:12,PresetVCenterWide:13,PresetHCenterWide:14"; + + ADD_PROPERTY(PropertyInfo(Variant::INT, "anchors_preset", PROPERTY_HINT_ENUM, anchors_presets_options, PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_anchors_layout_preset", "_get_anchors_layout_preset"); + ADD_PROPERTY_DEFAULT("anchors_preset", -1); + + ADD_SUBGROUP_INDENT("Anchor Points", "anchor_", 1); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_TOP); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_RIGHT); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM); - ADD_GROUP("Offset", "offset_"); + ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_LEFT); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_TOP); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_RIGHT); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_BOTTOM); - ADD_GROUP("Grow Direction", "grow_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); - - ADD_GROUP("Layout Direction", "layout_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); + ADD_SUBGROUP_INDENT("Grow Direction", "grow_", 1); + ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Left,Right,Both"), "set_h_grow_direction", "get_h_grow_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Top,Bottom,Both"), "set_v_grow_direction", "get_v_grow_direction"); - ADD_GROUP("Auto Translate", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); - - ADD_GROUP("Rect", "rect_"); + ADD_SUBGROUP("Rectangle", "rect_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); + + ADD_SUBGROUP("Transform", "rect_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rect_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_pivot_offset"), "set_pivot_offset", "get_pivot_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents"); + + ADD_SUBGROUP("Container Sizing", "size_flags_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_h_size_flags", "get_h_size_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_v_size_flags", "get_v_size_flags"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); + + ADD_GROUP("Auto Translate", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); ADD_GROUP("Hint", "hint_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); @@ -3044,11 +3391,6 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); - ADD_GROUP("Size Flags", "size_flags_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_v_size_flags", "get_v_size_flags"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); - ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); @@ -3107,6 +3449,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_HEIGHT); BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_SIZE); + BIND_ENUM_CONSTANT(SIZE_SHRINK_BEGIN); BIND_ENUM_CONSTANT(SIZE_FILL); BIND_ENUM_CONSTANT(SIZE_EXPAND); BIND_ENUM_CONSTANT(SIZE_EXPAND_FILL); diff --git a/scene/gui/control.h b/scene/gui/control.h index 962135280f..7024164e6c 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -67,12 +67,13 @@ public: }; enum SizeFlags { + SIZE_SHRINK_BEGIN = 0, SIZE_FILL = 1, SIZE_EXPAND = 2, - SIZE_EXPAND_FILL = SIZE_EXPAND | SIZE_FILL, - SIZE_SHRINK_CENTER = 4, //ignored by expand or fill - SIZE_SHRINK_END = 8, //ignored by expand or fill + SIZE_SHRINK_CENTER = 4, + SIZE_SHRINK_END = 8, + SIZE_EXPAND_FILL = SIZE_EXPAND | SIZE_FILL, }; enum MouseFilter { @@ -128,6 +129,13 @@ public: PRESET_MODE_KEEP_SIZE }; + enum LayoutMode { + LAYOUT_MODE_POSITION, + LAYOUT_MODE_ANCHORS, + LAYOUT_MODE_CONTAINER, + LAYOUT_MODE_UNCONTROLLED, + }; + enum LayoutDirection { LAYOUT_DIRECTION_INHERITED, LAYOUT_DIRECTION_LOCALE, @@ -230,7 +238,7 @@ private: } data; - static constexpr unsigned properties_managed_by_container_count = 11; + static constexpr unsigned properties_managed_by_container_count = 12; static String properties_managed_by_container[properties_managed_by_container_count]; void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest); @@ -241,6 +249,12 @@ private: void _set_global_position(const Point2 &p_point); void _set_size(const Size2 &p_size); + void _set_layout_mode(LayoutMode p_mode); + LayoutMode _get_layout_mode() const; + + void _set_anchors_layout_preset(int p_preset); + int _get_anchors_layout_preset() const; + void _theme_changed(); void _notify_theme_changed(); @@ -285,9 +299,10 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + virtual void _validate_property(PropertyInfo &property) const override; + void _notification(int p_notification); static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const override; //bind helpers @@ -378,6 +393,7 @@ public: void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); + void set_grow_direction_preset(LayoutPreset p_preset); void set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true); real_t get_anchor(Side p_side) const; @@ -563,6 +579,7 @@ VARIANT_ENUM_CAST(Control::LayoutPresetMode); VARIANT_ENUM_CAST(Control::MouseFilter); VARIANT_ENUM_CAST(Control::GrowDirection); VARIANT_ENUM_CAST(Control::Anchor); +VARIANT_ENUM_CAST(Control::LayoutMode); VARIANT_ENUM_CAST(Control::LayoutDirection); VARIANT_ENUM_CAST(Control::TextDirection); VARIANT_ENUM_CAST(Control::StructuredTextParser); diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index d1ac60b325..ba487b2905 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -223,6 +223,30 @@ Size2 FlowContainer::get_minimum_size() const { return minimum; } +Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> FlowContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void FlowContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index e3ed423ae1..84f2ae4ae3 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -54,6 +54,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + FlowContainer(bool p_vertical = false); }; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 30f6cf4a14..e0c59dd1bf 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -959,6 +959,25 @@ bool GraphNode::is_resizable() const { return resizable; } +Vector<int> GraphNode::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> GraphNode::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphNode::set_title); ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index b41fc7f5d4..7eb5f27cff 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -182,6 +182,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + bool is_resizing() const { return resizing; } GraphNode(); diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 7b696ddb84..89008a19c5 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -65,6 +65,24 @@ Size2 MarginContainer::get_minimum_size() const { return max; } +Vector<int> MarginContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> MarginContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void MarginContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { diff --git a/scene/gui/margin_container.h b/scene/gui/margin_container.h index 3a2f0fa8b3..f8a3c5bb11 100644 --- a/scene/gui/margin_container.h +++ b/scene/gui/margin_container.h @@ -42,6 +42,9 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + MarginContainer(); }; diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index 463ad3c513..91a343084b 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -60,6 +60,24 @@ Size2 PanelContainer::get_minimum_size() const { return ms; } +Vector<int> PanelContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> PanelContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void PanelContainer::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); diff --git a/scene/gui/panel_container.h b/scene/gui/panel_container.h index a5ff74cebb..8f07ce38eb 100644 --- a/scene/gui/panel_container.h +++ b/scene/gui/panel_container.h @@ -42,6 +42,9 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + PanelContainer(); }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 4865b9770e..b15e75b1c0 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -33,6 +33,7 @@ #include "core/math/math_defs.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "label.h" #include "scene/scene_string_names.h" #include "servers/display_server.h" @@ -1590,6 +1591,9 @@ void RichTextLabel::_notification(int p_what) { update(); } } break; + case NOTIFICATION_DRAG_END: { + selection.drag_attempt = false; + } break; } } @@ -1650,6 +1654,8 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { int c_index = 0; bool outside; + selection.drag_attempt = false; + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); if (c_item != nullptr) { if (selection.enabled) { @@ -1660,17 +1666,22 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { // Erase previous selection. if (selection.active) { - selection.from_frame = nullptr; - selection.from_line = 0; - selection.from_item = nullptr; - selection.from_char = 0; - selection.to_frame = nullptr; - selection.to_line = 0; - selection.to_item = nullptr; - selection.to_char = 0; - selection.active = false; - - update(); + if (_is_click_inside_selection()) { + selection.drag_attempt = true; + selection.click_item = nullptr; + } else { + selection.from_frame = nullptr; + selection.from_line = 0; + selection.from_item = nullptr; + selection.from_char = 0; + selection.to_frame = nullptr; + selection.to_line = 0; + selection.to_item = nullptr; + selection.to_char = 0; + selection.active = false; + + update(); + } } } } @@ -1683,6 +1694,8 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { int c_index = 0; bool outside; + selection.drag_attempt = false; + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); if (c_frame) { @@ -1714,6 +1727,22 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } selection.click_item = nullptr; + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (_is_click_inside_selection()) { + selection.from_frame = nullptr; + selection.from_line = 0; + selection.from_item = nullptr; + selection.from_char = 0; + selection.to_frame = nullptr; + selection.to_line = 0; + selection.to_item = nullptr; + selection.to_char = 0; + selection.active = false; + + update(); + } + } if (!b->is_double_click() && !scroll_updated) { Item *c_item = nullptr; @@ -3734,6 +3763,29 @@ void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) { } } +Variant RichTextLabel::get_drag_data(const Point2 &p_point) { + if (selection.drag_attempt && selection.enabled) { + String t = get_selected_text(); + Label *l = memnew(Label); + l->set_text(t); + set_drag_preview(l); + return t; + } + + return Variant(); +} + +bool RichTextLabel::_is_click_inside_selection() const { + if (selection.active && selection.enabled && selection.click_frame && selection.from_frame && selection.to_frame) { + const Line &l_click = selection.click_frame->lines[selection.click_line]; + const Line &l_from = selection.from_frame->lines[selection.from_line]; + const Line &l_to = selection.to_frame->lines[selection.to_line]; + return (l_click.char_offset + selection.click_char >= l_from.char_offset + selection.from_char) && (l_click.char_offset + selection.click_char <= l_to.char_offset + selection.to_char); + } else { + return false; + } +} + bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) { List<Item *>::Element *E = p_from; while (E != nullptr) { @@ -4157,6 +4209,14 @@ int RichTextLabel::get_content_height() const { return total_height; } +int RichTextLabel::get_content_width() const { + int total_width = 0; + for (int i = 0; i < main->lines.size(); i++) { + total_width = MAX(total_width, main->lines[i].offset.x + main->lines[i].text_buf->get_size().x); + } + return total_width; +} + #ifndef DISABLE_DEPRECATED // People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0) // Although some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty. @@ -4279,6 +4339,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_visible_paragraph_count"), &RichTextLabel::get_visible_paragraph_count); ClassDB::bind_method(D_METHOD("get_content_height"), &RichTextLabel::get_content_height); + ClassDB::bind_method(D_METHOD("get_content_width"), &RichTextLabel::get_content_width); ClassDB::bind_method(D_METHOD("parse_expressions_for_values", "expressions"), &RichTextLabel::parse_expressions_for_values); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index e79244f2e4..5ed1031798 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -407,6 +407,7 @@ private: bool active = false; // anything selected? i.e. from, to, etc. valid? bool enabled = false; // allow selections? + bool drag_attempt = false; }; Selection selection; @@ -416,6 +417,7 @@ private: float percent_visible = 1.0; VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING; + bool _is_click_inside_selection() const; void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr); String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const; @@ -554,10 +556,12 @@ public: int get_visible_line_count() const; int get_content_height() const; + int get_content_width() const; VScrollBar *get_v_scroll_bar() { return vscroll; } virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; + virtual Variant get_drag_data(const Point2 &p_point) override; void set_selection_enabled(bool p_enabled); bool is_selection_enabled() const; diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 874e5868b6..b56326088d 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -336,6 +336,30 @@ bool SplitContainer::is_collapsed() const { return collapsed; } +Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> SplitContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index ba6fff6f55..a69ffe4de9 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -79,6 +79,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + SplitContainer(bool p_vertical = false); }; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 760144591e..b8baefc307 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -91,6 +91,14 @@ int SubViewportContainer::get_stretch_shrink() const { return shrink; } +Vector<int> SubViewportContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} + void SubViewportContainer::_notification(int p_what) { if (p_what == NOTIFICATION_RESIZED) { if (!stretch) { diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index e7520763fb..3138a6144c 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -54,6 +54,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + SubViewportContainer(); }; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 818431a6a0..4bd3686e7c 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -1177,6 +1177,14 @@ bool TabContainer::get_use_hidden_tabs_for_min_size() const { return use_hidden_tabs_for_min_size; } +Vector<int> TabContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> TabContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} + void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_count"), &TabContainer::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 01e71e9fa8..ee1b3fea51 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -133,6 +133,9 @@ public: void set_use_hidden_tabs_for_min_size(bool p_use_hidden_tabs); bool get_use_hidden_tabs_for_min_size() const; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + TabContainer(); }; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index bb259843b8..5295acce8f 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -2472,7 +2472,7 @@ void TextEdit::_update_placeholder() { return; // Not in tree? } - // Placeholder is generally smaller then text docuemnts, and updates less so this should be fast enough for now. + // Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now. placeholder_data_buf->clear(); placeholder_data_buf->set_width(text.get_width()); placeholder_data_buf->set_direction((TextServer::Direction)text_direction); diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 240e662efb..09dfc50066 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -221,6 +221,7 @@ void ShaderGlobalsOverride::_get_property_list(List<PropertyInfo> *p_list) const } void ShaderGlobalsOverride::_activate() { + ERR_FAIL_NULL(get_tree()); List<Node *> nodes; get_tree()->get_nodes_in_group(SceneStringNames::get_singleton()->shader_overrides_group_active, &nodes); if (nodes.size() == 0) { diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp index 0cb703227b..2239a5d81c 100644 --- a/scene/multiplayer/scene_rpc_interface.cpp +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -459,7 +459,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m uint16_t rpc_id = UINT16_MAX; const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id); ERR_FAIL_COND_MSG(config.name == StringName(), - vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, node->get_path())); + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is missing or not marked for RPCs in the local script.", p_method, node->get_path())); if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { if (rpc_id & (1 << 15)) { call_local_native = config.call_local; diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index b13ae9d016..bf17a6ea97 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1332,7 +1332,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_min_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_sdfgi_min_cell_size", "get_sdfgi_min_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_cascade0_distance", PROPERTY_HINT_RANGE, "0.1,16384,0.1,or_greater"), "set_sdfgi_cascade0_distance", "get_sdfgi_cascade0_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_max_distance", PROPERTY_HINT_RANGE, "0.1,16384,0.1,or_greater"), "set_sdfgi_max_distance", "get_sdfgi_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sdfgi_y_scale", PROPERTY_HINT_ENUM, "Disable,75%,50%"), "set_sdfgi_y_scale", "get_sdfgi_y_scale"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sdfgi_y_scale", PROPERTY_HINT_ENUM, "50% (Compact),75% (Balanced),100% (Sparse)"), "set_sdfgi_y_scale", "get_sdfgi_y_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_energy"), "set_sdfgi_energy", "get_sdfgi_energy"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_normal_bias"), "set_sdfgi_normal_bias", "get_sdfgi_normal_bias"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sdfgi_probe_bias"), "set_sdfgi_probe_bias", "get_sdfgi_probe_bias"); @@ -1511,9 +1511,9 @@ void Environment::_bind_methods() { BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_REPLACE); BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_MIX); - BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_DISABLED); - BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_75_PERCENT); BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_50_PERCENT); + BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_75_PERCENT); + BIND_ENUM_CONSTANT(SDFGI_Y_SCALE_100_PERCENT); } Environment::Environment() { diff --git a/scene/resources/environment.h b/scene/resources/environment.h index b04723a221..dd1e664ca6 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -71,9 +71,9 @@ public: }; enum SDFGIYScale { - SDFGI_Y_SCALE_DISABLED, - SDFGI_Y_SCALE_75_PERCENT, SDFGI_Y_SCALE_50_PERCENT, + SDFGI_Y_SCALE_75_PERCENT, + SDFGI_Y_SCALE_100_PERCENT, }; enum GlowBlendMode { @@ -147,12 +147,12 @@ private: // SDFGI bool sdfgi_enabled = false; - int sdfgi_cascades = 6; + int sdfgi_cascades = 4; float sdfgi_min_cell_size = 0.2; - SDFGIYScale sdfgi_y_scale = SDFGI_Y_SCALE_DISABLED; + SDFGIYScale sdfgi_y_scale = SDFGI_Y_SCALE_75_PERCENT; bool sdfgi_use_occlusion = false; - float sdfgi_bounce_feedback = 0.0; - bool sdfgi_read_sky_light = false; + float sdfgi_bounce_feedback = 0.5; + bool sdfgi_read_sky_light = true; float sdfgi_energy = 1.0; float sdfgi_normal_bias = 1.1; float sdfgi_probe_bias = 1.1; diff --git a/servers/rendering/rasterizer_dummy.h b/servers/rendering/rasterizer_dummy.h index 7032f3fb03..7d5a596c50 100644 --- a/servers/rendering/rasterizer_dummy.h +++ b/servers/rendering/rasterizer_dummy.h @@ -72,11 +72,11 @@ public: /* SHADOW ATLAS API */ RID shadow_atlas_create() override { return RID(); } - void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = false) override {} + void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) override {} void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) override {} bool shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) override { return false; } - void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) override {} + void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) override {} int get_directional_light_shadow_size(RID p_light_intance) override { return 0; } void set_directional_shadow_count(int p_count) override {} diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 87301a9d3a..d113fcd4f0 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -969,10 +969,11 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con if (inst->fade_near || inst->fade_far) { float fade_dist = inst->transform.origin.distance_to(p_render_data->cam_transform.origin); + // Use `smoothstep()` to make opacity changes more gradual and less noticeable to the player. if (inst->fade_far && fade_dist > inst->fade_far_begin) { - fade_alpha = MAX(0.0, 1.0 - (fade_dist - inst->fade_far_begin) / (inst->fade_far_end - inst->fade_far_begin)); + fade_alpha = Math::smoothstep(0.0f, 1.0f, 1.0f - (fade_dist - inst->fade_far_begin) / (inst->fade_far_end - inst->fade_far_begin)); } else if (inst->fade_near && fade_dist < inst->fade_near_end) { - fade_alpha = MAX(0.0, (fade_dist - inst->fade_near_begin) / (inst->fade_near_end - inst->fade_near_begin)); + fade_alpha = Math::smoothstep(0.0f, 1.0f, (fade_dist - inst->fade_near_begin) / (inst->fade_near_end - inst->fade_near_begin)); } } @@ -1390,7 +1391,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co projection = correction * p_render_data->cam_projection; } - sky.setup(env, p_render_data->render_buffers, projection, p_render_data->cam_transform, screen_size, this); + sky.setup(env, p_render_data->render_buffers, *p_render_data->lights, projection, p_render_data->cam_transform, screen_size, this); RID sky_rid = env->sky; if (sky_rid.is_valid()) { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 778d7baa5d..a623af7533 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -633,7 +633,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color projection = correction * p_render_data->cam_projection; } - sky.setup(env, p_render_data->render_buffers, projection, p_render_data->cam_transform, screen_size, this); + sky.setup(env, p_render_data->render_buffers, *p_render_data->lights, projection, p_render_data->cam_transform, screen_size, this); RID sky_rid = env->sky; if (sky_rid.is_valid()) { diff --git a/servers/rendering/renderer_rd/renderer_scene_environment_rd.h b/servers/rendering/renderer_rd/renderer_scene_environment_rd.h index ed26fd467b..4e170b8cfb 100644 --- a/servers/rendering/renderer_rd/renderer_scene_environment_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_environment_rd.h @@ -135,15 +135,15 @@ public: /// SDFGI bool sdfgi_enabled = false; - int sdfgi_cascades = 6; + int sdfgi_cascades = 4; float sdfgi_min_cell_size = 0.2; bool sdfgi_use_occlusion = false; - float sdfgi_bounce_feedback = 0.0; - bool sdfgi_read_sky_light = false; + float sdfgi_bounce_feedback = 0.5; + bool sdfgi_read_sky_light = true; float sdfgi_energy = 1.0; float sdfgi_normal_bias = 1.1; float sdfgi_probe_bias = 1.1; - RS::EnvironmentSDFGIYScale sdfgi_y_scale = RS::ENV_SDFGI_Y_SCALE_DISABLED; + RS::EnvironmentSDFGIYScale sdfgi_y_scale = RS::ENV_SDFGI_Y_SCALE_75_PERCENT; /// Adjustments diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp index 3069b1c379..cb07c75db4 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp @@ -46,7 +46,7 @@ void RendererSceneGIRD::SDFGI::create(RendererSceneEnvironmentRD *p_env, const V min_cell_size = p_env->sdfgi_min_cell_size; uses_occlusion = p_env->sdfgi_use_occlusion; y_scale_mode = p_env->sdfgi_y_scale; - static const float y_scale[3] = { 1.0, 1.5, 2.0 }; + static const float y_scale[3] = { 2.0, 1.5, 1.0 }; y_mult = y_scale[y_scale_mode]; cascades.resize(num_cascades); probe_axis_count = SDFGI::PROBE_DIVISOR + 1; diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h index 5e55262798..25f0dca3b7 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.h @@ -392,7 +392,7 @@ public: return voxel_gi->texture; }; - RS::VoxelGIQuality voxel_gi_quality = RS::VOXEL_GI_QUALITY_HIGH; + RS::VoxelGIQuality voxel_gi_quality = RS::VOXEL_GI_QUALITY_LOW; /* SDFGI */ @@ -504,12 +504,12 @@ public: RID cascades_ubo; bool uses_occlusion = false; - float bounce_feedback = 0.0; - bool reads_sky = false; + float bounce_feedback = 0.5; + bool reads_sky = true; float energy = 1.0; float normal_bias = 1.1; float probe_bias = 1.1; - RS::EnvironmentSDFGIYScale y_scale_mode = RS::ENV_SDFGI_Y_SCALE_DISABLED; + RS::EnvironmentSDFGIYScale y_scale_mode = RS::ENV_SDFGI_Y_SCALE_75_PERCENT; float y_mult = 1.0; @@ -536,7 +536,7 @@ public: }; RS::EnvironmentSDFGIRayCount sdfgi_ray_count = RS::ENV_SDFGI_RAY_COUNT_16; - RS::EnvironmentSDFGIFramesToConverge sdfgi_frames_to_converge = RS::ENV_SDFGI_CONVERGE_IN_10_FRAMES; + RS::EnvironmentSDFGIFramesToConverge sdfgi_frames_to_converge = RS::ENV_SDFGI_CONVERGE_IN_30_FRAMES; RS::EnvironmentSDFGIFramesToUpdateLight sdfgi_frames_to_update_light = RS::ENV_SDFGI_UPDATE_LIGHT_IN_4_FRAMES; float sdfgi_solid_cell_ratio = 0.25; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 43a1812f89..2d34d2a2a0 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -3263,7 +3263,6 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const r_directional_light_count = 0; r_positional_light_count = 0; - sky.sky_scene_state.ubo.directional_light_count = 0; Plane camera_plane(-p_camera_transform.basis.get_axis(Vector3::AXIS_Z).normalized(), p_camera_transform.origin); @@ -3284,43 +3283,7 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const RS::LightType type = storage->light_get_type(base); switch (type) { case RS::LIGHT_DIRECTIONAL: { - // Copy to SkyDirectionalLightData - if (r_directional_light_count < sky.sky_scene_state.max_directional_lights) { - RendererSceneSkyRD::SkyDirectionalLightData &sky_light_data = sky.sky_scene_state.directional_lights[r_directional_light_count]; - Transform3D light_transform = li->transform; - Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); - - sky_light_data.direction[0] = world_direction.x; - sky_light_data.direction[1] = world_direction.y; - sky_light_data.direction[2] = -world_direction.z; - - float sign = storage->light_is_negative(base) ? -1 : 1; - sky_light_data.energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); - - Color linear_col = storage->light_get_color(base).to_linear(); - sky_light_data.color[0] = linear_col.r; - sky_light_data.color[1] = linear_col.g; - sky_light_data.color[2] = linear_col.b; - - sky_light_data.enabled = true; - - float angular_diameter = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); - if (angular_diameter > 0.0) { - // I know tan(0) is 0, but let's not risk it with numerical precision. - // technically this will keep expanding until reaching the sun, but all we care - // is expand until we reach the radius of the near plane (there can't be more occluders than that) - angular_diameter = Math::tan(Math::deg2rad(angular_diameter)); - if (storage->light_has_shadow(base)) { - r_directional_light_soft_shadows = true; - } - } else { - angular_diameter = 0.0; - } - sky_light_data.size = angular_diameter; - sky.sky_scene_state.ubo.directional_light_count++; - } - - if (r_directional_light_count >= cluster.max_directional_lights || storage->light_directional_is_sky_only(base)) { + if (r_directional_light_count >= cluster.max_directional_lights) { continue; } @@ -3397,6 +3360,9 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const // technically this will keep expanding until reaching the sun, but all we care // is expand until we reach the radius of the near plane (there can't be more occluders than that) angular_diameter = Math::tan(Math::deg2rad(angular_diameter)); + if (storage->light_has_shadow(base)) { + r_directional_light_soft_shadows = true; + } } else { angular_diameter = 0.0; } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 899d2d763d..09c828ba37 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -293,7 +293,7 @@ private: uint32_t smallest_subdiv = 0; int size = 0; - bool use_16_bits = false; + bool use_16_bits = true; RID depth; RID fb; //for copying @@ -333,7 +333,7 @@ private: int light_count = 0; int size = 0; - bool use_16_bits = false; + bool use_16_bits = true; int current_light = 0; } directional_shadow; @@ -981,7 +981,7 @@ public: /* SHADOW ATLAS API */ virtual RID shadow_atlas_create() override; - virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = false) override; + virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) override; virtual void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) override; virtual bool shadow_atlas_update_light(RID p_atlas, RID p_light_instance, float p_coverage, uint64_t p_light_version) override; _FORCE_INLINE_ bool shadow_atlas_owns_light_instance(RID p_atlas, RID p_light_intance) { @@ -1002,7 +1002,7 @@ public: return Size2(atlas->size, atlas->size); } - virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) override; + virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) override; virtual int get_directional_light_shadow_size(RID p_light_intance) override; virtual void set_directional_shadow_count(int p_count) override; diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp index f6f39230f8..354516ae87 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp @@ -1040,8 +1040,8 @@ RendererSceneSkyRD::~RendererSceneSkyRD() { RD::get_singleton()->free(index_buffer); //array gets freed as dependency } -void RendererSceneSkyRD::setup(RendererSceneEnvironmentRD *p_env, RID p_render_buffers, const CameraMatrix &p_projection, const Transform3D &p_transform, const Size2i p_screen_size, RendererSceneRenderRD *p_scene_render) { - ERR_FAIL_COND(!p_env); // I guess without an environment we also can't have a sky... +void RendererSceneSkyRD::setup(RendererSceneEnvironmentRD *p_env, RID p_render_buffers, const PagedArray<RID> &p_lights, const CameraMatrix &p_projection, const Transform3D &p_transform, const Size2i p_screen_size, RendererSceneRenderRD *p_scene_render) { + ERR_FAIL_COND(!p_env); SkyMaterialData *material = nullptr; Sky *sky = get_sky(p_env->sky); @@ -1122,15 +1122,67 @@ void RendererSceneSkyRD::setup(RendererSceneEnvironmentRD *p_env, RID p_render_b } if (shader_data->uses_light) { + sky_scene_state.ubo.directional_light_count = 0; + // Run through the list of lights in the scene and pick out the Directional Lights. + // This can't be done in RenderSceneRenderRD::_setup lights because that needs to be called + // after the depth prepass, but this runs before the depth prepass + for (int i = 0; i < (int)p_lights.size(); i++) { + RendererSceneRenderRD::LightInstance *li = p_scene_render->light_instance_owner.get_or_null(p_lights[i]); + if (!li) { + continue; + } + RID base = li->light; + + ERR_CONTINUE(base.is_null()); + + RS::LightType type = storage->light_get_type(base); + if (type == RS::LIGHT_DIRECTIONAL) { + SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.ubo.directional_light_count]; + Transform3D light_transform = li->transform; + Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); + + sky_light_data.direction[0] = world_direction.x; + sky_light_data.direction[1] = world_direction.y; + sky_light_data.direction[2] = -world_direction.z; + + float sign = storage->light_is_negative(base) ? -1 : 1; + sky_light_data.energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); + + Color linear_col = storage->light_get_color(base).to_linear(); + sky_light_data.color[0] = linear_col.r; + sky_light_data.color[1] = linear_col.g; + sky_light_data.color[2] = linear_col.b; + + sky_light_data.enabled = true; + + float angular_diameter = storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); + if (angular_diameter > 0.0) { + // I know tan(0) is 0, but let's not risk it with numerical precision. + // technically this will keep expanding until reaching the sun, but all we care + // is expand until we reach the radius of the near plane (there can't be more occluders than that) + angular_diameter = Math::tan(Math::deg2rad(angular_diameter)); + } else { + angular_diameter = 0.0; + } + sky_light_data.size = angular_diameter; + sky_scene_state.ubo.directional_light_count++; + if (sky_scene_state.ubo.directional_light_count >= sky_scene_state.max_directional_lights) { + break; + } + } + } // Check whether the directional_light_buffer changes bool light_data_dirty = false; + // Light buffer is dirty if we have fewer or more lights + // If we have fewer lights, make sure that old lights are disabled if (sky_scene_state.ubo.directional_light_count != sky_scene_state.last_frame_directional_light_count) { light_data_dirty = true; for (uint32_t i = sky_scene_state.ubo.directional_light_count; i < sky_scene_state.max_directional_lights; i++) { sky_scene_state.directional_lights[i].enabled = false; } } + if (!light_data_dirty) { for (uint32_t i = 0; i < sky_scene_state.ubo.directional_light_count; i++) { if (sky_scene_state.directional_lights[i].direction[0] != sky_scene_state.last_frame_directional_lights[i].direction[0] || diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h b/servers/rendering/renderer_rd/renderer_scene_sky_rd.h index d81a415c2d..13d24e2508 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_sky_rd.h @@ -292,7 +292,7 @@ public: void set_texture_format(RD::DataFormat p_texture_format); ~RendererSceneSkyRD(); - void setup(RendererSceneEnvironmentRD *p_env, RID p_render_buffers, const CameraMatrix &p_projection, const Transform3D &p_transform, const Size2i p_screen_size, RendererSceneRenderRD *p_scene_render); + void setup(RendererSceneEnvironmentRD *p_env, RID p_render_buffers, const PagedArray<RID> &p_lights, const CameraMatrix &p_projection, const Transform3D &p_transform, const Size2i p_screen_size, RendererSceneRenderRD *p_scene_render); void update(RendererSceneEnvironmentRD *p_env, const CameraMatrix &p_projection, const Transform3D &p_transform, double p_time, float p_luminance_multiplier = 1.0); void draw(RendererSceneEnvironmentRD *p_env, bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time); // only called by clustered renderer void update_res_buffers(RendererSceneEnvironmentRD *p_env, uint32_t p_view_count, const CameraMatrix *p_projections, const Transform3D &p_transform, double p_time, float p_luminance_multiplier = 1.0); diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index d22f936a35..16f77fb91a 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -877,17 +877,17 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)); splane /= splane.w; - vec2 proj_uv = normal_to_panorama(splane.xyz) * spot_lights.data[idx].projector_rect.zw; + vec2 proj_uv = splane.xy * spot_lights.data[idx].projector_rect.zw; if (sc_projector_use_mipmaps) { //ensure we have proper mipmaps vec4 splane_ddx = (spot_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0)); splane_ddx /= splane_ddx.w; - vec2 proj_uv_ddx = normal_to_panorama(splane_ddx.xyz) * spot_lights.data[idx].projector_rect.zw - proj_uv; + vec2 proj_uv_ddx = splane_ddx.xy * spot_lights.data[idx].projector_rect.zw - proj_uv; vec4 splane_ddy = (spot_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddy, 1.0)); splane_ddy /= splane_ddy.w; - vec2 proj_uv_ddy = normal_to_panorama(splane_ddy.xyz) * spot_lights.data[idx].projector_rect.zw - proj_uv; + vec2 proj_uv_ddy = splane_ddy.xy * spot_lights.data[idx].projector_rect.zw - proj_uv; vec4 proj = textureGrad(sampler2D(decal_atlas_srgb, light_projector_sampler), proj_uv + spot_lights.data[idx].projector_rect.xy, proj_uv_ddx, proj_uv_ddy); color *= proj.rgb * proj.a; diff --git a/servers/rendering/renderer_scene.h b/servers/rendering/renderer_scene.h index 406d946e12..4c58fce1c9 100644 --- a/servers/rendering/renderer_scene.h +++ b/servers/rendering/renderer_scene.h @@ -105,7 +105,7 @@ public: virtual Variant instance_geometry_get_shader_parameter(RID p_instance, const StringName &p_parameter) const = 0; virtual Variant instance_geometry_get_shader_parameter_default_value(RID p_instance, const StringName &p_parameter) const = 0; - virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) = 0; + virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) = 0; /* SKY API */ @@ -187,7 +187,7 @@ public: virtual void directional_shadow_quality_set(RS::ShadowQuality p_quality) = 0; virtual RID shadow_atlas_create() = 0; - virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_use_16_bits = false) = 0; + virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_use_16_bits = true) = 0; virtual void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) = 0; /* Render Buffers */ diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h index 3eb90ccc4d..cedf6575fd 100644 --- a/servers/rendering/renderer_scene_render.h +++ b/servers/rendering/renderer_scene_render.h @@ -80,11 +80,11 @@ public: /* SHADOW ATLAS API */ virtual RID shadow_atlas_create() = 0; - virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = false) = 0; + virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) = 0; virtual void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) = 0; virtual bool shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) = 0; - virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) = 0; + virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) = 0; virtual int get_directional_light_shadow_size(RID p_light_intance) = 0; virtual void set_directional_shadow_count(int p_count) = 0; diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 7bc8f9961b..2245d9a216 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -91,7 +91,7 @@ public: RID shadow_atlas; int shadow_atlas_size; - bool shadow_atlas_16_bits = false; + bool shadow_atlas_16_bits = true; bool sdf_active; @@ -247,7 +247,7 @@ public: void viewport_set_global_canvas_transform(RID p_viewport, const Transform2D &p_transform); void viewport_set_canvas_stacking(RID p_viewport, RID p_canvas, int p_layer, int p_sublayer); - void viewport_set_shadow_atlas_size(RID p_viewport, int p_size, bool p_16_bits = false); + void viewport_set_shadow_atlas_size(RID p_viewport, int p_size, bool p_16_bits = true); void viewport_set_shadow_atlas_quadrant_subdivision(RID p_viewport, int p_quadrant, int p_subdiv); void viewport_set_msaa(RID p_viewport, RS::ViewportMSAA p_msaa); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index ead196b7dd..91201b2028 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -7871,7 +7871,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct if (is_sampler_type(type)) { if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE) { - _set_error(vformat(RTR("Uniforms with '%s' qualifiers can't be of sampler type.", "instance"))); + _set_error(vformat(RTR("The '%s' qualifier is not supported for sampler types."), "SCOPE_INSTANCE")); return ERR_PARSE_ERROR; } uniform2.texture_order = texture_uniforms++; @@ -7887,7 +7887,7 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct } } else { if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE && (type == TYPE_MAT2 || type == TYPE_MAT3 || type == TYPE_MAT4)) { - _set_error(vformat(RTR("Uniforms with '%s' qualifier can't be of matrix type.", "instance"))); + _set_error(vformat(RTR("The '%s' qualifier is not supported for matrix types."), "SCOPE_INSTANCE")); return ERR_PARSE_ERROR; } uniform2.texture_order = -1; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index ac181cb5eb..2037268134 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2388,9 +2388,9 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(ENV_SSIL_QUALITY_HIGH); BIND_ENUM_CONSTANT(ENV_SSIL_QUALITY_ULTRA); - BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_DISABLED); - BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_75_PERCENT); BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_50_PERCENT); + BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_75_PERCENT); + BIND_ENUM_CONSTANT(ENV_SDFGI_Y_SCALE_100_PERCENT); BIND_ENUM_CONSTANT(ENV_SDFGI_RAY_COUNT_4); BIND_ENUM_CONSTANT(ENV_SDFGI_RAY_COUNT_8); @@ -2830,12 +2830,12 @@ RenderingServer::RenderingServer() { GLOBAL_DEF("rendering/shadows/directional_shadow/size", 4096); GLOBAL_DEF("rendering/shadows/directional_shadow/size.mobile", 2048); ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/directional_shadow/size", PropertyInfo(Variant::INT, "rendering/shadows/directional_shadow/size", PROPERTY_HINT_RANGE, "256,16384")); - GLOBAL_DEF("rendering/shadows/directional_shadow/soft_shadow_quality", 3); + GLOBAL_DEF("rendering/shadows/directional_shadow/soft_shadow_quality", 2); GLOBAL_DEF("rendering/shadows/directional_shadow/soft_shadow_quality.mobile", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/directional_shadow/soft_shadow_quality", PropertyInfo(Variant::INT, "rendering/shadows/directional_shadow/soft_shadow_quality", PROPERTY_HINT_ENUM, "Hard (Fastest),Soft Very Low (Faster),Soft Low (Fast),Soft Medium (Average),Soft High (Slow),Soft Ultra (Slowest)")); GLOBAL_DEF("rendering/shadows/directional_shadow/16_bits", true); - GLOBAL_DEF("rendering/shadows/shadows/soft_shadow_quality", 3); + GLOBAL_DEF("rendering/shadows/shadows/soft_shadow_quality", 2); GLOBAL_DEF("rendering/shadows/shadows/soft_shadow_quality.mobile", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/shadows/shadows/soft_shadow_quality", PropertyInfo(Variant::INT, "rendering/shadows/shadows/soft_shadow_quality", PROPERTY_HINT_ENUM, "Hard (Fastest),Soft Very Low (Faster),Soft Low (Fast),Soft Medium (Average),Soft High (Slow),Soft Ultra (Slowest)")); @@ -2872,7 +2872,7 @@ RenderingServer::RenderingServer() { GLOBAL_DEF("rendering/global_illumination/gi/use_half_resolution", false); - GLOBAL_DEF("rendering/global_illumination/voxel_gi/quality", 1); + GLOBAL_DEF("rendering/global_illumination/voxel_gi/quality", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/global_illumination/voxel_gi/quality", PropertyInfo(Variant::INT, "rendering/global_illumination/voxel_gi/quality", PROPERTY_HINT_ENUM, "Low (4 Cones - Fast),High (6 Cones - Slow)")); GLOBAL_DEF("rendering/shading/overrides/force_vertex_shading", false); @@ -2979,7 +2979,7 @@ RenderingServer::RenderingServer() { GLOBAL_DEF("rendering/global_illumination/sdfgi/probe_ray_count", 1); ProjectSettings::get_singleton()->set_custom_property_info("rendering/global_illumination/sdfgi/probe_ray_count", PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/probe_ray_count", PROPERTY_HINT_ENUM, "8 (Fastest),16,32,64,96,128 (Slowest)")); - GLOBAL_DEF("rendering/global_illumination/sdfgi/frames_to_converge", 4); + GLOBAL_DEF("rendering/global_illumination/sdfgi/frames_to_converge", 5); ProjectSettings::get_singleton()->set_custom_property_info("rendering/global_illumination/sdfgi/frames_to_converge", PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/frames_to_converge", PROPERTY_HINT_ENUM, "5 (Less Latency but Lower Quality),10,15,20,25,30 (More Latency but Higher Quality)")); GLOBAL_DEF("rendering/global_illumination/sdfgi/frames_to_update_lights", 2); ProjectSettings::get_singleton()->set_custom_property_info("rendering/global_illumination/sdfgi/frames_to_update_lights", PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/frames_to_update_lights", PROPERTY_HINT_ENUM, "1 (Slower),2,4,8,16 (Faster)")); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index d21f3a3299..5e58afe718 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -473,7 +473,7 @@ public: virtual void light_directional_set_blend_splits(RID p_light, bool p_enable) = 0; virtual void light_directional_set_sky_only(RID p_light, bool p_sky_only) = 0; - virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = false) = 0; + virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) = 0; enum ShadowQuality { SHADOW_QUALITY_HARD, @@ -847,7 +847,7 @@ public: virtual void viewport_set_sdf_oversize_and_scale(RID p_viewport, ViewportSDFOversize p_oversize, ViewportSDFScale p_scale) = 0; - virtual void viewport_set_shadow_atlas_size(RID p_viewport, int p_size, bool p_16_bits = false) = 0; + virtual void viewport_set_shadow_atlas_size(RID p_viewport, int p_size, bool p_16_bits = true) = 0; virtual void viewport_set_shadow_atlas_quadrant_subdivision(RID p_viewport, int p_quadrant, int p_subdiv) = 0; enum ViewportMSAA { @@ -1042,9 +1042,9 @@ public: virtual void environment_set_ssil_quality(EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) = 0; enum EnvironmentSDFGIYScale { - ENV_SDFGI_Y_SCALE_DISABLED, + ENV_SDFGI_Y_SCALE_50_PERCENT, ENV_SDFGI_Y_SCALE_75_PERCENT, - ENV_SDFGI_Y_SCALE_50_PERCENT + ENV_SDFGI_Y_SCALE_100_PERCENT, }; virtual void environment_set_sdfgi(RID p_env, bool p_enable, int p_cascades, float p_min_cell_size, EnvironmentSDFGIYScale p_y_scale, bool p_use_occlusion, float p_bounce_feedback, bool p_read_sky, float p_energy, float p_normal_bias, float p_probe_bias) = 0; diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h index 136a531946..847a7c0b3f 100644 --- a/tests/core/math/test_vector3.h +++ b/tests/core/math/test_vector3.h @@ -58,7 +58,7 @@ TEST_CASE("[Vector3] Angle methods") { CHECK_MESSAGE( Math::is_equal_approx(vector_x.signed_angle_to(vector_y, vector_y), (real_t)Math_TAU / 4), - "Vector3 signed_angle_to edge case should be postiive."); + "Vector3 signed_angle_to edge case should be positive."); CHECK_MESSAGE( Math::is_equal_approx(vector_x.signed_angle_to(vector_yz, vector_y), (real_t)Math_TAU / -4), "Vector3 signed_angle_to should work as expected."); |